diff --git a/.gitattributes b/.gitattributes index 74550ae5b1..ef9757df4a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,11 +1,20 @@ # Set default behavior, in case users don't have core.autocrlf set. * text=auto +*.bat text eol=crlf +*.cmd text eol=crlf +*.psm1 text eol=crlf +*.psd1 text eol=crlf #Make the textual files have the text setting, so things never have the wrong line endings. *.java text *.xml text *.txt text *.yml text +*.ms text +*.msa text +*.md text +*.html text +*.js text *.png binary *.jpg binary diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml new file mode 100644 index 0000000000..1bd3b16322 --- /dev/null +++ b/.github/workflows/maven-publish.yml @@ -0,0 +1,88 @@ +# This workflow will build a package using Maven and then publish it to GitHub packages when a release is created +# For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#apache-maven-with-a-settings-path + +name: Maven Package + +on: + push: + branches: [ master ] +jobs: + build_linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 2 + uses: actions/setup-java@v2 + with: + java-version: '21' + distribution: 'adopt' + - name: Build with Maven + run: sudo -- sh -c "export methodscript_run_llvm_integration_tests=1 && export PATH=$JAVA_HOME/bin:\$PATH && export JAVA_HOME=$JAVA_HOME && mvn -version && mvn -B clean package --file pom.xml -Pfail-on-test-failures" + build_windows: + runs-on: windows-latest + env: + methodscript_run_llvm_integration_tests: 1 + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 21 + uses: actions/setup-java@v2 + with: + java-version: '21' + distribution: 'adopt' + - name: Build with Maven + run: mvn -B clean package --file pom.xml -Pfail-on-test-failures + build_mac: + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 21 + uses: actions/setup-java@v2 + env: + methodscript_run_llvm_integration_tests: 1 + with: + java-version: '21' + distribution: 'adopt' + - name: Build with Maven + run: mvn -version && mvn -B clean package --file pom.xml -Pfail-on-test-failures + publish: + needs: + - build_windows # For now only windows, eventually all 3, once they're stable. + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 21 + uses: actions/setup-java@v2 + with: + java-version: '21' + distribution: 'adopt' + server-id: github # Value of the distributionManagement/repository/id field of the pom.xml + settings-path: ${{ github.workspace }} # location for the settings.xml file + - uses: actions/checkout@v2 + - name: Find and Replace + uses: jacobtomlinson/gha-find-replace@master + with: + input: "plugin.yml" + find: 'version: "\${project\.version}"' + replace: 'version: "${project.version}-${{ github.RUN_NUMBER }}"' + - name: Package Build + run: mvn clean package + env: + GITHUB_TOKEN: ${{ github.token }} + - name: Upload to Blob Store + uses: bacongobbler/azure-blob-storage-upload@v1.2.0 + with: + source_dir: target + container_name: commandhelperjar + connection_string: ${{ secrets.BlobStoreConnectionString }} + extra_args: --pattern commandhelper*-full.jar --destination-path build-${{ github.RUN_NUMBER }} + sync: false + - uses: LadyCailin/azure-table-storage-upload@v1.0.0 + with: + table_name: BuildInfo + partition_key: commandhelperjar + row_key: build-${{ github.RUN_NUMBER }} + data: "SHA=${{ github.SHA }}" + connection_string: ${{ secrets.BlobStoreConnectionString }} diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 0000000000..1c0eeda005 --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,36 @@ +# 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: + pull_request: + branches: [ master ] + +jobs: + build_linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 21 + uses: actions/setup-java@v2 + with: + java-version: '21' + distribution: 'adopt' + - name: Build with Maven + run: sudo -- sh -c "export methodscript_run_llvm_integration_tests=1 && export PATH=$JAVA_HOME/bin:\$PATH && export JAVA_HOME=$JAVA_HOME && mvn -version && mvn -B clean package --file pom.xml -Pfail-on-test-failures" + build_windows: + runs-on: windows-latest + env: + methodscript_run_llvm_integration_tests: 1 + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 21 + uses: actions/setup-java@v2 + with: + java-version: '21' + distribution: 'adopt' + - name: Build with Maven + run: mvn -B clean package --file pom.xml -Pfail-on-test-failures + + diff --git a/.gitignore b/.gitignore index ec5f1cb172..d82c2f66db 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,346 @@ /target/ /.settings/ +/.idea/ /plugins/ /com/ /CommandHelper/ /bin/ .classpath .project +*.iml +nbproject/ nbactions-provisional-build.xml nbactions-release-profile.xml nbactions.xml -true /logs/ /.logs/ /preferences.* .DS_Store -/test-backend/ \ No newline at end of file +/test-backend/ +/nbproject/ +dependency-reduced-pom.xml + +desktop.ini + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +.checkstyle + +# MethodScript +persistence.db +persistence.json + +# TSP +src/main/resources/apps.methodscript.com/tsp-output + +# VSCode +.vscode/* \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000000..4195b885eb --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,22 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000000..79ee123c2b --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.run/CommandHelper [clean].run.xml b/.run/CommandHelper [clean].run.xml new file mode 100644 index 0000000000..b8c3e35b82 --- /dev/null +++ b/.run/CommandHelper [clean].run.xml @@ -0,0 +1,31 @@ + + + + + + + + \ No newline at end of file diff --git a/.run/CommandHelper [fail-on-test-failures].run.xml b/.run/CommandHelper [fail-on-test-failures].run.xml new file mode 100644 index 0000000000..a01fc5299e --- /dev/null +++ b/.run/CommandHelper [fail-on-test-failures].run.xml @@ -0,0 +1,31 @@ + + + + + + + + \ No newline at end of file diff --git a/.run/CommandHelper [provisional-build].run.xml b/.run/CommandHelper [provisional-build].run.xml new file mode 100644 index 0000000000..a66c522da8 --- /dev/null +++ b/.run/CommandHelper [provisional-build].run.xml @@ -0,0 +1,31 @@ + + + + + + + + \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000000..fdcbe45502 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,14 @@ +{ + "recommendations": [ + "redhat.java", + "shengchen.vscode-checkstyle", + "vscjava.vscode-java-debug", + "vscjava.vscode-java-dependency", + "vscjava.vscode-java-pack", + "vscjava.vscode-java-test", + "vscjava.vscode-maven", + ], + "unwantedRecommendations": [ + + ] +} \ No newline at end of file diff --git a/.vscode/java-formatter.xml b/.vscode/java-formatter.xml new file mode 100644 index 0000000000..a6dbda7f64 --- /dev/null +++ b/.vscode/java-formatter.xml @@ -0,0 +1,382 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..07efae992c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,13 @@ +{ + "java.compile.nullAnalysis.mode": "automatic", + "java.checkstyle.configuration": "${workspaceFolder}/checkstyle.xml", + "java.checkstyle.properties": { + "config_loc": "${workspaceFolder}", + }, + "java.checkstyle.autocheck": true, + "java.format.settings.url": ".vscode/java-formatter.xml", + "[java]": { + "editor.insertSpaces": false + }, + "files.trimTrailingWhitespace": true, +} \ No newline at end of file diff --git a/CHANGELOG.txt b/CHANGELOG.txt deleted file mode 100644 index 7cf15f0174..0000000000 --- a/CHANGELOG.txt +++ /dev/null @@ -1,44 +0,0 @@ -3.1.2: -- Added exception handling, and fixed more bugs with command matching and -optional variables. Also added more advanced permission syntax for commands. -- Added functions: set_display_name, reset_display_name, try, array_index_exists, -is_array, is_boolean, is_double, is_integer, is_null, is_string, substr, to_lower, -to_upper, length, throw -- Newlines are now handled in an expected manner in all functions. The escape -sequence '\n' has been added, which allows for newlines to be embedded in a -string in a script. --Command labels can now be used to specify what groups have permission to run this -command - -3.1.1: --Fixed a bug where no argument functions in the root of the command would give a -compile error. - -3.1.0: -- Added several new functions, including eval, set_ploc, time, nano_time, break, -continue, get_worlds, pworld, pinfo, and kick. Also, reorganized the source code, -and made it so that if one command would cause a compiler error, the entire script -won't be affected, but just the one command. Also, concatenation automatically -happens now, the g() function is no longer necessary. Debug mode now outputs what -real command is being run, if debug mode is on. It is now possible to define -the location of your config file, in the preferences.txt file. See the upgrade -notices (on the wiki) for information about upgrading your scripts from -3.0.2 to 3.1.0. - -3.0.2: -- Added new functions to allow scripts to interact with the environment around - the player. - -3.0.1: -- Added several new functions, including a foreach loop, and the g function. - See the upgrade notices (on the wiki) for information about upgrading your - scripts from 3.0.0 to 3.0.1. - -3.0.0: -- Completly reworked the engine. Essentially a new plugin. - -2.0.1: -- Updated to work again. - -2.0: -- Updated for Bukkit. \ No newline at end of file diff --git a/CONTRIBUTING.txt b/CONTRIBUTING.txt index f321fa3f1b..bf368c6e33 100644 --- a/CONTRIBUTING.txt +++ b/CONTRIBUTING.txt @@ -20,6 +20,12 @@ Avoid lines longer than about 120 characters. When wrapping lines, follow these Use tabs for indentions, NOT spaces. This allows for tab length to be set by the client, and also allows for quicker backspacing if deleting indentation. +The following settings should be used in your IDE: + +- Do not expand tabs to spaces +- Number of spaces per indent should be 4 +- Tab size should be 4 + * Brace style Braces should be on the same line as the statement. if(condition) { @@ -48,7 +54,7 @@ All braceable statements MUST use braces, and newlines. If you have more than 2 commits, please rebase, unless the commit has already been reviewed, in which case new commits should remain separate. (This allows reviewers to just see the changes in the new commits, instead of having to re-review all changes.) -Do not make formatting changes to unrelated code, unless the entire commit is *soley* formatting +Do not make formatting changes to unrelated code, unless the entire commit is *solely* formatting changes. In general though, even that is discouraged, because the full commit needs to be reviewed regardless, and it's far easier for a single individual to do global code cleanup tools, rather than many individuals. diff --git a/LICENSE.txt b/LICENSE.txt index 5c5ef27bf8..15b6804ff7 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,7 +1,16 @@ +This project is dual licensed. It is released under the MIT license, and the +GPL license. Both licenses are attached. You are free to choose which license +you wish to distribute the project under, if you are planning on distributing +it. Simply using the project does not require you to choose. If you are an +extension author, you must choose which license you wish for your project to +be licensed under, if it is licensed under the GPL, you have additional +responsibilities. If it is licensed under the MIT, you have no additional +responsibilities. + +-------------------------------- MIT License -------------------------------- The MIT License (MIT) -Copyright (c) 2012 Layton Smith, sk89q, Deaygo, -t3hk0d3, zml2008, EntityReborn, and albatrossen +Copyright (c) 2011-2019 by various contributors 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 @@ -19,3 +28,680 @@ 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. + +-------------------------------- GPL License -------------------------------- + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/LLVMFunctionTracking.xlsx b/LLVMFunctionTracking.xlsx new file mode 100644 index 0000000000..c1edfbe744 Binary files /dev/null and b/LLVMFunctionTracking.xlsx differ diff --git a/README.html b/README.html deleted file mode 100644 index 2f3ff5a425..0000000000 --- a/README.html +++ /dev/null @@ -1,95 +0,0 @@ - - - -CommandHelper ${version} - - - - -
- -

CommandHelper ${version}

-

Thanks for choosing CommandHelper!

-

First Steps

- -

Now, to begin, you want to:

-
    -
  1. Install the plugin: Copy CommandHelper.jar into your Bukkit server's "plugins" directory. If the folder doesn't exist, create it.
  2. -
  3. Setup CommandHelper: Visit the CommandHelper wiki to learn how to setup aliases, permissions, and write scripts.
  4. -
-

Gotcha: You need to have WorldEdit installed! It's used by CommandHelper in a number of places. If you don't want to install the plugin, that's okay — just put WorldEdit.jar into your Bukkit root folder (outside of plugins/) and CommandHelper will still be able use it. Alternatively, download the version of CommandHelper that has WorldEdit bundled in.

-

Be sure to:

- -

Want to make CommandHelper better?

-

CommandHelper is entirely open source! You can download all of the plugin code to learn from it, modify it for your own self, or even contribute back!

- -

The code is covered under the MIT License.

-

Do you like CommandHelper? I spend a lot of time working on the plugin, and donations are appreciated. Check out the link on the right

-

Maven information: ${groupId}/${artifactId}/${version}

-
- - \ No newline at end of file diff --git a/README.md b/README.md index 494dad08ca..e02ef150f5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,16 @@ CommandHelper ============= +| Service | Badge | +|--------|---------:| +| Github Packages | [![Maven Package](https://github.com/EngineHub/CommandHelper/actions/workflows/maven-publish.yml/badge.svg)](https://github.com/EngineHub/CommandHelper/actions/workflows/maven-publish.yml) | +| Azure | [![Azure Build Status](https://dev.azure.com/MethodScript/CommandHelper/_apis/build/status/EngineHub.CommandHelper)](https://dev.azure.com/MethodScript/CommandHelper/_build/latest?definitionId=1) | +| Snyk.io | [![Known Vulnerabilities](https://snyk.io/test/github/EngineHub/CommandHelper/badge.svg)](https://snyk.io/test/github/EngineHub/CommandHelper) | +| Discord | [![Discord](https://img.shields.io/discord/446057847428481044.svg)](https://img.shields.io/discord/446057847428481044.svg) | +| Code Size | [![Code Size](https://img.shields.io/github/languages/code-size/EngineHub/CommandHelper.svg)](https://img.shields.io/github/languages/code-size/EngineHub/CommandHelper.svg) | +| Stars | [![Stars](https://img.shields.io/github/stars/EngineHub/CommandHelper.svg?style=social)](https://img.shields.io/github/stars/EngineHub/CommandHelper.svg?style=social) | +| Website | [![Website](https://img.shields.io/website/https/methodscript.com.svg?down_color=red&down_message=offline&up_color=green&up_message=online)](https://methodscript.com) | +| Contributors | [![Contributors](https://img.shields.io/github/contributors/EngineHub/CommandHelper.svg)](https://img.shields.io/github/contributors/EngineHub/CommandHelper.svg) | +| Last Commit | [![Last Commit](https://img.shields.io/github/last-commit/EngineHub/CommandHelper.svg)](https://img.shields.io/github/last-commit/EngineHub/CommandHelper.svg) | CommandHelper adds simple command aliases, complex macros, and the ability to script your own commands and events into Minecraft, @@ -11,7 +22,7 @@ Compiling You need to have Maven installed (http://maven.apache.org). Once installed, simply run: - mvn clean package install + mvn clean package install Maven will automatically download dependencies for you. Note: For that to work, be sure to add Maven to your "PATH". If you get a message about tests failing, @@ -40,4 +51,44 @@ explained in the SPECIAL_LICENSE.txt file, which is attached. For details about code formatting standards, and other basic information for contributors, please see the CONTRIBUTING.txt file. -Portions of CommandHelper are copyright by various contributors. \ No newline at end of file +Portions of CommandHelper are copyright by various contributors. + +This project uses BrowserStack (https://www.browserstack.com) for testing the website. + +Installing +---------- + +To install on Windows, you can follow the directions below (which are cross platform) or simply download the +Windows installer, found [here](https://github.com/EngineHub/CommandHelper/releases/tag/win-installer-v.1.0.1). + +There are two modes of installation, both first require obtaining the MethodScript jar. +You can build it yourself, or download the official builds from Github. + +For other platforms or manual installation on Windows, grab the jar from +[here](https://github.com/EngineHub/CommandHelper/packages/). + +Minecraft: Installation in Minecraft is simple. Simply drop the jar in the plugins +folder. + +Standalone Programming: MethodScript is a fledgling general purpose programming language, +and can be used from the command line, much like python, node, or other programming +languages. + +For all platforms, place the jar file in whatever location you like, (noting that +it will create a folder at the same level which contains the configuration files, so +it's probably easiest to put this in your user directory), +then run `java -jar MethodScript.jar install-cmdline` as root/Administrator. +This will install the `mscript` program and add it to your path, +which can be used to start a REPL shell for quick tasks, execute a script file, or +easily run the commandline tools. On Windows, this also installs a PowerShell module, +which can be used with `Import-Module -Name MethodScript` and `Invoke-MethodScript`. On Windows, +you must reboot your system after installation to use the `mscript` command in cmd.exe. +You can install MethodScript using the same jar that is used in the Minecraft server, +though two different environments are used, with separate folders for the CommandHelper +installation, and the MethodScript installation. You can symlink these folders together if you +wish your configuration to be the same for both environments. + +Commandline Tools: Various command line tools are available for use, and are useful +both for those that use the jar as a plugin, or as a general purpose language. Run +`java -jar MethodScript.jar help` for a list of these tools, or if you have installed +the commandline version, you can use `mscript -- help`. diff --git a/SPECIAL_LICENSE.txt b/SPECIAL_LICENSE.txt index ec557456b0..b570cad2d0 100644 --- a/SPECIAL_LICENSE.txt +++ b/SPECIAL_LICENSE.txt @@ -10,9 +10,12 @@ under MIT/GPL. In layman's terms, this means that you cannot submit code that ad Objects, for instance, and keep the copyright on it, you must release that to the public domain, however, your code will still be available to use under the MIT or GPL license, since it is immediately released under those licenses, in addition to the copyright being relinquished. +While the project as a whole is only released under MIT or GPL, code that is part of the core +may be individually released under the CC0 license. So if you copy paste individual core files +out, this may be released under your choice of CC0, MIT, or GPL. If commercialization of the software without crediting you in the future makes you uncomfortable, please do not submit code to the core. All other code unrelated to the core language that is included or linked to are -still licensed and released under the MIT license. \ No newline at end of file +still licensed and released under the MIT license. diff --git a/WindowsLauncher/App.config b/WindowsLauncher/App.config new file mode 100644 index 0000000000..731f6de6c2 --- /dev/null +++ b/WindowsLauncher/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/WindowsLauncher/Program.cs b/WindowsLauncher/Program.cs new file mode 100644 index 0000000000..81f31119b1 --- /dev/null +++ b/WindowsLauncher/Program.cs @@ -0,0 +1,193 @@ +using Microsoft.Win32; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Linq; + +namespace mscript { + class Program { + static void Main(string[] args) { + //try { + // if (System.Deployment.Application.ApplicationDeployment.CurrentDeployment.IsFirstRun) { + // Console.WriteLine("Finishing installation..."); + // SetAddRemoveProgramsIcon(); + // Console.WriteLine("Installation complete. Press enter to exit."); + // Console.ReadLine(); + // return; + // } + //} catch(System.Deployment.Application.InvalidDeploymentException e) { + // // Being run directly from the executable, so this is not the first installed run anyways. + //} + + string[] activationData = null; + if (AppDomain.CurrentDomain.SetupInformation.ActivationArguments != null) { + activationData = AppDomain.CurrentDomain.SetupInformation.ActivationArguments.ActivationData; + } + string startFile = null; + if (activationData != null) { + // We were launched directly if this is null, otherwise + // activationData[0] has the name of the file we were launched from + startFile = activationData[0]; + } + + string jarLocation = Registry.CurrentUser.OpenSubKey("Software\\MethodScript")?.GetValue("JarLocation")?.ToString(); + if(jarLocation == null) { + Console.WriteLine("Cannot locate jar file, Registry Key HKCU\\Software\\MethodScript\\JarLocation is not set."); + Environment.Exit(1); + } + + // To ease transition in java versions, make the java path configurable by registry key. By default, just "java.exe". + string javaPath = "java.exe"; + string javaPathRegistry = Registry.CurrentUser.OpenSubKey("Software\\MethodScript")?.GetValue("JavaPath")?.ToString(); + if(javaPathRegistry != null) { + javaPath = javaPathRegistry; + } + + List modulesArgs = new List(); + // Pull out the modules and add them here if java > 8 + { + ProcessStartInfo start = new ProcessStartInfo(); + start.FileName = javaPath; + start.RedirectStandardOutput = true; + start.UseShellExecute = false; + List a = new List(); + a.Add("-jar"); + a.Add(jarLocation); + a.Add("java-version"); + start.Arguments = JoinAndEscape(a); + string strOutput; + using(Process p = Process.Start(start)) { + strOutput = p.StandardOutput.ReadToEnd(); + p.WaitForExit(); + } + int javaVersion = int.Parse(strOutput); + if(javaVersion > 8) { + ZipArchive zipFile = ZipFile.OpenRead(jarLocation); + ZipArchiveEntry file = zipFile.Entries.Where((ZipArchiveEntry e) => { + return e.FullName.Equals("interpreter-helpers/modules"); + }).Single(); + Stream stream = file.Open(); + StreamReader reader = new StreamReader(stream); + string modules = reader.ReadToEnd(); + modules = modules.Replace("\r", ""); + foreach(string module in modules.Split('\n')) { + if(module.Equals(string.Empty)) { + continue; + } + modulesArgs.Add("--add-opens"); + modulesArgs.Add(module + "=ALL-UNNAMED"); + } + } + if(javaVersion < 16) + { + Console.WriteLine("MethodScript requires Java version 16 or greater to be installed."); + Environment.Exit(1); + } + } + + List command = new List(); + if(System.Environment.GetEnvironmentVariable("DEBUG_MSCRIPT") != null && System.Environment.GetEnvironmentVariable("DEBUG_MSCRIPT") == "1") { + // java debug mode + command.Add("-Xrs"); + command.Add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:9001"); + } + command.AddRange(modulesArgs); + if(args.Length == 0) { + // start interpreter + command.Add("-jar"); + command.Add(jarLocation); + command.Add("interpreter"); + // Don't think these are needed here + //command.Add("--location-----"); + //command.Add(AppDomain.CurrentDomain.BaseDirectory); + } else { + // launched from a *.ms file + if(args.Length > 0 && args[0] == "--") { + // Script passthrough to java -jar CH.jar + command.Add("-jar"); + command.Add(jarLocation); + // Remove the -- from the args + for(int i = 1; i < args.Length; i++) { + command.Add(args[i]); + } + } else { + // Normal launch with script + command.Add("-jar"); + command.Add(jarLocation); + command.Add("cmdline"); + command.Add(startFile); + command.AddRange(args); + } + } + + { + ProcessStartInfo start = new ProcessStartInfo(); + start.UseShellExecute = false; + start.Arguments = JoinAndEscape(command); +#if DEBUG + Console.WriteLine("Command is: " + start.Arguments); +#endif + start.FileName = javaPath; + start.UseShellExecute = false; + + //Console.WriteLine(start.Arguments); + + int exitCode; + + using(Process proc = Process.Start(start)) { + proc.WaitForExit(); + exitCode = proc.ExitCode; + } + } + + //Console.WriteLine("command: " + string.Join(" ", command)); + //Console.ReadLine(); + return; + } + + private static string JoinAndEscape(List args) { + string s = ""; + foreach(string arg in args) { + if(arg == null) { + continue; + } + string arg2 = arg.Replace("\\", "\\\\"); + arg2 = arg2.Replace("\"", "\\\""); + arg2 = "\"" + arg2 + "\""; + s += arg2; + s += " "; + } + return s; + } + + //private static void SetAddRemoveProgramsIcon() { + // try { + // string assemblyDescription = "MethodScript"; + + // //the icon is included in this program + // string iconSourcePath = Path.Combine(System.Windows.Forms.Application.StartupPath, "commandhelper_icon.ico"); + // //Console.WriteLine("iconSourcePath: " + iconSourcePath); + // //Console.WriteLine("assemblyDescription: " + assemblyDescription); + + // if (!File.Exists(iconSourcePath)) + // return; + + // RegistryKey myUninstallKey = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Uninstall"); + // string[] mySubKeyNames = myUninstallKey.GetSubKeyNames(); + // for (int i = 0; i < mySubKeyNames.Length; i++) { + // RegistryKey myKey = myUninstallKey.OpenSubKey(mySubKeyNames[i], true); + // object myValue = myKey.GetValue("DisplayName"); + // if (myValue != null && myValue.ToString() == assemblyDescription) { + // //Console.WriteLine("Setting iconSourcePath in " + myKey.Name); + // myKey.SetValue("DisplayIcon", iconSourcePath); + // break; + // } + // } + // } catch(Exception) { + // //log an error + // } + //} + } +} diff --git a/WindowsLauncher/Properties/AssemblyInfo.cs b/WindowsLauncher/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..2f6857e62d --- /dev/null +++ b/WindowsLauncher/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("MethodScript")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("MethodScript")] +[assembly: AssemblyCopyright("Copyright ©2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("8bc476c5-e566-4bfd-a86c-2ee0c813676f")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/WindowsLauncher/commandhelper_icon.ico b/WindowsLauncher/commandhelper_icon.ico new file mode 100644 index 0000000000..90f993e6f6 Binary files /dev/null and b/WindowsLauncher/commandhelper_icon.ico differ diff --git a/WindowsLauncher/mscript.csproj b/WindowsLauncher/mscript.csproj new file mode 100644 index 0000000000..b900feccbc --- /dev/null +++ b/WindowsLauncher/mscript.csproj @@ -0,0 +1,139 @@ + + + + + Debug + AnyCPU + {8BC476C5-E566-4BFD-A86C-2EE0C813676F} + Exe + mscript + mscript + v4.6.1 + 512 + true + false + ..\src\main\resources\interpreter-helpers\csharp\ + true + Disk + false + Foreground + 7 + Days + false + false + true + https://methodscript.com + https://methodscript.com + en + MethodScript + MethodScript Team + 1 + 0.0.0.1 + false + true + true + + + AnyCPU + true + full + false + ..\target\csharp\x86\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + AnyCPU + portable + true + ..\target\csharp\x86\Release\ + TRACE + prompt + 4 + false + true + + + 8316999540B038BC3D016377F0522994E7CE72C9 + + + + + + + true + + + false + + + commandhelper_icon.ico + + + true + ..\target\csharp\x64\Debug\ + DEBUG;TRACE + full + x64 + prompt + MinimumRecommendedRules.ruleset + + + ..\target\csharp\x64\Release\ + TRACE + true + portable + x64 + prompt + MinimumRecommendedRules.ruleset + true + + + + + + + + + + + + + + + + + + + + + + + + + + False + Microsoft .NET Framework 4.6.1 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 + false + + + + + + + + False + MethodScript main file + mscript + commandhelper_icon.ico + + + + \ No newline at end of file diff --git a/WindowsLauncher/mscript.sln b/WindowsLauncher/mscript.sln new file mode 100644 index 0000000000..cc94c5c090 --- /dev/null +++ b/WindowsLauncher/mscript.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29020.237 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "mscript", "mscript.csproj", "{8BC476C5-E566-4BFD-A86C-2EE0C813676F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8BC476C5-E566-4BFD-A86C-2EE0C813676F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8BC476C5-E566-4BFD-A86C-2EE0C813676F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8BC476C5-E566-4BFD-A86C-2EE0C813676F}.Debug|x64.ActiveCfg = Debug|x64 + {8BC476C5-E566-4BFD-A86C-2EE0C813676F}.Debug|x64.Build.0 = Debug|x64 + {8BC476C5-E566-4BFD-A86C-2EE0C813676F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8BC476C5-E566-4BFD-A86C-2EE0C813676F}.Release|Any CPU.Build.0 = Release|Any CPU + {8BC476C5-E566-4BFD-A86C-2EE0C813676F}.Release|x64.ActiveCfg = Release|x64 + {8BC476C5-E566-4BFD-A86C-2EE0C813676F}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {974CFA5F-62A8-4130-B626-07AD39800993} + EndGlobalSection +EndGlobal diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 0000000000..27c93d932a --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,37 @@ +# Maven +# Build your Java project and run tests with Apache Maven. +# Add steps that analyze code, save build artifacts, deploy, and more: +# https://docs.microsoft.com/azure/devops/pipelines/languages/java + +pool: + vmImage: 'windows-2025' + +steps: +- task: JavaToolInstaller@0 + inputs: + versionSpec: '21' + jdkArchitectureOption: x64 + jdkSourceOption: 'AzureStorage' + azureResourceManagerEndpoint: Azure Resource Manager OIDC + azureStorageAccountName: methodscriptjdkbuilds + azureContainerName: jdks + azureCommonVirtualFile: 'OpenJDK21U-jdk_x64_windows_hotspot_21.0.3_9.zip' + jdkDestinationDirectory: '$(agent.toolsDirectory)/jdk21' + cleanDestinationDirectory: true +- task: Maven@4 + inputs: + mavenPomFile: 'pom.xml' + javaHomeOption: 'Path' + jdkDirectory: '$(agent.toolsDirectory)\jdk21\JAVA_HOME_21_X64_OpenJDK21U-jdk_x64_windows_hotspot_21.0.3_9_zip\jdk-21.0.3+9' + jdkArchitectureOption: 'x64' + publishJUnitResults: true + testResultsFiles: '**/TEST-*.xml' + goals: 'package' +- task: PowerShell@2 + inputs: + targetType: 'inline' + script: '[xml]$pomXml = Get-Content "pom.xml"; $version = $pomXml.project.version; Write-Host "##vso[task.setvariable variable=version]$version"' +- task: PublishPipelineArtifact@0 + inputs: + targetPath: 'target/commandhelper-$(version)-full.jar' + artifactName: 'CommandHelper-$(version)-$(Build.BuildNumber).jar' diff --git a/checkstyle.xml b/checkstyle.xml new file mode 100644 index 0000000000..d6ed128d52 --- /dev/null +++ b/checkstyle.xml @@ -0,0 +1,239 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/checkstyle_suppressions.xml b/checkstyle_suppressions.xml new file mode 100644 index 0000000000..e0738b60d5 --- /dev/null +++ b/checkstyle_suppressions.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mustacheTemplates/ApiClient.mustache b/mustacheTemplates/ApiClient.mustache new file mode 100644 index 0000000000..a30bdb1dfa --- /dev/null +++ b/mustacheTemplates/ApiClient.mustache @@ -0,0 +1,772 @@ +{{>licenseInfo}} +package {{invokerPackage}}; + +{{#threetenbp}} +import org.threeten.bp.*; + +{{/threetenbp}} +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.*; +{{#joda}} +import com.fasterxml.jackson.datatype.joda.JodaModule; +{{/joda}} +{{#java8}} +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +{{/java8}} +{{#threetenbp}} +import com.fasterxml.jackson.datatype.threetenbp.ThreeTenModule; +{{/threetenbp}} +import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider; + +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.GenericType; +import com.sun.jersey.api.client.config.DefaultClientConfig; +import com.sun.jersey.api.client.filter.GZIPContentEncodingFilter; +import com.sun.jersey.api.client.filter.LoggingFilter; +import com.sun.jersey.api.client.WebResource.Builder; + +import com.sun.jersey.multipart.FormDataMultiPart; +import com.sun.jersey.multipart.file.FileDataBodyPart; + +import javax.ws.rs.core.Response.Status.Family; +import javax.ws.rs.core.MediaType; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import java.util.HashMap; +import java.util.List; +import java.util.ArrayList; +import java.util.Date; +import java.util.TimeZone; + +import java.net.URLEncoder; + +import java.io.File; +import java.io.UnsupportedEncodingException; + +import java.text.DateFormat; + +import {{invokerPackage}}.auth.Authentication; +import {{invokerPackage}}.auth.HttpBasicAuth; +import {{invokerPackage}}.auth.ApiKeyAuth; +import {{invokerPackage}}.auth.OAuth; + +{{>generatedAnnotation}} +public class ApiClient { + private Map defaultHeaderMap = new HashMap(); + private String basePath = "{{{basePath}}}"; + private boolean debugging = false; + private int connectionTimeout = 0; + + private Client httpClient; + private ObjectMapper objectMapper; + + private Map authentications; + + private int statusCode; + private Map> responseHeaders; + + private DateFormat dateFormat; + + public ApiClient() { + objectMapper = new ObjectMapper(); + objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + objectMapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false); + objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + objectMapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING); + objectMapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING); + {{#joda}} + objectMapper.registerModule(new JodaModule()); + {{/joda}} + {{#java8}} + objectMapper.registerModule(new JavaTimeModule()); + {{/java8}} + {{#threetenbp}} + ThreeTenModule module = new ThreeTenModule(); + module.addDeserializer(Instant.class, CustomInstantDeserializer.INSTANT); + module.addDeserializer(OffsetDateTime.class, CustomInstantDeserializer.OFFSET_DATE_TIME); + module.addDeserializer(ZonedDateTime.class, CustomInstantDeserializer.ZONED_DATE_TIME); + objectMapper.registerModule(module); + {{/threetenbp}} + objectMapper.setDateFormat(ApiClient.buildDefaultDateFormat()); + + dateFormat = ApiClient.buildDefaultDateFormat(); + + // Set default User-Agent. + setUserAgent("{{#httpUserAgent}}{{{.}}}{{/httpUserAgent}}{{^httpUserAgent}}Swagger-Codegen/{{{artifactVersion}}}/java{{/httpUserAgent}}"); + + // Setup authentications (key: authentication name, value: authentication). + authentications = new HashMap();{{#authMethods}}{{#is this 'basic'}} + authentications.put("{{name}}", new HttpBasicAuth());{{/is}}{{#is this 'api-key'}} + authentications.put("{{name}}", new ApiKeyAuth({{#is this 'key-in-header'}}"header"{{/is}}{{#isNot this 'key-in-header'}}"query"{{/isNot}}, "{{keyParamName}}"));{{/is}}{{#is this 'oauth'}} + authentications.put("{{name}}", new OAuth());{{/is}}{{#is this 'bearer'}} + authentications.put("{{name}}", new OAuth());{{/is}}{{/authMethods}} + // Prevent the authentications from being modified. + authentications = Collections.unmodifiableMap(authentications); + + rebuildHttpClient(); + } + + public static DateFormat buildDefaultDateFormat() { + return new RFC3339DateFormat(); + } + + /** + * Build the Client used to make HTTP requests with the latest settings, + * i.e. objectMapper and debugging. + * TODO: better to use the Builder Pattern? + * @return API client + */ + public ApiClient rebuildHttpClient() { + // Add the JSON serialization support to Jersey + JacksonJsonProvider jsonProvider = new JacksonJsonProvider(objectMapper); + DefaultClientConfig conf = new DefaultClientConfig(); + conf.getSingletons().add(jsonProvider); + Client client = Client.create(conf); + client.addFilter(new GZIPContentEncodingFilter({{#useGzipFeature}}true{{/useGzipFeature}}{{^useGzipFeature}}false{{/useGzipFeature}})); + if (debugging) { + client.addFilter(new LoggingFilter()); + } + this.httpClient = client; + return this; + } + + /** + * Returns the current object mapper used for JSON serialization/deserialization. + *

+ * Note: If you make changes to the object mapper, remember to set it back via + * setObjectMapper in order to trigger HTTP client rebuilding. + *

+ * @return Object mapper + */ + public ObjectMapper getObjectMapper() { + return objectMapper; + } + + public ApiClient setObjectMapper(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + // Need to rebuild the Client as it depends on object mapper. + rebuildHttpClient(); + return this; + } + + public Client getHttpClient() { + return httpClient; + } + + public ApiClient setHttpClient(Client httpClient) { + this.httpClient = httpClient; + return this; + } + + public String getBasePath() { + return basePath; + } + + public ApiClient setBasePath(String basePath) { + this.basePath = basePath; + return this; + } + + /** + * Gets the status code of the previous request + * @return Status code + */ + public int getStatusCode() { + return statusCode; + } + + /** + * Gets the response headers of the previous request + * @return Response headers + */ + public Map> getResponseHeaders() { + return responseHeaders; + } + + /** + * Get authentications (key: authentication name, value: authentication). + * @return Map of authentication + */ + public Map getAuthentications() { + return authentications; + } + + /** + * Get authentication for the given name. + * + * @param authName The authentication name + * @return The authentication, null if not found + */ + public Authentication getAuthentication(String authName) { + return authentications.get(authName); + } + + /** + * Helper method to set username for the first HTTP basic authentication. + * @param username Username + */ + public void setUsername(String username) { + for (Authentication auth : authentications.values()) { + if (auth instanceof HttpBasicAuth) { + ((HttpBasicAuth) auth).setUsername(username); + return; + } + } + throw new RuntimeException("No HTTP basic authentication configured!"); + } + + /** + * Helper method to set password for the first HTTP basic authentication. + * @param password Password + */ + public void setPassword(String password) { + for (Authentication auth : authentications.values()) { + if (auth instanceof HttpBasicAuth) { + ((HttpBasicAuth) auth).setPassword(password); + return; + } + } + throw new RuntimeException("No HTTP basic authentication configured!"); + } + + /** + * Helper method to set API key value for the first API key authentication. + * @param apiKey API key + */ + public void setApiKey(String apiKey) { + for (Authentication auth : authentications.values()) { + if (auth instanceof ApiKeyAuth) { + ((ApiKeyAuth) auth).setApiKey(apiKey); + return; + } + } + throw new RuntimeException("No API key authentication configured!"); + } + + /** + * Helper method to set API key prefix for the first API key authentication. + * @param apiKeyPrefix API key prefix + */ + public void setApiKeyPrefix(String apiKeyPrefix) { + for (Authentication auth : authentications.values()) { + if (auth instanceof ApiKeyAuth) { + ((ApiKeyAuth) auth).setApiKeyPrefix(apiKeyPrefix); + return; + } + } + throw new RuntimeException("No API key authentication configured!"); + } + + /** + * Helper method to set access token for the first OAuth2 authentication. + * @param accessToken Access token + */ + public void setAccessToken(String accessToken) { + for (Authentication auth : authentications.values()) { + if (auth instanceof OAuth) { + ((OAuth) auth).setAccessToken(accessToken); + return; + } + } + throw new RuntimeException("No OAuth2 authentication configured!"); + } + + /** + * Set the User-Agent header's value (by adding to the default header map). + * @param userAgent User agent + * @return API client + */ + public ApiClient setUserAgent(String userAgent) { + addDefaultHeader("User-Agent", userAgent); + return this; + } + + /** + * Add a default header. + * + * @param key The header's key + * @param value The header's value + * @return API client + */ + public ApiClient addDefaultHeader(String key, String value) { + defaultHeaderMap.put(key, value); + return this; + } + + /** + * Check that whether debugging is enabled for this API client. + * @return True if debugging is on + */ + public boolean isDebugging() { + return debugging; + } + + /** + * Enable/disable debugging for this API client. + * + * @param debugging To enable (true) or disable (false) debugging + * @return API client + */ + public ApiClient setDebugging(boolean debugging) { + this.debugging = debugging; + // Need to rebuild the Client as it depends on the value of debugging. + rebuildHttpClient(); + return this; + } + + /** + * Connect timeout (in milliseconds). + * @return Connection timeout + */ + public int getConnectTimeout() { + return connectionTimeout; + } + + /** + * Set the connect timeout (in milliseconds). + * A value of 0 means no timeout, otherwise values must be between 1 and + * {@link Integer#MAX_VALUE}. + * @param connectionTimeout Connection timeout in milliseconds + * @return API client + */ + public ApiClient setConnectTimeout(int connectionTimeout) { + this.connectionTimeout = connectionTimeout; + httpClient.setConnectTimeout(connectionTimeout); + return this; + } + + /** + * Get the date format used to parse/format date parameters. + * @return Date format + */ + public DateFormat getDateFormat() { + return dateFormat; + } + + /** + * Set the date format used to parse/format date parameters. + * @param dateFormat Date format + * @return API client + */ + public ApiClient setDateFormat(DateFormat dateFormat) { + this.dateFormat = dateFormat; + // Also set the date format for model (de)serialization with Date properties. + this.objectMapper.setDateFormat((DateFormat) dateFormat.clone()); + // Need to rebuild the Client as objectMapper changes. + rebuildHttpClient(); + return this; + } + + /** + * Parse the given string into Date object. + * @param str String + * @return Date + */ + public Date parseDate(String str) { + try { + return dateFormat.parse(str); + } catch (java.text.ParseException e) { + throw new RuntimeException(e); + } + } + + /** + * Format the given Date object into string. + * @param date Date + * @return Date in string format + */ + public String formatDate(Date date) { + return dateFormat.format(date); + } + + /** + * Format the given parameter object into string. + * @param param Object + * @return Object in string format + */ + public String parameterToString(Object param) { + if (param == null) { + return ""; + } else if (param instanceof Date) { + return formatDate((Date) param); + } else if (param instanceof Collection) { + StringBuilder b = new StringBuilder(); + for(Object o : (Collection)param) { + if(b.length() > 0) { + b.append(','); + } + b.append(String.valueOf(o)); + } + return b.toString(); + } else { + return String.valueOf(param); + } + } + + /** + * Formats the specified query parameter to a list containing a single {@code Pair} object. + * + * Note that {@code value} must not be a collection. + * + * @param name The name of the parameter. + * @param value The value of the parameter. + * @return A list containing a single {@code Pair} object. + */ + public List parameterToPair(String name, Object value) { + List params = new ArrayList(); + + // preconditions + if (name == null || name.isEmpty() || value == null || value instanceof Collection) return params; + + params.add(new Pair(name, parameterToString(value))); + return params; + } + + /** + * Formats the specified collection query parameters to a list of {@code Pair} objects. + * + * Note that the values of each of the returned Pair objects are percent-encoded. + * + * @param collectionFormat The collection format of the parameter. + * @param name The name of the parameter. + * @param value The value of the parameter. + * @return A list of {@code Pair} objects. + */ + public List parameterToPairs(String collectionFormat, String name, Collection value) { + List params = new ArrayList(); + + // preconditions + if (name == null || name.isEmpty() || value == null) { + return params; + } + + // create the params based on the collection format + if ("multi".equals(collectionFormat)) { + for (Object item : value) { + params.add(new Pair(name, escapeString(parameterToString(item)))); + } + return params; + } + + // collectionFormat is assumed to be "csv" by default + String delimiter = ","; + + // escape all delimiters except commas, which are URI reserved + // characters + if ("ssv".equals(collectionFormat)) { + delimiter = escapeString(" "); + } else if ("tsv".equals(collectionFormat)) { + delimiter = escapeString("\t"); + } else if ("pipes".equals(collectionFormat)) { + delimiter = escapeString("|"); + } + + StringBuilder sb = new StringBuilder() ; + for (Object item : value) { + sb.append(delimiter); + sb.append(escapeString(parameterToString(item))); + } + + params.add(new Pair(name, sb.substring(delimiter.length()))); + + return params; + } + + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * application/vnd.company+json + * @param mime MIME + * @return True if MIME type is boolean + */ + public boolean isJsonMime(String mime) { + String jsonMime = "(?i)^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$"; + return mime != null && (mime.matches(jsonMime) || mime.equals("*/*")); + } + + /** + * Select the Accept header's value from the given accepts array: + * if JSON exists in the given array, use it; + * otherwise use all of them (joining into a string) + * + * @param accepts The accepts array to select from + * @return The Accept header to use. If the given array is empty, + * null will be returned (not to set the Accept header explicitly). + */ + public String selectHeaderAccept(String[] accepts) { + if (accepts.length == 0) { + return null; + } + for (String accept : accepts) { + if (isJsonMime(accept)) { + return accept; + } + } + return StringUtil.join(accepts, ","); + } + + /** + * Select the Content-Type header's value from the given array: + * if JSON exists in the given array, use it; + * otherwise use the first one of the array. + * + * @param contentTypes The Content-Type array to select from + * @return The Content-Type header to use. If the given array is empty, + * or matches "any", JSON will be used. + */ + public String selectHeaderContentType(String[] contentTypes) { + if (contentTypes.length == 0 || contentTypes[0].equals("*/*")) { + return "application/json"; + } + for (String contentType : contentTypes) { + if (isJsonMime(contentType)) { + return contentType; + } + } + return contentTypes[0]; + } + + /** + * Escape the given string to be used as URL query value. + * @param str String + * @return Escaped string + */ + public String escapeString(String str) { + try { + return URLEncoder.encode(str, "utf8").replaceAll("\\+", "%20"); + } catch (UnsupportedEncodingException e) { + return str; + } + } + + /** + * Serialize the given Java object into string according the given + * Content-Type (only JSON is supported for now). + * @param obj Object + * @param contentType Content type + * @param formParams Form parameters + * @return Object + * @throws ApiException API exception + */ + public Object serialize(Object obj, String contentType, Map formParams) throws ApiException { + if (contentType.startsWith("multipart/form-data")) { + FormDataMultiPart mp = new FormDataMultiPart(); + for (Entry param: formParams.entrySet()) { + if( param.getValue() instanceof List && !( ( List ) param.getValue() ).isEmpty() + && ( ( List ) param.getValue() ).get( 0 ) instanceof File ) { + @SuppressWarnings( "unchecked" ) + List files = ( List ) param.getValue(); + for( File file : files ) { + mp.bodyPart( new FileDataBodyPart( param.getKey(), file, MediaType.APPLICATION_OCTET_STREAM_TYPE ) ); + } + } else if (param.getValue() instanceof File) { + File file = (File) param.getValue(); + mp.bodyPart(new FileDataBodyPart(param.getKey(), file, MediaType.APPLICATION_OCTET_STREAM_TYPE)); + } else { + mp.field(param.getKey(), parameterToString(param.getValue()), MediaType.MULTIPART_FORM_DATA_TYPE); + } + } + return mp; + } else if (contentType.startsWith("application/x-www-form-urlencoded")) { + return this.getXWWWFormUrlencodedParams(formParams); + } else { + // We let Jersey attempt to serialize the body + return obj; + } + } + + /** + * Build full URL by concatenating base path, the given sub path and query parameters. + * + * @param path The sub path + * @param queryParams The query parameters + * @param collectionQueryParams The collection query parameters + * @return The full URL + */ + private String buildUrl(String path, List queryParams, List collectionQueryParams) { + final StringBuilder url = new StringBuilder(); + url.append(basePath).append(path); + + if (queryParams != null && !queryParams.isEmpty()) { + // support (constant) query string in `path`, e.g. "/posts?draft=1" + String prefix = path.contains("?") ? "&" : "?"; + for (Pair param : queryParams) { + if (param.getValue() != null) { + if (prefix != null) { + url.append(prefix); + prefix = null; + } else { + url.append("&"); + } + String value = parameterToString(param.getValue()); + url.append(escapeString(param.getName())).append("=").append(escapeString(value)); + } + } + } + + if (collectionQueryParams != null && !collectionQueryParams.isEmpty()) { + String prefix = url.toString().contains("?") ? "&" : "?"; + for (Pair param : collectionQueryParams) { + if (param.getValue() != null) { + if (prefix != null) { + url.append(prefix); + prefix = null; + } else { + url.append("&"); + } + String value = parameterToString(param.getValue()); + // collection query parameter value already escaped as part of parameterToPairs + url.append(escapeString(param.getName())).append("=").append(value); + } + } + } + + return url.toString(); + } + + private ClientResponse getAPIResponse(String path, String method, List queryParams, List collectionQueryParams, Object body, Map headerParams, Map formParams, String accept, String contentType, String[] authNames) throws ApiException { + if (body != null && !formParams.isEmpty()) { + throw new ApiException(500, "Cannot have body and form params"); + } + + updateParamsForAuth(authNames, queryParams, headerParams); + + final String url = buildUrl(path, queryParams, collectionQueryParams); + Builder builder; + if (accept == null) { + builder = httpClient.resource(url).getRequestBuilder(); + } else { + builder = httpClient.resource(url).accept(accept); + } + + for (String key : headerParams.keySet()) { + builder = builder.header(key, headerParams.get(key)); + } + for (String key : defaultHeaderMap.keySet()) { + if (!headerParams.containsKey(key)) { + builder = builder.header(key, defaultHeaderMap.get(key)); + } + } + + ClientResponse response = null; + + if ("GET".equals(method)) { + response = (ClientResponse) builder.get(ClientResponse.class); + } else if ("POST".equals(method)) { + response = builder.type(contentType).post(ClientResponse.class, serialize(body, contentType, formParams)); + } else if ("PUT".equals(method)) { + response = builder.type(contentType).put(ClientResponse.class, serialize(body, contentType, formParams)); + } else if ("DELETE".equals(method)) { + response = builder.type(contentType).delete(ClientResponse.class, serialize(body, contentType, formParams)); + } else if ("PATCH".equals(method)) { + response = builder.type(contentType).header("X-HTTP-Method-Override", "PATCH").post(ClientResponse.class, serialize(body, contentType, formParams)); + } else if ("HEAD".equals(method)) { + response = builder.head(); + } else { + throw new ApiException(500, "unknown method type " + method); + } + return response; + } + + /** + * Invoke API by sending HTTP request with the given options. + * + * @param Type + * @param path The sub-path of the HTTP URL + * @param method The request method, one of "GET", "POST", "PUT", and "DELETE" + * @param queryParams The query parameters + * @param collectionQueryParams The collection query parameters + * @param body The request body object - if it is not binary, otherwise null + * @param headerParams The header parameters + * @param formParams The form parameters + * @param accept The request's Accept header + * @param contentType The request's Content-Type header + * @param authNames The authentications to apply + * @param returnType Return type + * @return The response body in type of string + * @throws ApiException API exception + */ + public T invokeAPI(String path, String method, List queryParams, List collectionQueryParams, Object body, Map headerParams, Map formParams, String accept, String contentType, String[] authNames, GenericType returnType) throws ApiException { + + ClientResponse response = getAPIResponse(path, method, queryParams, collectionQueryParams, body, headerParams, formParams, accept, contentType, authNames); + + statusCode = response.getStatusInfo().getStatusCode(); + responseHeaders = response.getHeaders(); + + if(response.getStatusInfo().getStatusCode() == ClientResponse.Status.NO_CONTENT.getStatusCode()) { + return null; + } else if (response.getStatusInfo().getFamily() == Family.SUCCESSFUL) { + if (returnType == null) + return null; + else + return response.getEntity(returnType); + } else { + String message = "error"; + String respBody = null; + if (response.hasEntity()) { + try { + respBody = response.getEntity(String.class); + message = respBody; + } catch (RuntimeException e) { + // e.printStackTrace(); + } + } + throw new ApiException( + response.getStatusInfo().getStatusCode(), + message, + response.getHeaders(), + respBody); + } + } + + /** + * Update query and header parameters based on authentication settings. + * + * @param authNames The authentications to apply + * @param queryParams Query parameters + * @param headerParams Header parameters + */ + private void updateParamsForAuth(String[] authNames, List queryParams, Map headerParams) { + for (String authName : authNames) { + Authentication auth = authentications.get(authName); + if (auth == null) throw new RuntimeException("Authentication undefined: " + authName); + auth.applyToParams(queryParams, headerParams); + } + } + + /** + * Encode the given form parameters as request body. + * @param formParams Form parameters + * @return HTTP form encoded parameters + */ + private String getXWWWFormUrlencodedParams(Map formParams) { + StringBuilder formParamBuilder = new StringBuilder(); + + for (Entry param : formParams.entrySet()) { + String valueStr = parameterToString(param.getValue()); + try { + formParamBuilder.append(URLEncoder.encode(param.getKey(), "utf8")) + .append("=") + .append(URLEncoder.encode(valueStr, "utf8")); + formParamBuilder.append("&"); + } catch (UnsupportedEncodingException e) { + // move on to next + } + } + + String encodedFormParams = formParamBuilder.toString(); + if (encodedFormParams.endsWith("&")) { + encodedFormParams = encodedFormParams.substring(0, encodedFormParams.length() - 1); + } + + return encodedFormParams; + } +} diff --git a/mustacheTemplates/BeanValidationException.mustache b/mustacheTemplates/BeanValidationException.mustache new file mode 100644 index 0000000000..ab8ef30b69 --- /dev/null +++ b/mustacheTemplates/BeanValidationException.mustache @@ -0,0 +1,27 @@ +package {{invokerPackage}}; + +import java.util.Set; + +import javax.validation.ConstraintViolation; +import javax.validation.ValidationException; + +public class BeanValidationException extends ValidationException { + /** + * + */ + private static final long serialVersionUID = -5294733947409491364L; + Set> violations; + + public BeanValidationException(Set> violations) { + this.violations = violations; + } + + public Set> getViolations() { + return violations; + } + + public void setViolations(Set> violations) { + this.violations = violations; + } + +} diff --git a/mustacheTemplates/Configuration.mustache b/mustacheTemplates/Configuration.mustache new file mode 100644 index 0000000000..cb425df356 --- /dev/null +++ b/mustacheTemplates/Configuration.mustache @@ -0,0 +1,28 @@ +{{>licenseInfo}} + +package {{invokerPackage}}; + +{{>generatedAnnotation}} +public class Configuration { + private static ApiClient defaultApiClient = new ApiClient(); + + /** + * Get the default API client, which would be used when creating API + * instances without providing an API client. + * + * @return Default API client + */ + public static ApiClient getDefaultApiClient() { + return defaultApiClient; + } + + /** + * Set the default API client, which would be used when creating API + * instances without providing an API client. + * + * @param apiClient API client + */ + public static void setDefaultApiClient(ApiClient apiClient) { + defaultApiClient = apiClient; + } +} diff --git a/mustacheTemplates/CustomInstantDeserializer.mustache b/mustacheTemplates/CustomInstantDeserializer.mustache new file mode 100644 index 0000000000..dbf4d30e11 --- /dev/null +++ b/mustacheTemplates/CustomInstantDeserializer.mustache @@ -0,0 +1,232 @@ +package {{invokerPackage}}; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.datatype.threetenbp.DateTimeUtils; +import com.fasterxml.jackson.datatype.threetenbp.DecimalUtils; +import com.fasterxml.jackson.datatype.threetenbp.deser.ThreeTenDateTimeDeserializerBase; +import com.fasterxml.jackson.datatype.threetenbp.function.BiFunction; +import com.fasterxml.jackson.datatype.threetenbp.function.Function; +import org.threeten.bp.DateTimeException; +import org.threeten.bp.Instant; +import org.threeten.bp.OffsetDateTime; +import org.threeten.bp.ZoneId; +import org.threeten.bp.ZonedDateTime; +import org.threeten.bp.format.DateTimeFormatter; +import org.threeten.bp.temporal.Temporal; +import org.threeten.bp.temporal.TemporalAccessor; + +import java.io.IOException; +import java.math.BigDecimal; + +/** + * Deserializer for ThreeTen temporal {@link Instant}s, {@link OffsetDateTime}, and {@link ZonedDateTime}s. + * Adapted from the jackson threetenbp InstantDeserializer to add support for deserializing rfc822 format. + * + * @author Nick Williams + */ +public class CustomInstantDeserializer + extends ThreeTenDateTimeDeserializerBase { + private static final long serialVersionUID = 1L; + + public static final CustomInstantDeserializer INSTANT = new CustomInstantDeserializer( + Instant.class, DateTimeFormatter.ISO_INSTANT, + new Function() { + @Override + public Instant apply(TemporalAccessor temporalAccessor) { + return Instant.from(temporalAccessor); + } + }, + new Function() { + @Override + public Instant apply(FromIntegerArguments a) { + return Instant.ofEpochMilli(a.value); + } + }, + new Function() { + @Override + public Instant apply(FromDecimalArguments a) { + return Instant.ofEpochSecond(a.integer, a.fraction); + } + }, + null + ); + + public static final CustomInstantDeserializer OFFSET_DATE_TIME = new CustomInstantDeserializer( + OffsetDateTime.class, DateTimeFormatter.ISO_OFFSET_DATE_TIME, + new Function() { + @Override + public OffsetDateTime apply(TemporalAccessor temporalAccessor) { + return OffsetDateTime.from(temporalAccessor); + } + }, + new Function() { + @Override + public OffsetDateTime apply(FromIntegerArguments a) { + return OffsetDateTime.ofInstant(Instant.ofEpochMilli(a.value), a.zoneId); + } + }, + new Function() { + @Override + public OffsetDateTime apply(FromDecimalArguments a) { + return OffsetDateTime.ofInstant(Instant.ofEpochSecond(a.integer, a.fraction), a.zoneId); + } + }, + new BiFunction() { + @Override + public OffsetDateTime apply(OffsetDateTime d, ZoneId z) { + return d.withOffsetSameInstant(z.getRules().getOffset(d.toLocalDateTime())); + } + } + ); + + public static final CustomInstantDeserializer ZONED_DATE_TIME = new CustomInstantDeserializer( + ZonedDateTime.class, DateTimeFormatter.ISO_ZONED_DATE_TIME, + new Function() { + @Override + public ZonedDateTime apply(TemporalAccessor temporalAccessor) { + return ZonedDateTime.from(temporalAccessor); + } + }, + new Function() { + @Override + public ZonedDateTime apply(FromIntegerArguments a) { + return ZonedDateTime.ofInstant(Instant.ofEpochMilli(a.value), a.zoneId); + } + }, + new Function() { + @Override + public ZonedDateTime apply(FromDecimalArguments a) { + return ZonedDateTime.ofInstant(Instant.ofEpochSecond(a.integer, a.fraction), a.zoneId); + } + }, + new BiFunction() { + @Override + public ZonedDateTime apply(ZonedDateTime zonedDateTime, ZoneId zoneId) { + return zonedDateTime.withZoneSameInstant(zoneId); + } + } + ); + + protected final Function fromMilliseconds; + + protected final Function fromNanoseconds; + + protected final Function parsedToValue; + + protected final BiFunction adjust; + + protected CustomInstantDeserializer(Class supportedType, + DateTimeFormatter parser, + Function parsedToValue, + Function fromMilliseconds, + Function fromNanoseconds, + BiFunction adjust) { + super(supportedType, parser); + this.parsedToValue = parsedToValue; + this.fromMilliseconds = fromMilliseconds; + this.fromNanoseconds = fromNanoseconds; + this.adjust = adjust == null ? new BiFunction() { + @Override + public T apply(T t, ZoneId zoneId) { + return t; + } + } : adjust; + } + + @SuppressWarnings("unchecked") + protected CustomInstantDeserializer(CustomInstantDeserializer base, DateTimeFormatter f) { + super((Class) base.handledType(), f); + parsedToValue = base.parsedToValue; + fromMilliseconds = base.fromMilliseconds; + fromNanoseconds = base.fromNanoseconds; + adjust = base.adjust; + } + + @Override + protected JsonDeserializer withDateFormat(DateTimeFormatter dtf) { + if (dtf == _formatter) { + return this; + } + return new CustomInstantDeserializer(this, dtf); + } + + @Override + public T deserialize(JsonParser parser, DeserializationContext context) throws IOException { + //NOTE: Timestamps contain no timezone info, and are always in configured TZ. Only + //string values have to be adjusted to the configured TZ. + switch (parser.getCurrentTokenId()) { + case JsonTokenId.ID_NUMBER_FLOAT: { + BigDecimal value = parser.getDecimalValue(); + long seconds = value.longValue(); + int nanoseconds = DecimalUtils.extractNanosecondDecimal(value, seconds); + return fromNanoseconds.apply(new FromDecimalArguments( + seconds, nanoseconds, getZone(context))); + } + + case JsonTokenId.ID_NUMBER_INT: { + long timestamp = parser.getLongValue(); + if (context.isEnabled(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)) { + return this.fromNanoseconds.apply(new FromDecimalArguments( + timestamp, 0, this.getZone(context) + )); + } + return this.fromMilliseconds.apply(new FromIntegerArguments( + timestamp, this.getZone(context) + )); + } + + case JsonTokenId.ID_STRING: { + String string = parser.getText().trim(); + if (string.length() == 0) { + return null; + } + if (string.endsWith("+0000")) { + string = string.substring(0, string.length() - 5) + "Z"; + } + T value; + try { + TemporalAccessor acc = _formatter.parse(string); + value = parsedToValue.apply(acc); + if (context.isEnabled(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)) { + return adjust.apply(value, this.getZone(context)); + } + } catch (DateTimeException e) { + throw _peelDTE(e); + } + return value; + } + } + throw context.mappingException("Expected type float, integer, or string."); + } + + private ZoneId getZone(DeserializationContext context) { + // Instants are always in UTC, so don't waste compute cycles + return (_valueClass == Instant.class) ? null : DateTimeUtils.timeZoneToZoneId(context.getTimeZone()); + } + + private static class FromIntegerArguments { + public final long value; + public final ZoneId zoneId; + + private FromIntegerArguments(long value, ZoneId zoneId) { + this.value = value; + this.zoneId = zoneId; + } + } + + private static class FromDecimalArguments { + public final long integer; + public final int fraction; + public final ZoneId zoneId; + + private FromDecimalArguments(long integer, int fraction, ZoneId zoneId) { + this.integer = integer; + this.fraction = fraction; + this.zoneId = zoneId; + } + } +} diff --git a/mustacheTemplates/Pair.mustache b/mustacheTemplates/Pair.mustache new file mode 100644 index 0000000000..c08f145a48 --- /dev/null +++ b/mustacheTemplates/Pair.mustache @@ -0,0 +1,41 @@ +{{>licenseInfo}} + +package {{invokerPackage}}; + +{{>generatedAnnotation}} +public class Pair { + private String name = ""; + private String value = ""; + + public Pair (String name, String value) { + setName(name); + setValue(value); + } + + private void setName(String name) { + if (!isValidString(name)) return; + + this.name = name; + } + + private void setValue(String value) { + if (!isValidString(value)) return; + + this.value = value; + } + + public String getName() { + return this.name; + } + + public String getValue() { + return this.value; + } + + private boolean isValidString(String arg) { + if (arg == null) return false; + if (arg.trim().isEmpty()) return false; + + return true; + } +} diff --git a/mustacheTemplates/README.mustache b/mustacheTemplates/README.mustache new file mode 100644 index 0000000000..cc5c3afefd --- /dev/null +++ b/mustacheTemplates/README.mustache @@ -0,0 +1,167 @@ +# {{artifactId}} + +{{appName}} +- API version: {{appVersion}} +{{^hideGenerationTimestamp}} + - Build date: {{generatedDate}} +{{/hideGenerationTimestamp}} + +{{#appDescription}}{{{appDescription}}}{{/appDescription}} + +{{#infoUrl}} + For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}}) +{{/infoUrl}} + +*Automatically generated by the [Swagger Codegen](https://github.com/swagger-api/swagger-codegen)* + + +## Requirements + +Building the API client library requires: +1. Java 1.7+ +2. Maven/Gradle + +## Installation + +To install the API client library to your local Maven repository, simply execute: + +```shell +mvn clean install +``` + +To deploy it to a remote Maven repository instead, configure the settings of the repository and execute: + +```shell +mvn clean deploy +``` + +Refer to the [OSSRH Guide](http://central.sonatype.org/pages/ossrh-guide.html) for more information. + +### Maven users + +Add this dependency to your project's POM: + +```xml + + {{{groupId}}} + {{{artifactId}}} + {{{artifactVersion}}} + compile + +``` + +### Gradle users + +Add this dependency to your project's build file: + +```groovy +compile "{{{groupId}}}:{{{artifactId}}}:{{{artifactVersion}}}" +``` + +### Others + +At first generate the JAR by executing: + +```shell +mvn clean package +``` + +Then manually install the following JARs: + +* `target/{{{artifactId}}}-{{{artifactVersion}}}.jar` +* `target/lib/*.jar` + +## Getting Started + +Please follow the [installation](#installation) instruction and execute the following Java code: + +```java +{{#apiInfo}}{{#apis}}{{#@first}}{{#operations}}{{#operation}}{{#contents}}{{#@first}} +import {{{invokerPackage}}}.*; +import {{{invokerPackage}}}.auth.*; +{{#hasModel}}import {{modelPackage}}.*;{{/hasModel}} +import {{{package}}}.{{{classname}}}; + +import java.io.File; +import java.util.*; + +public class {{{classname}}}Example { + + public static void main(String[] args) { + {{#hasAuthMethods}}ApiClient defaultClient = Configuration.getDefaultApiClient(); + {{#authMethods}}{{#isBasic}} + // Configure HTTP basic authorization: {{{name}}} + HttpBasicAuth {{{name}}} = (HttpBasicAuth) defaultClient.getAuthentication("{{{name}}}"); + {{{name}}}.setUsername("YOUR USERNAME"); + {{{name}}}.setPassword("YOUR PASSWORD");{{/isBasic}}{{#isApiKey}} + // Configure API key authorization: {{{name}}} + ApiKeyAuth {{{name}}} = (ApiKeyAuth) defaultClient.getAuthentication("{{{name}}}"); + {{{name}}}.setApiKey("YOUR API KEY"); + // Uncomment the following line to set a prefix for the API key, e.g. "Token" (defaults to null) + //{{{name}}}.setApiKeyPrefix("Token");{{/isApiKey}}{{#isOAuth}} + // Configure OAuth2 access token for authorization: {{{name}}} + OAuth {{{name}}} = (OAuth) defaultClient.getAuthentication("{{{name}}}"); + {{{name}}}.setAccessToken("YOUR ACCESS TOKEN");{{/isOAuth}} + {{/authMethods}} + {{/hasAuthMethods}} + + {{{classname}}} apiInstance = new {{{classname}}}(); + {{#allParams}} + {{{dataType}}} {{{paramName}}} = {{{example}}}; // {{{dataType}}} | {{{description}}} + {{/allParams}} + try { + {{#returnType}}{{{returnType}}} result = {{/returnType}}apiInstance.{{{operationId}}}({{#allParams}}{{{paramName}}}{{#hasMore}}, {{/hasMore}}{{/allParams}});{{#returnType}} + System.out.println(result);{{/returnType}} + } catch (ApiException e) { + System.err.println("Exception when calling {{{classname}}}#{{{operationId}}}"); + e.printStackTrace(); + } + } +} +{{/@first}}{{/contents}}{{/operation}}{{/operations}}{{/@first}}{{/apis}}{{/apiInfo}} +``` + +## Documentation for API Endpoints + +All URIs are relative to *{{basePath}}* + +Class | Method | HTTP request | Description +------------ | ------------- | ------------- | ------------- +{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{classname}}* | [**{{operationId}}**]({{apiDocPath}}{{classname}}.md#{{operationId}}) | **{{httpMethod}}** {{path}} | {{#summary}}{{summary}}{{/summary}} +{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} + +## Documentation for Models + +{{#models}}{{#model}} - [{{classname}}]({{modelDocPath}}{{classname}}.md) +{{/model}}{{/models}} + +## Documentation for Authorization + +{{^authMethods}}All endpoints do not require authorization. +{{/authMethods}}Authentication schemes defined for the API: +{{#authMethods}}### {{name}} + +{{#isApiKey}}- **Type**: API key +- **API key parameter name**: {{keyParamName}} +- **Location**: {{#isKeyInQuery}}URL query string{{/isKeyInQuery}}{{#isKeyInHeader}}HTTP header{{/isKeyInHeader}} +{{/isApiKey}} +{{#isBasic}}- **Type**: HTTP basic authentication +{{/isBasic}} +{{#isOAuth}}- **Type**: OAuth +- **Flow**: {{flow}} +- **Authorization URL**: {{authorizationUrl}} +- **Scopes**: {{^scopes}}N/A{{/scopes}} +{{#scopes}} - {{scope}}: {{description}} +{{/scopes}} +{{/isOAuth}} + +{{/authMethods}} + +## Recommendation + +It's recommended to create an instance of `ApiClient` per thread in a multithreaded environment to avoid any potential issues. + +## Author + +{{#apiInfo}}{{#apis}}{{^hasMore}}{{infoEmail}} +{{/hasMore}}{{/apis}}{{/apiInfo}} diff --git a/mustacheTemplates/RFC3339DateFormat.mustache b/mustacheTemplates/RFC3339DateFormat.mustache new file mode 100644 index 0000000000..16fd2a495a --- /dev/null +++ b/mustacheTemplates/RFC3339DateFormat.mustache @@ -0,0 +1,21 @@ +{{>licenseInfo}} +package {{invokerPackage}}; + +import com.fasterxml.jackson.databind.util.ISO8601DateFormat; +import com.fasterxml.jackson.databind.util.ISO8601Utils; + +import java.text.FieldPosition; +import java.util.Date; + + +public class RFC3339DateFormat extends ISO8601DateFormat { + + // Same as ISO8601DateFormat but serializing milliseconds. + @Override + public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) { + String value = ISO8601Utils.format(date, true); + toAppendTo.append(value); + return toAppendTo; + } + +} \ No newline at end of file diff --git a/mustacheTemplates/StringUtil.mustache b/mustacheTemplates/StringUtil.mustache new file mode 100644 index 0000000000..7b72a7bab4 --- /dev/null +++ b/mustacheTemplates/StringUtil.mustache @@ -0,0 +1,44 @@ +{{>licenseInfo}} + +package {{invokerPackage}}; + +{{>generatedAnnotation}} +public class StringUtil { + /** + * Check if the given array contains the given value (with case-insensitive comparison). + * + * @param array The array + * @param value The value to search + * @return true if the array contains the value + */ + public static boolean containsIgnoreCase(String[] array, String value) { + for (String str : array) { + if (value == null && str == null) return true; + if (value != null && value.equalsIgnoreCase(str)) return true; + } + return false; + } + + /** + * Join an array of strings with the given separator. + *

+ * Note: This might be replaced by utility method from commons-lang or guava someday + * if one of those libraries is added as dependency. + *

+ * + * @param array The array of strings + * @param separator The separator + * @return the resulting string + */ + public static String join(String[] array, String separator) { + int len = array.length; + if (len == 0) return ""; + + StringBuilder out = new StringBuilder(); + out.append(array[0]); + for (int i = 1; i < len; i++) { + out.append(separator).append(array[i]); + } + return out.toString(); + } +} diff --git a/mustacheTemplates/api.mustache b/mustacheTemplates/api.mustache new file mode 100644 index 0000000000..d208b6856f --- /dev/null +++ b/mustacheTemplates/api.mustache @@ -0,0 +1,117 @@ +{{>licenseInfo}} +package {{package}}; + +import com.sun.jersey.api.client.GenericType; + +import {{invokerPackage}}.ApiException; +import {{invokerPackage}}.ApiClient; +import {{invokerPackage}}.Configuration; +{{#hasModel}}import {{modelPackage}}.*;{{/hasModel}} + +import {{invokerPackage}}.Pair; + +{{#imports}}import {{import}}; +{{/imports}} +{{^fullJavaUtil}} +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +{{/fullJavaUtil}} +{{>generatedAnnotation}} +{{#operations}} +public class {{classname}} { + private ApiClient {{localVariablePrefix}}apiClient; + + public {{classname}}() { + this(Configuration.getDefaultApiClient()); + } + + public {{classname}}(ApiClient apiClient) { + this.{{localVariablePrefix}}apiClient = apiClient; + } + + public ApiClient getApiClient() { + return {{localVariablePrefix}}apiClient; + } + + public void setApiClient(ApiClient apiClient) { + this.{{localVariablePrefix}}apiClient = apiClient; + } + + {{#operation}} + {{#contents}} + /** + * {{summary}} + * {{notes}} + {{#parameters}} + * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}} + {{/parameters}} + {{#returnType}} + * @return {{returnType}} + {{/returnType}} + * @throws ApiException if fails to make API call + {{#isDeprecated}} + * @deprecated + {{/isDeprecated}} + {{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation + {{/externalDocs}} + */ + {{#isDeprecated}} + @Deprecated + {{/isDeprecated}} + public {{#returnType}}{{{returnType}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{operationId}}({{#parameters}}{{{dataType}}} {{paramName}}{{#has this 'more'}}, {{/has}}{{/parameters}}) throws ApiException { + Object {{localVariablePrefix}}localVarPostBody = {{#bodyParam}}{{paramName}}{{/bodyParam}}{{^bodyParam}}null{{/bodyParam}}; + {{#parameters}}{{#required}} + // verify the required parameter '{{paramName}}' is set + if ({{paramName}} == null) { + throw new ApiException(400, "Missing the required parameter '{{paramName}}' when calling {{operationId}}"); + } + {{/required}}{{/parameters}} + // create path and map variables + String {{localVariablePrefix}}localVarPath = "{{{path}}}"{{#pathParams}} + .replaceAll("\\{" + "{{baseName}}" + "\\}", {{localVariablePrefix}}apiClient.escapeString({{{paramName}}}.toString())){{/pathParams}}; + + // query params + {{javaUtilPrefix}}List {{localVariablePrefix}}localVarQueryParams = new {{javaUtilPrefix}}ArrayList(); + {{javaUtilPrefix}}List {{localVariablePrefix}}localVarCollectionQueryParams = new {{javaUtilPrefix}}ArrayList(); + {{javaUtilPrefix}}Map {{localVariablePrefix}}localVarHeaderParams = new {{javaUtilPrefix}}HashMap(); + {{javaUtilPrefix}}Map {{localVariablePrefix}}localVarFormParams = new {{javaUtilPrefix}}HashMap(); + + {{#queryParams}} + {{localVariablePrefix}}{{#collectionFormat}}localVarCollectionQueryParams.addAll({{localVariablePrefix}}apiClient.parameterToPairs("{{{collectionFormat}}}", {{/collectionFormat}}{{^collectionFormat}}localVarQueryParams.addAll({{localVariablePrefix}}apiClient.parameterToPair({{/collectionFormat}}"{{baseName}}", {{paramName}})); + {{/queryParams}} + + {{#headerParams}}if ({{paramName}} != null) + {{localVariablePrefix}}localVarHeaderParams.put("{{baseName}}", {{localVariablePrefix}}apiClient.parameterToString({{paramName}})); + {{/headerParams}} + + {{#formParams}}if ({{paramName}} != null) + {{localVariablePrefix}}localVarFormParams.put("{{baseName}}", {{paramName}}); + {{/formParams}} + + final String[] {{localVariablePrefix}}localVarAccepts = { + {{#produces}}"{{{mediaType}}}"{{#hasMore}}, {{/hasMore}}{{/produces}} + }; + final String {{localVariablePrefix}}localVarAccept = {{localVariablePrefix}}apiClient.selectHeaderAccept({{localVariablePrefix}}localVarAccepts); + + final String[] {{localVariablePrefix}}localVarContentTypes = { + {{#consumes}}"{{{mediaType}}}"{{#hasMore}}, {{/hasMore}}{{/consumes}} + }; + final String {{localVariablePrefix}}localVarContentType = {{localVariablePrefix}}apiClient.selectHeaderContentType({{localVariablePrefix}}localVarContentTypes); + + String[] {{localVariablePrefix}}localVarAuthNames = new String[] { {{#authMethods}}"{{name}}"{{#has this 'more'}}, {{/has}}{{/authMethods}} }; + + {{#returnType}} + GenericType<{{{returnType}}}> {{localVariablePrefix}}localVarReturnType = new GenericType<{{{returnType}}}>() {}; + return {{localVariablePrefix}}apiClient.invokeAPI({{localVariablePrefix}}localVarPath, "{{httpMethod}}", {{localVariablePrefix}}localVarQueryParams, {{localVariablePrefix}}localVarCollectionQueryParams, {{localVariablePrefix}}localVarPostBody, {{localVariablePrefix}}localVarHeaderParams, {{localVariablePrefix}}localVarFormParams, {{localVariablePrefix}}localVarAccept, {{localVariablePrefix}}localVarContentType, {{localVariablePrefix}}localVarAuthNames, {{localVariablePrefix}}localVarReturnType); + {{/returnType}}{{^returnType}} + {{localVariablePrefix}}apiClient.invokeAPI({{localVariablePrefix}}localVarPath, "{{httpMethod}}", {{localVariablePrefix}}localVarQueryParams, {{localVariablePrefix}}localVarCollectionQueryParams, {{localVariablePrefix}}localVarPostBody, {{localVariablePrefix}}localVarHeaderParams, {{localVariablePrefix}}localVarFormParams, {{localVariablePrefix}}localVarAccept, {{localVariablePrefix}}localVarContentType, {{localVariablePrefix}}localVarAuthNames, null); + {{/returnType}} + } + {{/contents}} + {{/operation}} +} +{{/operations}} diff --git a/mustacheTemplates/apiException.mustache b/mustacheTemplates/apiException.mustache new file mode 100644 index 0000000000..5b450c9ba6 --- /dev/null +++ b/mustacheTemplates/apiException.mustache @@ -0,0 +1,80 @@ +{{>licenseInfo}} + +package {{invokerPackage}}; + +import java.util.Map; +import java.util.List; + +{{>generatedAnnotation}} +public class ApiException extends{{#useRuntimeException}} RuntimeException {{/useRuntimeException}}{{^useRuntimeException}} Exception {{/useRuntimeException}}{ + private int code = 0; + private Map> responseHeaders = null; + private String responseBody = null; + + public ApiException() {} + + public ApiException(Throwable throwable) { + super(throwable); + } + + public ApiException(String message) { + super(message); + } + + public ApiException(String message, Throwable throwable, int code, Map> responseHeaders, String responseBody) { + super(message, throwable); + this.code = code; + this.responseHeaders = responseHeaders; + this.responseBody = responseBody; + } + + public ApiException(String message, int code, Map> responseHeaders, String responseBody) { + this(message, (Throwable) null, code, responseHeaders, responseBody); + } + + public ApiException(String message, Throwable throwable, int code, Map> responseHeaders) { + this(message, throwable, code, responseHeaders, null); + } + + public ApiException(int code, Map> responseHeaders, String responseBody) { + this((String) null, (Throwable) null, code, responseHeaders, responseBody); + } + + public ApiException(int code, String message) { + super(message); + this.code = code; + } + + public ApiException(int code, String message, Map> responseHeaders, String responseBody) { + this(code, message); + this.responseHeaders = responseHeaders; + this.responseBody = responseBody; + } + + /** + * Get the HTTP status code. + * + * @return HTTP status code + */ + public int getCode() { + return code; + } + + /** + * Get the HTTP response headers. + * + * @return A map of list of string + */ + public Map> getResponseHeaders() { + return responseHeaders; + } + + /** + * Get the HTTP response body. + * + * @return Response body in the form of string + */ + public String getResponseBody() { + return responseBody; + } +} diff --git a/mustacheTemplates/api_doc.mustache b/mustacheTemplates/api_doc.mustache new file mode 100644 index 0000000000..bb10129cbe --- /dev/null +++ b/mustacheTemplates/api_doc.mustache @@ -0,0 +1,84 @@ +# {{classname}}{{#description}} +{{description}}{{/description}} + +All URIs are relative to *{{basePath}}* + +Method | HTTP request | Description +------------- | ------------- | ------------- +{{#operations}}{{#operation}}[**{{operationId}}**]({{classname}}.md#{{operationId}}) | **{{httpMethod}}** {{path}} | {{#summary}}{{summary}}{{/summary}} +{{/operation}}{{/operations}} + +{{#operations}} +{{#operation}} +{{#contents}} + +# **{{operationId}}** +> {{#returnType}}{{returnType}} {{/returnType}}{{operationId}}({{#parameters}}{{{paramName}}}{{#has this 'more'}}, {{/has}}{{/parameters}}) + +{{summary}}{{#notes}} + +{{notes}}{{/notes}} + +### Example +```java +// Import classes:{{#hasAuthMethods}} +//import {{{invokerPackage}}}.ApiClient;{{/hasAuthMethods}} +//import {{{invokerPackage}}}.ApiException;{{#hasAuthMethods}} +//import {{{invokerPackage}}}.Configuration; +//import {{{invokerPackage}}}.auth.*;{{/hasAuthMethods}} +//import {{{package}}}.{{{classname}}}; + +{{#hasAuthMethods}} +ApiClient defaultClient = Configuration.getDefaultApiClient(); +{{#authMethods}}{{#is this 'basic'}} +// Configure HTTP basic authorization: {{{name}}} +HttpBasicAuth {{{name}}} = (HttpBasicAuth) defaultClient.getAuthentication("{{{name}}}"); +{{{name}}}.setUsername("YOUR USERNAME"); +{{{name}}}.setPassword("YOUR PASSWORD");{{/is}}{{#is this 'api-key'}} +// Configure API key authorization: {{{name}}} +ApiKeyAuth {{{name}}} = (ApiKeyAuth) defaultClient.getAuthentication("{{{name}}}"); +{{{name}}}.setApiKey("YOUR API KEY"); +// Uncomment the following line to set a prefix for the API key, e.g. "Token" (defaults to null) +//{{{name}}}.setApiKeyPrefix("Token");{{/is}}{{#is this 'oauth'}} +// Configure OAuth2 access token for authorization: {{{name}}} +OAuth {{{name}}} = (OAuth) defaultClient.getAuthentication("{{{name}}}"); +{{{name}}}.setAccessToken("YOUR ACCESS TOKEN");{{/is}} +{{/authMethods}} +{{/hasAuthMethods}} + +{{{classname}}} apiInstance = new {{{classname}}}(); +{{#parameters}} +{{{dataType}}} {{{paramName}}} = {{{example}}}; // {{{dataType}}} | {{{description}}} +{{/parameters}} +try { + {{#returnType}}{{{returnType}}} result = {{/returnType}}apiInstance.{{{operationId}}}({{#parameters}}{{{paramName}}}{{#has this 'more'}}, {{/has}}{{/parameters}});{{#returnType}} + System.out.println(result);{{/returnType}} +} catch (ApiException e) { + System.err.println("Exception when calling {{{classname}}}#{{{operationId}}}"); + e.printStackTrace(); +} +``` + +### Parameters +{{^parameters}}This endpoint does not need any parameter.{{/parameters}}{{#parameters}}{{#@last}} +Name | Type | Description | Notes +------------- | ------------- | ------------- | -------------{{/@last}}{{/parameters}} +{{#parameters}} **{{paramName}}** | {{#is this 'primitive-type'}}**{{dataType}}**{{/is}}{{#isNot this 'primitive-type'}}{{#isBinary}}**{{dataType}}**{{/isBinary}}{{^isBinary}}[**{{dataType}}**]({{baseType}}.md){{/isBinary}}{{/isNot}}| {{description}} |{{^required}} [optional]{{/required}}{{#defaultValue}} [default to {{defaultValue}}]{{/defaultValue}}{{#allowableValues}} [enum: {{#values}}{{{.}}}{{^@last}}, {{/@last}}{{/values}}]{{/allowableValues}} +{{/parameters}} + +### Return type + +{{#returnType}}{{#returnTypeIsPrimitive}}**{{returnType}}**{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}[**{{returnType}}**]({{returnBaseType}}.md){{/returnTypeIsPrimitive}}{{/returnType}}{{^returnType}}null (empty response body){{/returnType}} + +### Authorization + +{{^authMethods}}No authorization required{{/authMethods}}{{#authMethods}}[{{name}}](../README.md#{{name}}){{/authMethods}} + +### HTTP request headers + + - **Content-Type**: {{#consumes}}{{{mediaType}}}{{#hasMore}}, {{/hasMore}}{{/consumes}}{{^consumes}}Not defined{{/consumes}} + - **Accept**: {{#produces}}{{{mediaType}}}{{#hasMore}}, {{/hasMore}}{{/produces}}{{^produces}}Not defined{{/produces}} + +{{/contents}} +{{/operation}} +{{/operations}} diff --git a/mustacheTemplates/api_test.mustache b/mustacheTemplates/api_test.mustache new file mode 100644 index 0000000000..c9a25efeef --- /dev/null +++ b/mustacheTemplates/api_test.mustache @@ -0,0 +1,51 @@ +{{>licenseInfo}} + +package {{package}}; + +import {{invokerPackage}}.ApiException; +{{#imports}}import {{import}}; +{{/imports}} +import org.junit.Test; +import org.junit.Ignore; + +{{^fullJavaUtil}} +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +{{/fullJavaUtil}} + +/** + * API tests for {{classname}} + */ +@Ignore +public class {{classname}}Test { + + private final {{classname}} api = new {{classname}}(); + + {{#operations}} + {{#operation}} + {{#contents}} + {{#@first}} + /** + * {{summary}} + * + * {{notes}} + * + * @throws ApiException + * if the Api call fails + */ + @Test + public void {{operationId}}Test() throws ApiException { + {{#parameters}} + {{{dataType}}} {{paramName}} = null; + {{/parameters}} + {{#returnType}}{{{returnType}}} response = {{/returnType}}api.{{operationId}}({{#parameters}}{{paramName}}{{#has this 'more'}}, {{/has}}{{/parameters}}); + + // TODO: test validations + } + {{/@first}} + {{/contents}} + {{/operation}} + {{/operations}} +} diff --git a/mustacheTemplates/auth/ApiKeyAuth.mustache b/mustacheTemplates/auth/ApiKeyAuth.mustache new file mode 100644 index 0000000000..5af060f117 --- /dev/null +++ b/mustacheTemplates/auth/ApiKeyAuth.mustache @@ -0,0 +1,64 @@ +{{>licenseInfo}} + +package {{invokerPackage}}.auth; + +import {{invokerPackage}}.Pair; + +import java.util.Map; +import java.util.List; + +{{>generatedAnnotation}} +public class ApiKeyAuth implements Authentication { + private final String location; + private final String paramName; + + private String apiKey; + private String apiKeyPrefix; + + public ApiKeyAuth(String location, String paramName) { + this.location = location; + this.paramName = paramName; + } + + public String getLocation() { + return location; + } + + public String getParamName() { + return paramName; + } + + public String getApiKey() { + return apiKey; + } + + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } + + public String getApiKeyPrefix() { + return apiKeyPrefix; + } + + public void setApiKeyPrefix(String apiKeyPrefix) { + this.apiKeyPrefix = apiKeyPrefix; + } + + @Override + public void applyToParams(List queryParams, Map headerParams) { + if (apiKey == null) { + return; + } + String value; + if (apiKeyPrefix != null) { + value = apiKeyPrefix + " " + apiKey; + } else { + value = apiKey; + } + if ("query".equals(location)) { + queryParams.add(new Pair(paramName, value)); + } else if ("header".equals(location)) { + headerParams.put(paramName, value); + } + } +} diff --git a/mustacheTemplates/auth/Authentication.mustache b/mustacheTemplates/auth/Authentication.mustache new file mode 100644 index 0000000000..26174fa3b5 --- /dev/null +++ b/mustacheTemplates/auth/Authentication.mustache @@ -0,0 +1,18 @@ +{{>licenseInfo}} + +package {{invokerPackage}}.auth; + +import {{invokerPackage}}.Pair; + +import java.util.Map; +import java.util.List; + +public interface Authentication { + /** + * Apply authentication settings to header and query params. + * + * @param queryParams List of query parameters + * @param headerParams Map of header parameters + */ + void applyToParams(List queryParams, Map headerParams); +} diff --git a/mustacheTemplates/auth/HttpBasicAuth.mustache b/mustacheTemplates/auth/HttpBasicAuth.mustache new file mode 100644 index 0000000000..015e042e17 --- /dev/null +++ b/mustacheTemplates/auth/HttpBasicAuth.mustache @@ -0,0 +1,60 @@ +{{>licenseInfo}} + +package {{invokerPackage}}.auth; + +import {{invokerPackage}}.Pair; + +{{^java8}} +import com.migcomponents.migbase64.Base64; +{{/java8}} +{{#java8}} +import java.util.Base64; +import java.nio.charset.StandardCharsets; +{{/java8}} + +import java.util.Map; +import java.util.List; + +{{^java8}} +import java.io.UnsupportedEncodingException; +{{/java8}} + +{{>generatedAnnotation}} +public class HttpBasicAuth implements Authentication { + private String username; + private String password; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + @Override + public void applyToParams(List queryParams, Map headerParams) { + if (username == null && password == null) { + return; + } + String str = (username == null ? "" : username) + ":" + (password == null ? "" : password); +{{^java8}} + try { + headerParams.put("Authorization", "Basic " + Base64.encodeToString(str.getBytes("UTF-8"), false)); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } +{{/java8}} +{{#java8}} + headerParams.put("Authorization", "Basic " + Base64.getEncoder().encodeToString(str.getBytes(StandardCharsets.UTF_8))); +{{/java8}} + } +} diff --git a/mustacheTemplates/auth/OAuth.mustache b/mustacheTemplates/auth/OAuth.mustache new file mode 100644 index 0000000000..ffc9923d1f --- /dev/null +++ b/mustacheTemplates/auth/OAuth.mustache @@ -0,0 +1,28 @@ +{{>licenseInfo}} + +package {{invokerPackage}}.auth; + +import {{invokerPackage}}.Pair; + +import java.util.Map; +import java.util.List; + +{{>generatedAnnotation}} +public class OAuth implements Authentication { + private String accessToken; + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + @Override + public void applyToParams(List queryParams, Map headerParams) { + if (accessToken != null) { + headerParams.put("Authorization", "Bearer " + accessToken); + } + } +} diff --git a/mustacheTemplates/auth/OAuthFlow.mustache b/mustacheTemplates/auth/OAuthFlow.mustache new file mode 100644 index 0000000000..002e9572f3 --- /dev/null +++ b/mustacheTemplates/auth/OAuthFlow.mustache @@ -0,0 +1,7 @@ +{{>licenseInfo}} + +package {{invokerPackage}}.auth; + +public enum OAuthFlow { + accessCode, implicit, password, application +} diff --git a/mustacheTemplates/beanValidation.mustache b/mustacheTemplates/beanValidation.mustache new file mode 100644 index 0000000000..165af94c59 --- /dev/null +++ b/mustacheTemplates/beanValidation.mustache @@ -0,0 +1,16 @@ +{{#required}} + @NotNull +{{/required}} +{{#is this 'container'}} +{{#isNot this 'primitive-type'}} +{{#isNot this 'enum'}} + @Valid +{{/isNot}} +{{/isNot}} +{{/is}} +{{#isNot this 'container'}} +{{#isNot this 'primitive-type'}} + @Valid +{{/isNot}} +{{/isNot}} +{{>beanValidationCore}} \ No newline at end of file diff --git a/mustacheTemplates/beanValidationCore.mustache b/mustacheTemplates/beanValidationCore.mustache new file mode 100644 index 0000000000..7a78adfb68 --- /dev/null +++ b/mustacheTemplates/beanValidationCore.mustache @@ -0,0 +1,20 @@ +{{#pattern}} @Pattern(regexp="{{{pattern}}}"){{/pattern}}{{! +minLength && maxLength set +}}{{#minLength}}{{#maxLength}} @Size(min={{minLength}},max={{maxLength}}){{/maxLength}}{{/minLength}}{{! +minLength set, maxLength not +}}{{#minLength}}{{^maxLength}} @Size(min={{minLength}}){{/maxLength}}{{/minLength}}{{! +minLength not set, maxLength set +}}{{^minLength}}{{#maxLength}} @Size(max={{maxLength}}){{/maxLength}}{{/minLength}}{{! +@Size: minItems && maxItems set +}}{{#minItems}}{{#maxItems}} @Size(min={{minItems}},max={{maxItems}}){{/maxItems}}{{/minItems}}{{! +@Size: minItems set, maxItems not +}}{{#minItems}}{{^maxItems}} @Size(min={{minItems}}){{/maxItems}}{{/minItems}}{{! +@Size: minItems not set && maxItems set +}}{{^minItems}}{{#maxItems}} @Size(max={{maxItems}}){{/maxItems}}{{/minItems}}{{! +check for integer or long / all others=decimal type with @Decimal* +isInteger set +}}{{#is this 'integer'}}{{#minimum}} @Min({{minimum}}){{/minimum}}{{#maximum}} @Max({{maximum}}){{/maximum}}{{/is}}{{! +isLong set +}}{{#is this 'long'}}{{#minimum}} @Min({{minimum}}){{/minimum}}{{#maximum}} @Max({{maximum}}){{/maximum}}{{/is}}{{! +Not Integer, not Long => we have a decimal value! +}}{{#isNot this 'integer'}}{{#isNot this 'long'}}{{#minimum}} @DecimalMin("{{minimum}}"){{/minimum}}{{#maximum}} @DecimalMax("{{maximum}}"){{/maximum}}{{/isNot}}{{/isNot}} \ No newline at end of file diff --git a/mustacheTemplates/beanValidationQueryParams.mustache b/mustacheTemplates/beanValidationQueryParams.mustache new file mode 100644 index 0000000000..f8eef8f94c --- /dev/null +++ b/mustacheTemplates/beanValidationQueryParams.mustache @@ -0,0 +1 @@ +{{#required}} @NotNull{{/required}}{{>beanValidationCore}} \ No newline at end of file diff --git a/mustacheTemplates/build.gradle.mustache b/mustacheTemplates/build.gradle.mustache new file mode 100644 index 0000000000..34cb09be17 --- /dev/null +++ b/mustacheTemplates/build.gradle.mustache @@ -0,0 +1,147 @@ +apply plugin: 'idea' +apply plugin: 'eclipse' + +group = '{{groupId}}' +version = '{{artifactVersion}}' + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.3.+' + classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' + } +} + +repositories { + jcenter() +} + + +if(hasProperty('target') && target == 'android') { + + apply plugin: 'com.android.library' + apply plugin: 'com.github.dcendents.android-maven' + + android { + compileSdkVersion 25 + buildToolsVersion '25.0.2' + defaultConfig { + minSdkVersion 14 + targetSdkVersion 25 + } + compileOptions { + {{#java8}} + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + {{/java8}} + {{^java8}} + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 + {{/java8}} + } + + // Rename the aar correctly + libraryVariants.all { variant -> + variant.outputs.each { output -> + def outputFile = output.outputFile + if (outputFile != null && outputFile.name.endsWith('.aar')) { + def fileName = "${project.name}-${variant.baseName}-${version}.aar" + output.outputFile = new File(outputFile.parent, fileName) + } + } + } + + dependencies { + provided 'javax.annotation:jsr250-api:1.0' + } + } + + afterEvaluate { + android.libraryVariants.all { variant -> + def task = project.tasks.create "jar${variant.name.capitalize()}", Jar + task.description = "Create jar artifact for ${variant.name}" + task.dependsOn variant.javaCompile + task.from variant.javaCompile.destinationDir + task.destinationDir = project.file("${project.buildDir}/outputs/jar") + task.archiveName = "${project.name}-${variant.baseName}-${version}.jar" + artifacts.add('archives', task); + } + } + + task sourcesJar(type: Jar) { + from android.sourceSets.main.java.srcDirs + classifier = 'sources' + } + + artifacts { + archives sourcesJar + } + +} else { + + apply plugin: 'java' + apply plugin: 'maven' + + {{#java8}} + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + {{/java8}} + {{^java8}} + sourceCompatibility = JavaVersion.VERSION_1_7 + targetCompatibility = JavaVersion.VERSION_1_7 + {{/java8}} + + install { + repositories.mavenInstaller { + pom.artifactId = '{{artifactId}}' + } + } + + task execute(type:JavaExec) { + main = System.getProperty('mainClass') + classpath = sourceSets.main.runtimeClasspath + } +} + +ext { + {{#useOas2}} + swagger_annotations_version = "1.5.15" + {{/useOas2}} + {{^useOas2}} + swagger_annotations_version = "2.0.0" + {{/useOas2}} + jackson_version = "{{^threetenbp}}2.10.1{{/threetenbp}}{{#threetenbp}}2.6.4{{/threetenbp}}" + jersey_version = "1.19.4" + jodatime_version = "2.9.9" + junit_version = "4.12" +} + +dependencies { + {{#useOas2}} + compile "io.swagger:swagger-annotations:$swagger_annotations_version" + {{/useOas2}} + {{^useOas2}} + compile "io.swagger.core.v3:swagger-annotations:$swagger_annotations_version" + {{/useOas2}} + compile "com.sun.jersey:jersey-client:$jersey_version" + compile "com.sun.jersey.contribs:jersey-multipart:$jersey_version" + compile "com.fasterxml.jackson.core:jackson-core:$jackson_version" + compile "com.fasterxml.jackson.core:jackson-annotations:$jackson_version" + compile "com.fasterxml.jackson.core:jackson-databind:$jackson_version" + compile "com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:$jackson_version" + {{#joda}} + compile "com.fasterxml.jackson.datatype:jackson-datatype-joda:$jackson_version" + {{/joda}} + {{#java8}} + compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jackson_version" + {{/java8}} + {{#threetenbp}} + compile "com.github.joschi.jackson:jackson-datatype-threetenbp:$jackson_version" + {{/threetenbp}} + {{^java8}} + compile "com.brsanthu:migbase64:2.2" + {{/java8}} + testCompile "junit:junit:$junit_version" +} diff --git a/mustacheTemplates/enum_outer_doc.mustache b/mustacheTemplates/enum_outer_doc.mustache new file mode 100644 index 0000000000..20c512aaea --- /dev/null +++ b/mustacheTemplates/enum_outer_doc.mustache @@ -0,0 +1,7 @@ +# {{classname}} + +## Enum + +{{#allowableValues}}{{#enumVars}} +* `{{name}}` (value: `{{{value}}}`) +{{/enumVars}}{{/allowableValues}} diff --git a/mustacheTemplates/generatedAnnotation.mustache b/mustacheTemplates/generatedAnnotation.mustache new file mode 100644 index 0000000000..a47b6faa85 --- /dev/null +++ b/mustacheTemplates/generatedAnnotation.mustache @@ -0,0 +1 @@ +{{^hideGenerationTimestamp}}@javax.annotation.Generated(value = "{{generatorClass}}", date = "{{generatedDate}}"){{/hideGenerationTimestamp}} \ No newline at end of file diff --git a/mustacheTemplates/git_push.sh.mustache b/mustacheTemplates/git_push.sh.mustache new file mode 100644 index 0000000000..e153ce23ec --- /dev/null +++ b/mustacheTemplates/git_push.sh.mustache @@ -0,0 +1,52 @@ +#!/bin/sh +# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/ +# +# Usage example: /bin/sh ./git_push.sh wing328 swagger-petstore-perl "minor update" + +git_user_id=$1 +git_repo_id=$2 +release_note=$3 + +if [ "$git_user_id" = "" ]; then + git_user_id="{{{gitUserId}}}" + echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id" +fi + +if [ "$git_repo_id" = "" ]; then + git_repo_id="{{{gitRepoId}}}" + echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id" +fi + +if [ "$release_note" = "" ]; then + release_note="{{{releaseNote}}}" + echo "[INFO] No command line input provided. Set \$release_note to $release_note" +fi + +# Initialize the local directory as a Git repository +git init + +# Adds the files in the local repository and stages them for commit. +git add . + +# Commits the tracked changes and prepares them to be pushed to a remote repository. +git commit -m "$release_note" + +# Sets the new remote +git_remote=`git remote` +if [ "$git_remote" = "" ]; then # git remote not defined + + if [ "$GIT_TOKEN" = "" ]; then + echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git crediential in your environment." + git remote add origin https://github.com/${git_user_id}/${git_repo_id}.git + else + git remote add origin https://${git_user_id}:${GIT_TOKEN}@github.com/${git_user_id}/${git_repo_id}.git + fi + +fi + +git pull origin master + +# Pushes (Forces) the changes in the local repository up to the remote repository +echo "Git pushing to https://github.com/${git_user_id}/${git_repo_id}.git" +git push origin master 2>&1 | grep -v 'To https' + diff --git a/mustacheTemplates/gitignore.mustache b/mustacheTemplates/gitignore.mustache new file mode 100644 index 0000000000..a530464afa --- /dev/null +++ b/mustacheTemplates/gitignore.mustache @@ -0,0 +1,21 @@ +*.class + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear + +# exclude jar for gradle wrapper +!gradle/wrapper/*.jar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +# build files +**/target +target +.gradle +build diff --git a/mustacheTemplates/gradle-wrapper.jar b/mustacheTemplates/gradle-wrapper.jar new file mode 100644 index 0000000000..2c6137b878 Binary files /dev/null and b/mustacheTemplates/gradle-wrapper.jar differ diff --git a/mustacheTemplates/gradle-wrapper.properties.mustache b/mustacheTemplates/gradle-wrapper.properties.mustache new file mode 100644 index 0000000000..b7a3647395 --- /dev/null +++ b/mustacheTemplates/gradle-wrapper.properties.mustache @@ -0,0 +1,6 @@ +#Tue May 17 23:08:05 CST 2016 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.6-bin.zip diff --git a/mustacheTemplates/gradle.properties.mustache b/mustacheTemplates/gradle.properties.mustache new file mode 100644 index 0000000000..05644f0754 --- /dev/null +++ b/mustacheTemplates/gradle.properties.mustache @@ -0,0 +1,2 @@ +# Uncomment to build for Android +#target = android \ No newline at end of file diff --git a/mustacheTemplates/gradlew.bat.mustache b/mustacheTemplates/gradlew.bat.mustache new file mode 100644 index 0000000000..5f192121eb --- /dev/null +++ b/mustacheTemplates/gradlew.bat.mustache @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/mustacheTemplates/gradlew.mustache b/mustacheTemplates/gradlew.mustache new file mode 100644 index 0000000000..9d82f78915 --- /dev/null +++ b/mustacheTemplates/gradlew.mustache @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/mustacheTemplates/interface.mustache b/mustacheTemplates/interface.mustache new file mode 100644 index 0000000000..becc0c5549 --- /dev/null +++ b/mustacheTemplates/interface.mustache @@ -0,0 +1,6 @@ +/** +* {{#description}}{{.}}{{/description}}{{^description}}{{classname}}{{/description}} +*/ +public interface {{{classname}}} { + +} diff --git a/mustacheTemplates/libraries/feign/ApiClient.mustache b/mustacheTemplates/libraries/feign/ApiClient.mustache new file mode 100644 index 0000000000..973aa9b76b --- /dev/null +++ b/mustacheTemplates/libraries/feign/ApiClient.mustache @@ -0,0 +1,361 @@ +package {{invokerPackage}}; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.oltu.oauth2.client.request.OAuthClientRequest.AuthenticationRequestBuilder; +import org.apache.oltu.oauth2.client.request.OAuthClientRequest.TokenRequestBuilder; +{{#threetenbp}} +import org.threeten.bp.*; +{{/threetenbp}} + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +{{#joda}} +import com.fasterxml.jackson.datatype.joda.JodaModule; +{{/joda}} +{{#java8}} +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +{{/java8}} +{{#threetenbp}} +import com.fasterxml.jackson.datatype.threetenbp.ThreeTenModule; +{{/threetenbp}} + +import feign.Feign; +import feign.RequestInterceptor; +import feign.form.FormEncoder; +import feign.jackson.JacksonDecoder; +import feign.jackson.JacksonEncoder; +import feign.slf4j.Slf4jLogger; +import {{invokerPackage}}.auth.*; +import {{invokerPackage}}.auth.OAuth.AccessTokenListener; + +{{>generatedAnnotation}} +public class ApiClient { + public interface Api {} + + protected ObjectMapper objectMapper; + private String basePath = "{{{basePath}}}"; + private Map apiAuthorizations; + private Feign.Builder feignBuilder; + + public ApiClient() { + objectMapper = createObjectMapper(); + apiAuthorizations = new LinkedHashMap(); + feignBuilder = Feign.builder() + .encoder(new FormEncoder(new JacksonEncoder(objectMapper))) + .decoder(new JacksonDecoder(objectMapper)) + .logger(new Slf4jLogger()); + } + + public ApiClient(String[] authNames) { + this(); + for(String authName : authNames) { + {{#hasAuthMethods}} + RequestInterceptor auth; + {{#authMethods}}if ("{{name}}".equals(authName)) { + {{#is this 'basic'}} + auth = new HttpBasicAuth(); + {{/is}} + {{#is this 'api-key'}} + auth = new ApiKeyAuth({{#is this 'key-in-header'}}"header"{{/is}}{{#isNot this 'key-in-header'}}"query"{{/isNot}}, "{{keyParamName}}"); + {{/is}} + {{#is this 'oauth'}} + auth = new OAuth(OAuthFlow.{{flow}}, "{{authorizationUrl}}", "{{tokenUrl}}", "{{#scopes}}{{scope}}{{#hasMore}}, {{/hasMore}}{{/scopes}}"); + {{/is}} + } else {{/authMethods}}{ + throw new RuntimeException("auth name \"" + authName + "\" not found in available auth names"); + } + addAuthorization(authName, auth); + {{/hasAuthMethods}} + {{^hasAuthMethods}} + throw new RuntimeException("auth name \"" + authName + "\" not found in available auth names"); + {{/hasAuthMethods}} + } + } + + /** + * Basic constructor for single auth name + * @param authName + */ + public ApiClient(String authName) { + this(new String[]{authName}); + } + + /** + * Helper constructor for single api key + * @param authName + * @param apiKey + */ + public ApiClient(String authName, String apiKey) { + this(authName); + this.setApiKey(apiKey); + } + + /** + * Helper constructor for single basic auth or password oauth2 + * @param authName + * @param username + * @param password + */ + public ApiClient(String authName, String username, String password) { + this(authName); + this.setCredentials(username, password); + } + + /** + * Helper constructor for single password oauth2 + * @param authName + * @param clientId + * @param secret + * @param username + * @param password + */ + public ApiClient(String authName, String clientId, String secret, String username, String password) { + this(authName); + this.getTokenEndPoint() + .setClientId(clientId) + .setClientSecret(secret) + .setUsername(username) + .setPassword(password); + } + + public String getBasePath() { + return basePath; + } + + public ApiClient setBasePath(String basePath) { + this.basePath = basePath; + return this; + } + + public Map getApiAuthorizations() { + return apiAuthorizations; + } + + public void setApiAuthorizations(Map apiAuthorizations) { + this.apiAuthorizations = apiAuthorizations; + } + + public Feign.Builder getFeignBuilder() { + return feignBuilder; + } + + public ApiClient setFeignBuilder(Feign.Builder feignBuilder) { + this.feignBuilder = feignBuilder; + return this; + } + + private ObjectMapper createObjectMapper() { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING); + objectMapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING); + objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + objectMapper.disable(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE); + objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + objectMapper.setDateFormat(new RFC3339DateFormat()); + {{#joda}} + objectMapper.registerModule(new JodaModule()); + {{/joda}} + {{#java8}} + objectMapper.registerModule(new JavaTimeModule()); + {{/java8}} + {{#threetenbp}} + ThreeTenModule module = new ThreeTenModule(); + module.addDeserializer(Instant.class, CustomInstantDeserializer.INSTANT); + module.addDeserializer(OffsetDateTime.class, CustomInstantDeserializer.OFFSET_DATE_TIME); + module.addDeserializer(ZonedDateTime.class, CustomInstantDeserializer.ZONED_DATE_TIME); + objectMapper.registerModule(module); + {{/threetenbp}} + return objectMapper; + } + + public ObjectMapper getObjectMapper(){ + return objectMapper; + } + + /** + * Creates a feign client for given API interface. + * + * Usage: + * ApiClient apiClient = new ApiClient(); + * apiClient.setBasePath("http://localhost:8080"); + * XYZApi api = apiClient.buildClient(XYZApi.class); + * XYZResponse response = api.someMethod(...); + * @param Type + * @param clientClass Client class + * @return The Client + */ + public T buildClient(Class clientClass) { + return feignBuilder.target(clientClass, basePath); + } + + /** + * Select the Accept header's value from the given accepts array: + * if JSON exists in the given array, use it; + * otherwise use all of them (joining into a string) + * + * @param accepts The accepts array to select from + * @return The Accept header to use. If the given array is empty, + * null will be returned (not to set the Accept header explicitly). + */ + public String selectHeaderAccept(String[] accepts) { + if (accepts.length == 0) return null; + if (StringUtil.containsIgnoreCase(accepts, "application/json")) return "application/json"; + return StringUtil.join(accepts, ","); + } + + /** + * Select the Content-Type header's value from the given array: + * if JSON exists in the given array, use it; + * otherwise use the first one of the array. + * + * @param contentTypes The Content-Type array to select from + * @return The Content-Type header to use. If the given array is empty, + * JSON will be used. + */ + public String selectHeaderContentType(String[] contentTypes) { + if (contentTypes.length == 0) return "application/json"; + if (StringUtil.containsIgnoreCase(contentTypes, "application/json")) return "application/json"; + return contentTypes[0]; + } + + /** + * Helper method to configure the first api key found + * @param apiKey API key + */ + public void setApiKey(String apiKey) { + for(RequestInterceptor apiAuthorization : apiAuthorizations.values()) { + if (apiAuthorization instanceof ApiKeyAuth) { + ApiKeyAuth keyAuth = (ApiKeyAuth) apiAuthorization; + keyAuth.setApiKey(apiKey); + return ; + } + } + throw new RuntimeException("No API key authentication configured!"); + } + + /** + * Helper method to configure the username/password for basic auth or password OAuth + * @param username Username + * @param password Password + */ + public void setCredentials(String username, String password) { + for(RequestInterceptor apiAuthorization : apiAuthorizations.values()) { + if (apiAuthorization instanceof HttpBasicAuth) { + HttpBasicAuth basicAuth = (HttpBasicAuth) apiAuthorization; + basicAuth.setCredentials(username, password); + return; + } + if (apiAuthorization instanceof OAuth) { + OAuth oauth = (OAuth) apiAuthorization; + oauth.getTokenRequestBuilder().setUsername(username).setPassword(password); + return; + } + } + throw new RuntimeException("No Basic authentication or OAuth configured!"); + } + + /** + * Helper method to configure the token endpoint of the first oauth found in the apiAuthorizations (there should be only one) + * @return Token request builder + */ + public TokenRequestBuilder getTokenEndPoint() { + for(RequestInterceptor apiAuthorization : apiAuthorizations.values()) { + if (apiAuthorization instanceof OAuth) { + OAuth oauth = (OAuth) apiAuthorization; + return oauth.getTokenRequestBuilder(); + } + } + return null; + } + + /** + * Helper method to configure authorization endpoint of the first oauth found in the apiAuthorizations (there should be only one) + * @return Authentication request builder + */ + public AuthenticationRequestBuilder getAuthorizationEndPoint() { + for(RequestInterceptor apiAuthorization : apiAuthorizations.values()) { + if (apiAuthorization instanceof OAuth) { + OAuth oauth = (OAuth) apiAuthorization; + return oauth.getAuthenticationRequestBuilder(); + } + } + return null; + } + + /** + * Helper method to pre-set the oauth access token of the first oauth found in the apiAuthorizations (there should be only one) + * @param accessToken Access Token + * @param expiresIn Validity period in seconds + */ + public void setAccessToken(String accessToken, Long expiresIn) { + for(RequestInterceptor apiAuthorization : apiAuthorizations.values()) { + if (apiAuthorization instanceof OAuth) { + OAuth oauth = (OAuth) apiAuthorization; + oauth.setAccessToken(accessToken, expiresIn); + return; + } + } + } + + /** + * Helper method to configure the oauth accessCode/implicit flow parameters + * @param clientId Client ID + * @param clientSecret Client secret + * @param redirectURI Redirect URI + */ + public void configureAuthorizationFlow(String clientId, String clientSecret, String redirectURI) { + for(RequestInterceptor apiAuthorization : apiAuthorizations.values()) { + if (apiAuthorization instanceof OAuth) { + OAuth oauth = (OAuth) apiAuthorization; + oauth.getTokenRequestBuilder() + .setClientId(clientId) + .setClientSecret(clientSecret) + .setRedirectURI(redirectURI); + oauth.getAuthenticationRequestBuilder() + .setClientId(clientId) + .setRedirectURI(redirectURI); + return; + } + } + } + + /** + * Configures a listener which is notified when a new access token is received. + * @param accessTokenListener Acesss token listener + */ + public void registerAccessTokenListener(AccessTokenListener accessTokenListener) { + for(RequestInterceptor apiAuthorization : apiAuthorizations.values()) { + if (apiAuthorization instanceof OAuth) { + OAuth oauth = (OAuth) apiAuthorization; + oauth.registerAccessTokenListener(accessTokenListener); + return; + } + } + } + + /** + * Gets request interceptor based on authentication name + * @param authName Authentiation name + * @return Request Interceptor + */ + public RequestInterceptor getAuthorization(String authName) { + return apiAuthorizations.get(authName); + } + + /** + * Adds an authorization to be used by the client + * @param authName Authentication name + * @param authorization Request interceptor + */ + public void addAuthorization(String authName, RequestInterceptor authorization) { + if (apiAuthorizations.containsKey(authName)) { + throw new RuntimeException("auth name \"" + authName + "\" already in api authorizations"); + } + apiAuthorizations.put(authName, authorization); + feignBuilder.requestInterceptor(authorization); + } + +} diff --git a/mustacheTemplates/libraries/feign/EncodingUtils.mustache b/mustacheTemplates/libraries/feign/EncodingUtils.mustache new file mode 100644 index 0000000000..6f43e1c3c2 --- /dev/null +++ b/mustacheTemplates/libraries/feign/EncodingUtils.mustache @@ -0,0 +1,86 @@ +package {{invokerPackage}}; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** +* Utilities to support Swagger encoding formats in Feign. +*/ +public final class EncodingUtils { + + /** + * Private constructor. Do not construct this class. + */ + private EncodingUtils() {} + + /** + *

Encodes a collection of query parameters according to the Swagger + * collection format.

+ * + *

Of the various collection formats defined by Swagger ("csv", "tsv", + * etc), Feign only natively supports "multi". This utility generates the + * other format types so it will be properly processed by Feign.

+ * + *

Note, as part of reformatting, it URL encodes the parameters as + * well.

+ * @param parameters The collection object to be formatted. This object will + * not be changed. + * @param collectionFormat The Swagger collection format (eg, "csv", "tsv", + * "pipes"). See the + * + * Swagger Spec for more details. + * @return An object that will be correctly formatted by Feign. + */ + public static Object encodeCollection(Collection parameters, + String collectionFormat) { + if (parameters == null) { + return parameters; + } + List stringValues = new ArrayList<>(parameters.size()); + for (Object parameter : parameters) { + // ignore null values (same behavior as Feign) + if (parameter != null) { + stringValues.add(encode(parameter)); + } + } + // Feign natively handles single-element lists and the "multi" format. + if (stringValues.size() < 2 || "multi".equals(collectionFormat)) { + return stringValues; + } + // Otherwise return a formatted String + String[] stringArray = stringValues.toArray(new String[0]); + switch (collectionFormat) { + case "csv": + default: + return StringUtil.join(stringArray, ","); + case "ssv": + return StringUtil.join(stringArray, " "); + case "tsv": + return StringUtil.join(stringArray, "\t"); + case "pipes": + return StringUtil.join(stringArray, "|"); + } + } + + /** + * URL encode a single query parameter. + * @param parameter The query parameter to encode. This object will not be + * changed. + * @return The URL encoded string representation of the parameter. If the + * parameter is null, returns null. + */ + public static String encode(Object parameter) { + if (parameter == null) { + return null; + } + try { + return URLEncoder.encode(parameter.toString(), "UTF-8"); + } catch (UnsupportedEncodingException e) { + // Should never happen, UTF-8 is always supported + throw new RuntimeException(e); + } + } +} diff --git a/mustacheTemplates/libraries/feign/ParamExpander.mustache b/mustacheTemplates/libraries/feign/ParamExpander.mustache new file mode 100644 index 0000000000..2f5095d00f --- /dev/null +++ b/mustacheTemplates/libraries/feign/ParamExpander.mustache @@ -0,0 +1,22 @@ +package {{invokerPackage}}; + +import feign.Param; + +import java.text.DateFormat; +import java.util.Date; + +/** + * Param Expander to convert {@link Date} to RFC3339 + */ +public class ParamExpander implements Param.Expander { + + private static final DateFormat dateformat = new RFC3339DateFormat(); + + @Override + public String expand(Object value) { + if (value instanceof Date) { + return dateformat.format(value); + } + return value.toString(); + } +} diff --git a/mustacheTemplates/libraries/feign/README.mustache b/mustacheTemplates/libraries/feign/README.mustache new file mode 100644 index 0000000000..56560172ee --- /dev/null +++ b/mustacheTemplates/libraries/feign/README.mustache @@ -0,0 +1,43 @@ +# {{artifactId}} + +## Requirements + +Building the API client library requires [Maven](https://maven.apache.org/) to be installed. + +## Installation & Usage + +To install the API client library to your local Maven repository, simply execute: + +```shell +mvn install +``` + +To deploy it to a remote Maven repository instead, configure the settings of the repository and execute: + +```shell +mvn deploy +``` + +Refer to the [official documentation](https://maven.apache.org/plugins/maven-deploy-plugin/usage.html) for more information. + +After the client library is installed/deployed, you can use it in your Maven project by adding the following to your *pom.xml*: + +```xml + + {{groupId}} + {{artifactId}} + {{artifactVersion}} + compile + + +``` + +## Recommendation + +It's recommended to create an instance of `ApiClient` per thread in a multithreaded environment to avoid any potential issues. + +## Author + +{{#apiInfo}}{{#apis}}{{^hasMore}}{{infoEmail}} +{{/hasMore}}{{/apis}}{{/apiInfo}} + diff --git a/mustacheTemplates/libraries/feign/api.mustache b/mustacheTemplates/libraries/feign/api.mustache new file mode 100644 index 0000000000..ccefd3fcb3 --- /dev/null +++ b/mustacheTemplates/libraries/feign/api.mustache @@ -0,0 +1,123 @@ +package {{package}}; + +import {{invokerPackage}}.ApiClient; +import {{invokerPackage}}.EncodingUtils; +{{#legacyDates}} +import {{invokerPackage}}.ParamExpander; +{{/legacyDates}} + +{{#imports}}import {{import}}; +{{/imports}} + +{{^fullJavaUtil}} +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +{{/fullJavaUtil}} +import feign.*; + +{{>generatedAnnotation}} +public interface {{classname}} extends ApiClient.Api { + +{{#operations}} +{{#operation}} +{{#contents}} + /** + * {{summary}} + * {{notes}} + {{#parameters}} + * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}} + {{/parameters}} + {{#returnType}} + * @return {{returnType}} + {{/returnType}} + {{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation + {{/externalDocs}} + */ + @RequestLine("{{httpMethod}} {{{path}}}{{#hasQueryParams}}?{{/hasQueryParams}}{{#queryParams}}{{baseName}}={{braces "left"}}{{paramName}}{{braces "right"}}{{#has this 'more'}}&{{/has}}{{/queryParams}}") + @Headers({ + {{#vendorExtensions.x-contentType}} + "Content-Type: {{vendorExtensions.x-contentType}}", + {{/vendorExtensions.x-contentType}} + {{#vendorExtensions.x-accepts}} + "Accept: {{vendorExtensions.x-accepts}}", + {{/vendorExtensions.x-accepts}} + {{^vendorExtensions.x-accepts}} + "Accept: */*", + {{/vendorExtensions.x-accepts}} + {{#headerParams}} + "{{baseName}}: {{braces "left"}}{{paramName}}{{braces "right"}}"{{#has this 'more'}},{{/has}} + {{/headerParams}} + }) + {{#returnType}}{{{returnType}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{nickname}}({{#parameters}}{{#isNot this 'body-param'}}{{^legacyDates}}@Param("{{paramName}}") {{/legacyDates}}{{#legacyDates}}@Param(value="{{paramName}}", expander=ParamExpander.class) {{/legacyDates}}{{/isNot}}{{{dataType}}} {{paramName}}{{#has this 'more'}}, {{/has}}{{/parameters}}); + {{#hasQueryParams}} + + /** + * {{summary}} + * {{notes}} + * Note, this is equivalent to the other {{operationId}} method, + * but with the query parameters collected into a single Map parameter. This + * is convenient for services with optional query parameters, especially when + * used with the {@link {{operationIdCamelCase}}QueryParams} class that allows for + * building up this map in a fluent style. + {{#parameters}} + {{#isNot this 'query-param'}} + * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}} + {{/isNot}} + {{/parameters}} + * @param queryParams Map of query parameters as name-value pairs + *

The following elements may be specified in the query map:

+ *
    + {{#queryParams}} + *
  • {{paramName}} - {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}
  • + {{/queryParams}} + *
+ {{#returnType}} + * @return {{returnType}} + {{/returnType}} + {{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation{{/externalDocs}} + */ + @RequestLine("{{httpMethod}} {{{path}}}?{{#queryParams}}{{baseName}}={{braces "left"}}{{paramName}}{{braces "right"}}{{#has this 'more'}}&{{/has}}{{/queryParams}}") + @Headers({ + {{#vendorExtensions.x-contentType}} + "Content-Type: {{vendorExtensions.x-contentType}}", + {{/vendorExtensions.x-contentType}} + {{^vendorExtensions.x-contentType}} + "Content-Type: */*", + {{/vendorExtensions.x-contentType}} + {{#vendorExtensions.x-accepts}} + "Accept: {{vendorExtensions.x-accepts}}", + {{/vendorExtensions.x-accepts}} + {{#headerParams}} + "{{baseName}}: {{braces "left"}}{{paramName}}{{braces "right"}}"{{#has this 'more'}},{{/has}} + {{/headerParams}} + }) + {{#returnType}}{{{returnType}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{nickname}}({{#parameters}}{{#isNot this 'query-param'}}{{#isNot this 'body-param'}}{{^legacyDates}}@Param("{{paramName}}") {{/legacyDates}}{{#legacyDates}}@Param(value="{{paramName}}", expander=ParamExpander.class) {{/legacyDates}}{{/isNot}}{{{dataType}}} {{paramName}}, {{/isNot}}{{/parameters}}@QueryMap(encoded=true) Map queryParams); + + /** + * A convenience class for generating query parameters for the + * {{operationId}} method in a fluent style. + */ + public static class {{operationIdCamelCase}}QueryParams extends HashMap { + {{#queryParams}} + public {{operationIdCamelCase}}QueryParams {{paramName}}(final {{{dataType}}} value) { + {{#collectionFormat}} + put("{{baseName}}", EncodingUtils.encodeCollection(value, "{{collectionFormat}}")); + {{/collectionFormat}} + {{^collectionFormat}} + put("{{baseName}}", EncodingUtils.encode(value)); + {{/collectionFormat}} + return this; + } + {{/queryParams}} + } + {{/hasQueryParams}} + {{/contents}} + {{/operation}} +{{/operations}} +} diff --git a/mustacheTemplates/libraries/feign/api_test.mustache b/mustacheTemplates/libraries/feign/api_test.mustache new file mode 100644 index 0000000000..aed61d9d25 --- /dev/null +++ b/mustacheTemplates/libraries/feign/api_test.mustache @@ -0,0 +1,70 @@ +package {{package}}; + +import {{invokerPackage}}.ApiClient; +{{#imports}}import {{import}}; +{{/imports}} +import org.junit.Before; +import org.junit.Test; + +{{^fullJavaUtil}} +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +{{/fullJavaUtil}} + +/** + * API tests for {{classname}} + */ +public class {{classname}}Test { + + private {{classname}} api; + + @Before + public void setup() { + api = new ApiClient().buildClient({{classname}}.class); + } + + {{#operations}}{{#operation}}{{#contents}}{{#@first}} + /** + * {{summary}} + * + * {{notes}} + */ + @Test + public void {{operationId}}Test() { + {{#parameters}} + {{{dataType}}} {{paramName}} = null; + {{/parameters}} + // {{#returnType}}{{{returnType}}} response = {{/returnType}}api.{{operationId}}({{#parameters}}{{paramName}}{{#has this 'more'}}, {{/has}}{{/parameters}}); + + // TODO: test validations + } + + {{#hasQueryParams}} + /** + * {{summary}} + * + * {{notes}} + * + * This tests the overload of the method that uses a Map for query parameters instead of + * listing them out individually. + */ + @Test + public void {{operationId}}TestQueryMap() { + {{#parameters}} + {{#isNot this 'query-param'}} + {{{dataType}}} {{paramName}} = null; + {{/isNot}} + {{/parameters}} + {{classname}}.{{operationIdCamelCase}}QueryParams queryParams = new {{classname}}.{{operationIdCamelCase}}QueryParams() + {{#queryParams}} + .{{paramName}}(null){{#hasNot this 'more'}};{{/hasNot}} + {{/queryParams}} + // {{#returnType}}{{{returnType}}} response = {{/returnType}}api.{{operationId}}({{#parameters}}{{#isNot this 'query-param'}}{{paramName}}, {{/isNot}}{{/parameters}}queryParams); + + // TODO: test validations + } + {{/hasQueryParams}} + {{/@first}}{{/contents}}{{/operation}}{{/operations}} +} diff --git a/mustacheTemplates/libraries/feign/auth/ApiKeyAuth.mustache b/mustacheTemplates/libraries/feign/auth/ApiKeyAuth.mustache new file mode 100644 index 0000000000..9982cd7fc4 --- /dev/null +++ b/mustacheTemplates/libraries/feign/auth/ApiKeyAuth.mustache @@ -0,0 +1,41 @@ +package {{invokerPackage}}.auth; + +import feign.RequestInterceptor; +import feign.RequestTemplate; + +public class ApiKeyAuth implements RequestInterceptor { + private final String location; + private final String paramName; + + private String apiKey; + + public ApiKeyAuth(String location, String paramName) { + this.location = location; + this.paramName = paramName; + } + + public String getLocation() { + return location; + } + + public String getParamName() { + return paramName; + } + + public String getApiKey() { + return apiKey; + } + + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } + + @Override + public void apply(RequestTemplate template) { + if ("query".equals(location)) { + template.query(paramName, apiKey); + } else if ("header".equals(location)) { + template.header(paramName, apiKey); + } + } +} diff --git a/mustacheTemplates/libraries/feign/auth/HttpBasicAuth.mustache b/mustacheTemplates/libraries/feign/auth/HttpBasicAuth.mustache new file mode 100644 index 0000000000..2b1acb8ff7 --- /dev/null +++ b/mustacheTemplates/libraries/feign/auth/HttpBasicAuth.mustache @@ -0,0 +1,41 @@ +package {{invokerPackage}}.auth; + +import feign.RequestInterceptor; +import feign.RequestTemplate; +import feign.auth.BasicAuthRequestInterceptor; + +/** + * An interceptor that adds the request header needed to use HTTP basic authentication. + */ +public class HttpBasicAuth implements RequestInterceptor { + + private String username; + private String password; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public void setCredentials(String username, String password) { + this.username = username; + this.password = password; + } + + @Override + public void apply(RequestTemplate template) { + RequestInterceptor requestInterceptor = new BasicAuthRequestInterceptor(username, password); + requestInterceptor.apply(template); + } +} diff --git a/mustacheTemplates/libraries/feign/auth/OAuth.mustache b/mustacheTemplates/libraries/feign/auth/OAuth.mustache new file mode 100644 index 0000000000..5b64a9146d --- /dev/null +++ b/mustacheTemplates/libraries/feign/auth/OAuth.mustache @@ -0,0 +1,203 @@ +package {{invokerPackage}}.auth; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +import org.apache.oltu.oauth2.client.HttpClient; +import org.apache.oltu.oauth2.client.OAuthClient; +import org.apache.oltu.oauth2.client.request.OAuthClientRequest; +import org.apache.oltu.oauth2.client.request.OAuthClientRequest.AuthenticationRequestBuilder; +import org.apache.oltu.oauth2.client.request.OAuthClientRequest.TokenRequestBuilder; +import org.apache.oltu.oauth2.client.response.OAuthClientResponse; +import org.apache.oltu.oauth2.client.response.OAuthClientResponseFactory; +import org.apache.oltu.oauth2.client.response.OAuthJSONAccessTokenResponse; +import org.apache.oltu.oauth2.common.exception.OAuthProblemException; +import org.apache.oltu.oauth2.common.exception.OAuthSystemException; +import org.apache.oltu.oauth2.common.message.types.GrantType; +import org.apache.oltu.oauth2.common.token.BasicOAuthToken; + +import feign.Client; +import feign.Request.Options; +import feign.RequestInterceptor; +import feign.RequestTemplate; +import feign.Response; +import feign.RetryableException; +import feign.Util; +import {{invokerPackage}}.StringUtil; + + +public class OAuth implements RequestInterceptor { + + static final int MILLIS_PER_SECOND = 1000; + + public interface AccessTokenListener { + void notify(BasicOAuthToken token); + } + + private volatile String accessToken; + private Long expirationTimeMillis; + private OAuthClient oauthClient; + private TokenRequestBuilder tokenRequestBuilder; + private AuthenticationRequestBuilder authenticationRequestBuilder; + private AccessTokenListener accessTokenListener; + + public OAuth(Client client, TokenRequestBuilder requestBuilder) { + this.oauthClient = new OAuthClient(new OAuthFeignClient(client)); + this.tokenRequestBuilder = requestBuilder; + } + + public OAuth(Client client, OAuthFlow flow, String authorizationUrl, String tokenUrl, String scopes) { + this(client, OAuthClientRequest.tokenLocation(tokenUrl).setScope(scopes)); + + switch(flow) { + case accessCode: + case implicit: + tokenRequestBuilder.setGrantType(GrantType.AUTHORIZATION_CODE); + break; + case password: + tokenRequestBuilder.setGrantType(GrantType.PASSWORD); + break; + case application: + tokenRequestBuilder.setGrantType(GrantType.CLIENT_CREDENTIALS); + break; + default: + break; + } + authenticationRequestBuilder = OAuthClientRequest.authorizationLocation(authorizationUrl); + } + + public OAuth(OAuthFlow flow, String authorizationUrl, String tokenUrl, String scopes) { + this(new Client.Default(null, null), flow, authorizationUrl, tokenUrl, scopes); + } + + @Override + public void apply(RequestTemplate template) { + // If the request already have an authorization (eg. Basic auth), do nothing + if (template.headers().containsKey("Authorization")) { + return; + } + // If first time, get the token + if (expirationTimeMillis == null || System.currentTimeMillis() >= expirationTimeMillis) { + updateAccessToken(); + } + if (getAccessToken() != null) { + template.header("Authorization", "Bearer " + getAccessToken()); + } + } + + public synchronized void updateAccessToken() { + OAuthJSONAccessTokenResponse accessTokenResponse; + try { + accessTokenResponse = oauthClient.accessToken(tokenRequestBuilder.buildBodyMessage()); + } catch (Exception e) { + throw new RetryableException(e.getMessage(), e,null); + } + if (accessTokenResponse != null && accessTokenResponse.getAccessToken() != null) { + setAccessToken(accessTokenResponse.getAccessToken(), accessTokenResponse.getExpiresIn()); + if (accessTokenListener != null) { + accessTokenListener.notify((BasicOAuthToken) accessTokenResponse.getOAuthToken()); + } + } + } + + public synchronized void registerAccessTokenListener(AccessTokenListener accessTokenListener) { + this.accessTokenListener = accessTokenListener; + } + + public synchronized String getAccessToken() { + return accessToken; + } + + public synchronized void setAccessToken(String accessToken, Long expiresIn) { + this.accessToken = accessToken; + this.expirationTimeMillis = System.currentTimeMillis() + expiresIn * MILLIS_PER_SECOND; + } + + public TokenRequestBuilder getTokenRequestBuilder() { + return tokenRequestBuilder; + } + + public void setTokenRequestBuilder(TokenRequestBuilder tokenRequestBuilder) { + this.tokenRequestBuilder = tokenRequestBuilder; + } + + public AuthenticationRequestBuilder getAuthenticationRequestBuilder() { + return authenticationRequestBuilder; + } + + public void setAuthenticationRequestBuilder(AuthenticationRequestBuilder authenticationRequestBuilder) { + this.authenticationRequestBuilder = authenticationRequestBuilder; + } + + public OAuthClient getOauthClient() { + return oauthClient; + } + + public void setOauthClient(OAuthClient oauthClient) { + this.oauthClient = oauthClient; + } + + public void setOauthClient(Client client) { + this.oauthClient = new OAuthClient( new OAuthFeignClient(client)); + } + + public static class OAuthFeignClient implements HttpClient { + + private Client client; + + public OAuthFeignClient() { + this.client = new Client.Default(null, null); + } + + public OAuthFeignClient(Client client) { + this.client = client; + } + + public T execute(OAuthClientRequest request, Map headers, + String requestMethod, Class responseClass) + throws OAuthSystemException, OAuthProblemException { + + RequestTemplate req = new RequestTemplate() + .append(request.getLocationUri()) + .method(requestMethod) + .body(request.getBody()); + + for (Entry entry : headers.entrySet()) { + req.header(entry.getKey(), entry.getValue()); + } + Response feignResponse; + String body = ""; + try { + feignResponse = client.execute(req.request(), new Options()); + body = Util.toString(feignResponse.body().asReader()); + } catch (IOException e) { + throw new OAuthSystemException(e); + } + + String contentType = null; + Collection contentTypeHeader = feignResponse.headers().get("Content-Type"); + if(contentTypeHeader != null) { + contentType = StringUtil.join(contentTypeHeader.toArray(new String[0]), ";"); + } + + return OAuthClientResponseFactory.createCustomResponse( + body, + contentType, + feignResponse.status(), + feignResponse.headers().entrySet().stream().collect(Collectors.toMap( + Map.Entry::getKey, + e -> new ArrayList<>(e.getValue()) + )), + responseClass + ); + } + + public void shutdown() { + // Nothing to do here + } + } +} diff --git a/mustacheTemplates/libraries/feign/build.gradle.mustache b/mustacheTemplates/libraries/feign/build.gradle.mustache new file mode 100644 index 0000000000..07b70c841d --- /dev/null +++ b/mustacheTemplates/libraries/feign/build.gradle.mustache @@ -0,0 +1,145 @@ +apply plugin: 'idea' +apply plugin: 'eclipse' + +group = '{{groupId}}' +version = '{{artifactVersion}}' + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.3.+' + classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' + } +} + +repositories { + jcenter() +} + + +if(hasProperty('target') && target == 'android') { + + apply plugin: 'com.android.library' + apply plugin: 'com.github.dcendents.android-maven' + + android { + compileSdkVersion 25 + buildToolsVersion '25.0.2' + defaultConfig { + minSdkVersion 14 + targetSdkVersion 25 + } + compileOptions { + {{#java8}} + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + {{/java8}} + {{^java8}} + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 + {{/java8}} + } + + // Rename the aar correctly + libraryVariants.all { variant -> + variant.outputs.each { output -> + def outputFile = output.outputFile + if (outputFile != null && outputFile.name.endsWith('.aar')) { + def fileName = "${project.name}-${variant.baseName}-${version}.aar" + output.outputFile = new File(outputFile.parent, fileName) + } + } + } + + dependencies { + provided 'javax.annotation:jsr250-api:1.0' + } + } + + afterEvaluate { + android.libraryVariants.all { variant -> + def task = project.tasks.create "jar${variant.name.capitalize()}", Jar + task.description = "Create jar artifact for ${variant.name}" + task.dependsOn variant.javaCompile + task.from variant.javaCompile.destinationDir + task.destinationDir = project.file("${project.buildDir}/outputs/jar") + task.archiveName = "${project.name}-${variant.baseName}-${version}.jar" + artifacts.add('archives', task); + } + } + + task sourcesJar(type: Jar) { + from android.sourceSets.main.java.srcDirs + classifier = 'sources' + } + + artifacts { + archives sourcesJar + } + +} else { + + apply plugin: 'java' + apply plugin: 'maven' + + sourceCompatibility = JavaVersion.VERSION_{{^java8}}1_7{{/java8}}{{#java8}}1_8{{/java8}} + targetCompatibility = JavaVersion.VERSION_{{^java8}}1_7{{/java8}}{{#java8}}1_8{{/java8}} + + install { + repositories.mavenInstaller { + pom.artifactId = '{{artifactId}}' + } + } + + task execute(type:JavaExec) { + main = System.getProperty('mainClass') + classpath = sourceSets.main.runtimeClasspath + } +} + +ext { + {{#useOas2}} + swagger_annotations_version = "1.5.9" + {{/useOas2}} + {{^useOas2}} + swagger_annotations_version = "2.0.0" + {{/useOas2}} + jackson_version = "2.10.1" + {{#threetenbp}} + threepane_version = "2.6.4" + {{/threetenbp}} + feign_version = "9.4.0" + feign_form_version = "2.1.0" + junit_version = "4.12" + oltu_version = "1.0.2" +} + +dependencies { + {{#useOas2}} + compile "io.swagger:swagger-annotations:$swagger_annotations_version" + {{/useOas2}} + {{^useOas2}} + compile "io.swagger.core.v3:swagger-annotations:$swagger_annotations_version" + {{/useOas2}} + compile "com.netflix.feign:feign-core:$feign_version" + compile "com.netflix.feign:feign-jackson:$feign_version" + compile "com.netflix.feign:feign-slf4j:$feign_version" + compile "io.github.openfeign.form:feign-form:$feign_form_version" + compile "com.fasterxml.jackson.core:jackson-core:$jackson_version" + compile "com.fasterxml.jackson.core:jackson-annotations:$jackson_version" + compile "com.fasterxml.jackson.core:jackson-databind:$jackson_version" + {{#joda}} + compile "com.fasterxml.jackson.datatype:jackson-datatype-joda:$jackson_version" + {{/joda}} + {{#java8}} + compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jackson_version" + {{/java8}} + {{#threetenbp}} + compile "com.github.joschi.jackson:jackson-datatype-threetenbp:$threepane_version" + {{/threetenbp}} + compile "org.apache.oltu.oauth2:org.apache.oltu.oauth2.client:$oltu_version" + compile "com.brsanthu:migbase64:2.2" + testCompile "junit:junit:$junit_version" +} diff --git a/mustacheTemplates/libraries/feign/build.sbt.mustache b/mustacheTemplates/libraries/feign/build.sbt.mustache new file mode 100644 index 0000000000..7230311c3d --- /dev/null +++ b/mustacheTemplates/libraries/feign/build.sbt.mustache @@ -0,0 +1,31 @@ +lazy val root = (project in file(".")). + settings( + organization := "{{groupId}}", + name := "{{artifactId}}", + version := "{{artifactVersion}}", + scalaVersion := "2.11.4", + scalacOptions ++= Seq("-feature"), + javacOptions in compile ++= Seq("-Xlint:deprecation"), + publishArtifact in (Compile, packageDoc) := false, + resolvers += Resolver.mavenLocal, + libraryDependencies ++= Seq( + {{#useOas2}} + "io.swagger" % "swagger-annotations" % "1.5.9" % "compile", + {{/useOas2}} + {{^useOas2}} + "io.swagger.core.v3" % "swagger-annotations" % "2.0.0" % "compile", + {{/useOas2}} + "com.netflix.feign" % "feign-core" % "9.4.0" % "compile", + "com.netflix.feign" % "feign-jackson" % "9.4.0" % "compile", + "com.netflix.feign" % "feign-slf4j" % "9.4.0" % "compile", + "io.github.openfeign.form" % "feign-form" % "2.1.0" % "compile", + "com.fasterxml.jackson.core" % "jackson-core" % "2.10.1" % "compile", + "com.fasterxml.jackson.core" % "jackson-annotations" % "2.10.1" % "compile", + "com.fasterxml.jackson.core" % "jackson-databind" % "2.10.1" % "compile", + "com.fasterxml.jackson.datatype" % "jackson-datatype-{{^java8}}joda{{/java8}}{{#java8}}jsr310{{/java8}}" % "2.10.1" % "compile", + "org.apache.oltu.oauth2" % "org.apache.oltu.oauth2.client" % "1.0.2" % "compile", + "com.brsanthu" % "migbase64" % "2.2" % "compile", + "junit" % "junit" % "4.12" % "test", + "com.novocode" % "junit-interface" % "0.10" % "test" + ) + ) diff --git a/mustacheTemplates/libraries/feign/pom.mustache b/mustacheTemplates/libraries/feign/pom.mustache new file mode 100644 index 0000000000..86be3068b6 --- /dev/null +++ b/mustacheTemplates/libraries/feign/pom.mustache @@ -0,0 +1,301 @@ + + 4.0.0 + {{groupId}} + {{artifactId}} + jar + {{artifactId}} + {{artifactVersion}} + {{artifactUrl}} + {{artifactDescription}} + + {{scmConnection}} + {{scmDeveloperConnection}} + {{scmUrl}} + + + 2.2.0 + + + + + {{licenseName}} + {{licenseUrl}} + repo + + + + + + {{developerName}} + {{developerEmail}} + {{developerOrganization}} + {{developerOrganizationUrl}} + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.12 + + + + loggerPath + conf/log4j.properties + + + -Xms512m -Xmx1500m + methods + pertest + + + + maven-dependency-plugin + + + package + + copy-dependencies + + + ${project.build.directory}/lib + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.2 + + + + jar + test-jar + + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add_sources + generate-sources + + add-source + + + + src/main/java + + + + + add_test_sources + generate-test-sources + + add-test-source + + + + src/test/java + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + attach-sources + + jar-no-fork + + + + + + + + + + sign-artifacts + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + sign-artifacts + verify + + sign + + + + + + + + + + + {{#useOas2}} + + io.swagger + swagger-annotations + ${swagger-core-version} + + {{/useOas2}} + {{^useOas2}} + + io.swagger.core.v3 + swagger-annotations + ${swagger-core-version} + + {{/useOas2}} + + + + io.github.openfeign + feign-core + ${feign-version} + + + io.github.openfeign + feign-jackson + ${feign-version} + + + io.github.openfeign + feign-slf4j + ${feign-version} + + + io.github.openfeign.form + feign-form + ${feign-form-version} + + + + + com.fasterxml.jackson.core + jackson-core + ${jackson-version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson-version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson-version} + + {{#withXml}} + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + ${jackson-version} + + + {{/withXml}} + {{#joda}} + + com.fasterxml.jackson.datatype + jackson-datatype-joda + ${jackson-version} + + {{/joda}} + {{#java8}} + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson-version} + + {{/java8}} + {{#threetenbp}} + + com.github.joschi.jackson + jackson-datatype-threetenbp + ${jackson-threetenbp-version} + + {{/threetenbp}} + + org.apache.oltu.oauth2 + org.apache.oltu.oauth2.client + ${oltu-version} + + + + + junit + junit + ${junit-version} + test + + + com.squareup.okhttp3 + mockwebserver + 3.6.0 + test + + + org.assertj + assertj-core + 1.7.1 + test + + + + {{#java8}}1.8{{/java8}}{{^java8}}1.7{{/java8}} + ${java.version} + ${java.version} + {{#useOas2}} + 1.5.15 + {{/useOas2}} + {{^useOas2}} + 2.0.0 + {{/useOas2}} + 9.4.0 + 2.1.0 + 2.10.1 + {{#threetenbp}} + 2.6.4 + {{/threetenbp}} + 4.12 + 1.0.0 + 1.0.2 + + diff --git a/mustacheTemplates/libraries/jersey2/ApiClient.mustache b/mustacheTemplates/libraries/jersey2/ApiClient.mustache new file mode 100644 index 0000000000..8f43c9b7bd --- /dev/null +++ b/mustacheTemplates/libraries/jersey2/ApiClient.mustache @@ -0,0 +1,823 @@ +package {{invokerPackage}}; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.Invocation; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.Form; +import javax.ws.rs.core.GenericType; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.client.HttpUrlConnectorProvider; +import org.glassfish.jersey.jackson.JacksonFeature; +{{^supportJava6}} +import org.glassfish.jersey.logging.LoggingFeature; +{{/supportJava6}} +import org.glassfish.jersey.media.multipart.FormDataBodyPart; +import org.glassfish.jersey.media.multipart.FormDataContentDisposition; +import org.glassfish.jersey.media.multipart.MultiPart; +import org.glassfish.jersey.media.multipart.MultiPartFeature; + +import java.io.IOException; +import java.io.InputStream; + +{{^supportJava6}} +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +{{/supportJava6}} +{{#supportJava6}} +import org.apache.commons.io.FileUtils; +{{/supportJava6}} +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import java.util.HashMap; +import java.util.List; +import java.util.ArrayList; +import java.util.Date; +import java.util.TimeZone; + +import java.net.URLEncoder; + +import java.io.File; +import java.io.UnsupportedEncodingException; + +import java.text.DateFormat; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import {{invokerPackage}}.auth.Authentication; +import {{invokerPackage}}.auth.HttpBasicAuth; +import {{invokerPackage}}.auth.ApiKeyAuth; +import {{invokerPackage}}.auth.OAuth; + +{{>generatedAnnotation}} +public class ApiClient { + protected Map defaultHeaderMap = new HashMap(); + protected String basePath = "{{{basePath}}}"; + protected boolean debugging = false; + protected int connectionTimeout = 0; + private int readTimeout = 0; + + protected Client httpClient; + protected JSON json; + protected String tempFolderPath = null; + + protected Map authentications; + + protected int statusCode; + protected Map> responseHeaders; + + protected DateFormat dateFormat; + + public ApiClient() { + json = new JSON(); + httpClient = buildHttpClient(debugging); + + this.dateFormat = new RFC3339DateFormat(); + + // Set default User-Agent. + setUserAgent("{{#httpUserAgent}}{{{.}}}{{/httpUserAgent}}{{^httpUserAgent}}Swagger-Codegen/{{{artifactVersion}}}/java{{/httpUserAgent}}"); + + // Setup authentications (key: authentication name, value: authentication). + authentications = new HashMap();{{#authMethods}}{{#is this 'basic'}} + authentications.put("{{name}}", new HttpBasicAuth());{{/is}}{{#is this 'api-key'}} + authentications.put("{{name}}", new ApiKeyAuth({{#is this 'key-in-header'}}"header"{{/is}}{{#isNot this 'key-in-header'}}"query"{{/isNot}}, "{{keyParamName}}"));{{/is}}{{#is this 'oauth'}} + authentications.put("{{name}}", new OAuth());{{/is}}{{#is this 'bearer'}} + authentications.put("{{name}}", new OAuth());{{/is}}{{/authMethods}} + // Prevent the authentications from being modified. + authentications = Collections.unmodifiableMap(authentications); + } + + /** + * Gets the JSON instance to do JSON serialization and deserialization. + * @return JSON + */ + public JSON getJSON() { + return json; + } + + public Client getHttpClient() { + return httpClient; + } + + public ApiClient setHttpClient(Client httpClient) { + this.httpClient = httpClient; + return this; + } + + public String getBasePath() { + return basePath; + } + + public ApiClient setBasePath(String basePath) { + this.basePath = basePath; + return this; + } + + /** + * Gets the status code of the previous request + * @return Status code + */ + public int getStatusCode() { + return statusCode; + } + + /** + * Gets the response headers of the previous request + * @return Response headers + */ + public Map> getResponseHeaders() { + return responseHeaders; + } + + /** + * Get authentications (key: authentication name, value: authentication). + * @return Map of authentication object + */ + public Map getAuthentications() { + return authentications; + } + + /** + * Get authentication for the given name. + * + * @param authName The authentication name + * @return The authentication, null if not found + */ + public Authentication getAuthentication(String authName) { + return authentications.get(authName); + } + + /** + * Helper method to set username for the first HTTP basic authentication. + * @param username Username + */ + public void setUsername(String username) { + for (Authentication auth : authentications.values()) { + if (auth instanceof HttpBasicAuth) { + ((HttpBasicAuth) auth).setUsername(username); + return; + } + } + throw new RuntimeException("No HTTP basic authentication configured!"); + } + + /** + * Helper method to set password for the first HTTP basic authentication. + * @param password Password + */ + public void setPassword(String password) { + for (Authentication auth : authentications.values()) { + if (auth instanceof HttpBasicAuth) { + ((HttpBasicAuth) auth).setPassword(password); + return; + } + } + throw new RuntimeException("No HTTP basic authentication configured!"); + } + + /** + * Helper method to set API key value for the first API key authentication. + * @param apiKey API key + */ + public void setApiKey(String apiKey) { + for (Authentication auth : authentications.values()) { + if (auth instanceof ApiKeyAuth) { + ((ApiKeyAuth) auth).setApiKey(apiKey); + return; + } + } + throw new RuntimeException("No API key authentication configured!"); + } + + /** + * Helper method to set API key prefix for the first API key authentication. + * @param apiKeyPrefix API key prefix + */ + public void setApiKeyPrefix(String apiKeyPrefix) { + for (Authentication auth : authentications.values()) { + if (auth instanceof ApiKeyAuth) { + ((ApiKeyAuth) auth).setApiKeyPrefix(apiKeyPrefix); + return; + } + } + throw new RuntimeException("No API key authentication configured!"); + } + + /** + * Helper method to set access token for the first OAuth2 authentication. + * @param accessToken Access token + */ + public void setAccessToken(String accessToken) { + for (Authentication auth : authentications.values()) { + if (auth instanceof OAuth) { + ((OAuth) auth).setAccessToken(accessToken); + return; + } + } + throw new RuntimeException("No OAuth2 authentication configured!"); + } + + /** + * Set the User-Agent header's value (by adding to the default header map). + * @param userAgent Http user agent + * @return API client + */ + public ApiClient setUserAgent(String userAgent) { + addDefaultHeader("User-Agent", userAgent); + return this; + } + + /** + * Add a default header. + * + * @param key The header's key + * @param value The header's value + * @return API client + */ + public ApiClient addDefaultHeader(String key, String value) { + defaultHeaderMap.put(key, value); + return this; + } + + /** + * Check that whether debugging is enabled for this API client. + * @return True if debugging is switched on + */ + public boolean isDebugging() { + return debugging; + } + + /** + * Enable/disable debugging for this API client. + * + * @param debugging To enable (true) or disable (false) debugging + * @return API client + */ + public ApiClient setDebugging(boolean debugging) { + this.debugging = debugging; + // Rebuild HTTP Client according to the new "debugging" value. + this.httpClient = buildHttpClient(debugging); + return this; + } + + /** + * The path of temporary folder used to store downloaded files from endpoints + * with file response. The default value is null, i.e. using + * the system's default tempopary folder. + * + * @return Temp folder path + */ + public String getTempFolderPath() { + return tempFolderPath; + } + + /** + * Set temp folder path + * @param tempFolderPath Temp folder path + * @return API client + */ + public ApiClient setTempFolderPath(String tempFolderPath) { + this.tempFolderPath = tempFolderPath; + return this; + } + + /** + * Connect timeout (in milliseconds). + * @return Connection timeout + */ + public int getConnectTimeout() { + return connectionTimeout; + } + + /** + * Set the connect timeout (in milliseconds). + * A value of 0 means no timeout, otherwise values must be between 1 and + * {@link Integer#MAX_VALUE}. + * @param connectionTimeout Connection timeout in milliseconds + * @return API client + */ + public ApiClient setConnectTimeout(int connectionTimeout) { + this.connectionTimeout = connectionTimeout; + httpClient.property(ClientProperties.CONNECT_TIMEOUT, connectionTimeout); + return this; + } + + /** + * read timeout (in milliseconds). + * @return Read timeout + */ + public int getReadTimeout() { + return readTimeout; + } + + /** + * Set the read timeout (in milliseconds). + * A value of 0 means no timeout, otherwise values must be between 1 and + * {@link Integer#MAX_VALUE}. + * @param readTimeout Read timeout in milliseconds + * @return API client + */ + public ApiClient setReadTimeout(int readTimeout) { + this.readTimeout = readTimeout; + httpClient.property(ClientProperties.READ_TIMEOUT, readTimeout); + return this; + } + + /** + * Get the date format used to parse/format date parameters. + * @return Date format + */ + public DateFormat getDateFormat() { + return dateFormat; + } + + /** + * Set the date format used to parse/format date parameters. + * @param dateFormat Date format + * @return API client + */ + public ApiClient setDateFormat(DateFormat dateFormat) { + this.dateFormat = dateFormat; + // also set the date format for model (de)serialization with Date properties + this.json.setDateFormat((DateFormat) dateFormat.clone()); + return this; + } + + /** + * Parse the given string into Date object. + * @param str String + * @return Date + */ + public Date parseDate(String str) { + try { + return dateFormat.parse(str); + } catch (java.text.ParseException e) { + throw new RuntimeException(e); + } + } + + /** + * Format the given Date object into string. + * @param date Date + * @return Date in string format + */ + public String formatDate(Date date) { + return dateFormat.format(date); + } + + /** + * Format the given parameter object into string. + * @param param Object + * @return Object in string format + */ + public String parameterToString(Object param) { + if (param == null) { + return ""; + } else if (param instanceof Date) { + return formatDate((Date) param); + } else if (param instanceof Collection) { + StringBuilder b = new StringBuilder(); + for(Object o : (Collection)param) { + if(b.length() > 0) { + b.append(','); + } + b.append(String.valueOf(o)); + } + return b.toString(); + } else { + return String.valueOf(param); + } + } + + /* + * Format to {@code Pair} objects. + * @param collectionFormat Collection format + * @param name Name + * @param value Value + * @return List of pairs + */ + public List parameterToPairs(String collectionFormat, String name, Object value){ + List params = new ArrayList(); + + // preconditions + if (name == null || name.isEmpty() || value == null) return params; + + Collection valueCollection; + if (value instanceof Collection) { + valueCollection = (Collection) value; + } else { + params.add(new Pair(name, parameterToString(value))); + return params; + } + + if (valueCollection.isEmpty()){ + return params; + } + + // get the collection format (default: csv) + String format = (collectionFormat == null || collectionFormat.isEmpty() ? "csv" : collectionFormat); + + // create the params based on the collection format + if ("multi".equals(format)) { + for (Object item : valueCollection) { + params.add(new Pair(name, parameterToString(item))); + } + + return params; + } + + String delimiter = ","; + + if ("csv".equals(format)) { + delimiter = ","; + } else if ("ssv".equals(format)) { + delimiter = " "; + } else if ("tsv".equals(format)) { + delimiter = "\t"; + } else if ("pipes".equals(format)) { + delimiter = "|"; + } + + StringBuilder sb = new StringBuilder() ; + for (Object item : valueCollection) { + sb.append(delimiter); + sb.append(parameterToString(item)); + } + + params.add(new Pair(name, sb.substring(1))); + + return params; + } + + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * application/vnd.company+json + * "* / *" is also default to JSON + * @param mime MIME + * @return True if the MIME type is JSON + */ + public boolean isJsonMime(String mime) { + String jsonMime = "(?i)^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$"; + return mime != null && (mime.matches(jsonMime) || mime.equals("*/*")); + } + + /** + * Select the Accept header's value from the given accepts array: + * if JSON exists in the given array, use it; + * otherwise use all of them (joining into a string) + * + * @param accepts The accepts array to select from + * @return The Accept header to use. If the given array is empty, + * null will be returned (not to set the Accept header explicitly). + */ + public String selectHeaderAccept(String[] accepts) { + if (accepts.length == 0) { + return null; + } + for (String accept : accepts) { + if (isJsonMime(accept)) { + return accept; + } + } + return StringUtil.join(accepts, ","); + } + + /** + * Select the Content-Type header's value from the given array: + * if JSON exists in the given array, use it; + * otherwise use the first one of the array. + * + * @param contentTypes The Content-Type array to select from + * @return The Content-Type header to use. If the given array is empty, + * JSON will be used. + */ + public String selectHeaderContentType(String[] contentTypes) { + if (contentTypes.length == 0) { + return "application/json"; + } + for (String contentType : contentTypes) { + if (isJsonMime(contentType)) { + return contentType; + } + } + return contentTypes[0]; + } + + /** + * Escape the given string to be used as URL query value. + * @param str String + * @return Escaped string + */ + public String escapeString(String str) { + try { + return URLEncoder.encode(str, "utf8").replaceAll("\\+", "%20"); + } catch (UnsupportedEncodingException e) { + return str; + } + } + + /** + * Serialize the given Java object into string entity according the given + * Content-Type (only JSON is supported for now). + * @param obj Object + * @param formParams Form parameters + * @param contentType Context type + * @return Entity + * @throws ApiException API exception + */ + public Entity serialize(Object obj, Map formParams, String contentType) throws ApiException { + Entity entity; + if (contentType.startsWith("multipart/form-data")) { + MultiPart multiPart = new MultiPart(); + for (Entry param: formParams.entrySet()) { + if (param.getValue() instanceof File) { + File file = (File) param.getValue(); + FormDataContentDisposition contentDisp = FormDataContentDisposition.name(param.getKey()) + .fileName(file.getName()).size(file.length()).build(); + multiPart.bodyPart(new FormDataBodyPart(contentDisp, file, MediaType.APPLICATION_OCTET_STREAM_TYPE)); + } else { + FormDataContentDisposition contentDisp = FormDataContentDisposition.name(param.getKey()).build(); + multiPart.bodyPart(new FormDataBodyPart(contentDisp, parameterToString(param.getValue()))); + } + } + entity = Entity.entity(multiPart, MediaType.MULTIPART_FORM_DATA_TYPE); + } else if (contentType.startsWith("application/x-www-form-urlencoded")) { + Form form = new Form(); + for (Entry param: formParams.entrySet()) { + form.param(param.getKey(), parameterToString(param.getValue())); + } + entity = Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE); + } else { + // We let jersey handle the serialization + entity = Entity.entity(obj, contentType); + } + return entity; + } + + /** + * Deserialize response body to Java object according to the Content-Type. + * @param Type + * @param response Response + * @param returnType Return type + * @return Deserialize object + * @throws ApiException API exception + */ + @SuppressWarnings("unchecked") + public T deserialize(Response response, GenericType returnType) throws ApiException { + if (response == null || returnType == null) { + return null; + } + + if ("byte[]".equals(returnType.toString())) { + // Handle binary response (byte array). + return (T) response.readEntity(byte[].class); + } else if (returnType.getRawType() == File.class) { + // Handle file downloading. + T file = (T) downloadFileFromResponse(response); + return file; + } + + String contentType = null; + List contentTypes = response.getHeaders().get("Content-Type"); + if (contentTypes != null && !contentTypes.isEmpty()) + contentType = String.valueOf(contentTypes.get(0)); + if (contentType == null) + throw new ApiException(500, "missing Content-Type in response"); + + return response.readEntity(returnType); + } + + /** + * Download file from the given response. + * @param response Response + * @return File + * @throws ApiException If fail to read file content from response and write to disk + */ + public File downloadFileFromResponse(Response response) throws ApiException { + try { + File file = prepareDownloadFile(response); +{{^supportJava6}} + Files.copy(response.readEntity(InputStream.class), file.toPath(), StandardCopyOption.REPLACE_EXISTING); +{{/supportJava6}} +{{#supportJava6}} + // Java6 falls back to commons.io for file copying + FileUtils.copyToFile(response.readEntity(InputStream.class), file); +{{/supportJava6}} + return file; + } catch (IOException e) { + throw new ApiException(e); + } + } + + public File prepareDownloadFile(Response response) throws IOException { + String filename = null; + String contentDisposition = (String) response.getHeaders().getFirst("Content-Disposition"); + if (contentDisposition != null && !"".equals(contentDisposition)) { + // Get filename from the Content-Disposition header. + Pattern pattern = Pattern.compile("filename=['\"]?([^'\"\\s]+)['\"]?"); + Matcher matcher = pattern.matcher(contentDisposition); + if (matcher.find()) + filename = matcher.group(1); + } + + String prefix; + String suffix = null; + if (filename == null) { + prefix = "download-"; + suffix = ""; + } else { + int pos = filename.lastIndexOf('.'); + if (pos == -1) { + prefix = filename + "-"; + } else { + prefix = filename.substring(0, pos) + "-"; + suffix = filename.substring(pos); + } + // File.createTempFile requires the prefix to be at least three characters long + if (prefix.length() < 3) + prefix = "download-"; + } + + if (tempFolderPath == null) + return File.createTempFile(prefix, suffix); + else + return File.createTempFile(prefix, suffix, new File(tempFolderPath)); + } + + /** + * Invoke API by sending HTTP request with the given options. + * + * @param Type + * @param path The sub-path of the HTTP URL + * @param method The request method, one of "GET", "POST", "PUT", "HEAD" and "DELETE" + * @param queryParams The query parameters + * @param body The request body object + * @param headerParams The header parameters + * @param formParams The form parameters + * @param accept The request's Accept header + * @param contentType The request's Content-Type header + * @param authNames The authentications to apply + * @param returnType The return type into which to deserialize the response + * @return The response body in type of string + * @throws ApiException API exception + */ + public T invokeAPI(String path, String method, List queryParams, Object body, Map headerParams, Map formParams, String accept, String contentType, String[] authNames, GenericType returnType) throws ApiException { + updateParamsForAuth(authNames, queryParams, headerParams); + + // Not using `.target(this.basePath).path(path)` below, + // to support (constant) query string in `path`, e.g. "/posts?draft=1" + WebTarget target = httpClient.target(this.basePath + path); + + if (queryParams != null) { + for (Pair queryParam : queryParams) { + if (queryParam.getValue() != null) { + target = target.queryParam(queryParam.getName(), queryParam.getValue()); + } + } + } + + Invocation.Builder invocationBuilder = target.request(); + + if (accept != null) { + invocationBuilder = invocationBuilder.accept(accept); + } + + for (Entry entry : headerParams.entrySet()) { + String value = entry.getValue(); + if (value != null) { + invocationBuilder = invocationBuilder.header(entry.getKey(), value); + } + } + + for (Entry entry : defaultHeaderMap.entrySet()) { + String key = entry.getKey(); + if (!headerParams.containsKey(key)) { + String value = entry.getValue(); + if (value != null) { + invocationBuilder = invocationBuilder.header(key, value); + } + } + } + + Entity entity = serialize(body, formParams, contentType); + + Response response = null; + + try { + if ("GET".equals(method)) { + response = invocationBuilder.get(); + } else if ("POST".equals(method)) { + response = invocationBuilder.post(entity); + } else if ("PUT".equals(method)) { + response = invocationBuilder.put(entity); + } else if ("DELETE".equals(method)) { + response = invocationBuilder.delete(); + } else if ("PATCH".equals(method)) { + response = invocationBuilder.method("PATCH", entity); + } else if ("HEAD".equals(method)) { + response = invocationBuilder.head(); + } else { + throw new ApiException(500, "unknown method type " + method); + } + + statusCode = response.getStatusInfo().getStatusCode(); + responseHeaders = buildResponseHeaders(response); + + if (response.getStatus() == Status.NO_CONTENT.getStatusCode()) { + return null; + } else if (response.getStatusInfo().getFamily() == Status.Family.SUCCESSFUL) { + if (returnType == null) + return null; + else + return deserialize(response, returnType); + } else { + String message = "error"; + String respBody = null; + if (response.hasEntity()) { + try { + respBody = String.valueOf(response.readEntity(String.class)); + message = respBody; + } catch (RuntimeException e) { + // e.printStackTrace(); + } + } + throw new ApiException( + response.getStatus(), + message, + buildResponseHeaders(response), + respBody); + } + } finally { + try { + response.close(); + } catch (Exception e) { + // it's not critical, since the response object is local in method invokeAPI; that's fine, just continue + } + } + } + + /** + * Build the Client used to make HTTP requests. + * @param debugging Debug setting + * @return Client + */ + protected Client buildHttpClient(boolean debugging) { + final ClientConfig clientConfig = new ClientConfig(); + clientConfig.register(MultiPartFeature.class); + clientConfig.register(json); + clientConfig.register(JacksonFeature.class); + clientConfig.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true); + {{^supportJava6}} + if (debugging) { + clientConfig.register(new LoggingFeature(java.util.logging.Logger.getLogger(LoggingFeature.DEFAULT_LOGGER_NAME), java.util.logging.Level.INFO, LoggingFeature.Verbosity.PAYLOAD_ANY, 1024*50 /* Log payloads up to 50K */)); + clientConfig.property(LoggingFeature.LOGGING_FEATURE_VERBOSITY, LoggingFeature.Verbosity.PAYLOAD_ANY); + // Set logger to ALL + java.util.logging.Logger.getLogger(LoggingFeature.DEFAULT_LOGGER_NAME).setLevel(java.util.logging.Level.ALL); + } + {{/supportJava6}} + performAdditionalClientConfiguration(clientConfig); + return ClientBuilder.newClient(clientConfig); + } + + protected void performAdditionalClientConfiguration(ClientConfig clientConfig) { + // No-op extension point + } + + protected Map> buildResponseHeaders(Response response) { + Map> responseHeaders = new HashMap>(); + for (Entry> entry: response.getHeaders().entrySet()) { + List values = entry.getValue(); + List headers = new ArrayList(); + for (Object o : values) { + headers.add(String.valueOf(o)); + } + responseHeaders.put(entry.getKey(), headers); + } + return responseHeaders; + } + + /** + * Update query and header parameters based on authentication settings. + * + * @param authNames The authentications to apply + */ + protected void updateParamsForAuth(String[] authNames, List queryParams, Map headerParams) { + for (String authName : authNames) { + Authentication auth = authentications.get(authName); + if (auth == null) throw new RuntimeException("Authentication undefined: " + authName); + auth.applyToParams(queryParams, headerParams); + } + } +} diff --git a/mustacheTemplates/libraries/jersey2/JSON.mustache b/mustacheTemplates/libraries/jersey2/JSON.mustache new file mode 100644 index 0000000000..911391a6ba --- /dev/null +++ b/mustacheTemplates/libraries/jersey2/JSON.mustache @@ -0,0 +1,62 @@ +package {{invokerPackage}}; + +{{#threetenbp}} +import org.threeten.bp.*; +{{/threetenbp}} +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.*; +{{#java8}} +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +{{/java8}} +{{#joda}} +import com.fasterxml.jackson.datatype.joda.JodaModule; +{{/joda}} +{{#threetenbp}} +import com.fasterxml.jackson.datatype.threetenbp.ThreeTenModule; +{{/threetenbp}} + +import java.text.DateFormat; + +import javax.ws.rs.ext.ContextResolver; + +{{>generatedAnnotation}} +public class JSON implements ContextResolver { + private ObjectMapper mapper; + + public JSON() { + mapper = new ObjectMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + mapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false); + mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING); + mapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING); + mapper.setDateFormat(new RFC3339DateFormat()); + {{#java8}} + mapper.registerModule(new JavaTimeModule()); + {{/java8}} + {{#joda}} + mapper.registerModule(new JodaModule()); + {{/joda}} + {{#threetenbp}} + ThreeTenModule module = new ThreeTenModule(); + module.addDeserializer(Instant.class, CustomInstantDeserializer.INSTANT); + module.addDeserializer(OffsetDateTime.class, CustomInstantDeserializer.OFFSET_DATE_TIME); + module.addDeserializer(ZonedDateTime.class, CustomInstantDeserializer.ZONED_DATE_TIME); + mapper.registerModule(module); + {{/threetenbp}} + } + + /** + * Set the date format for JSON (de)serialization with Date properties. + * @param dateFormat Date format + */ + public void setDateFormat(DateFormat dateFormat) { + mapper.setDateFormat(dateFormat); + } + + @Override + public ObjectMapper getContext(Class type) { + return mapper; + } +} diff --git a/mustacheTemplates/libraries/jersey2/api.mustache b/mustacheTemplates/libraries/jersey2/api.mustache new file mode 100644 index 0000000000..3f21d85245 --- /dev/null +++ b/mustacheTemplates/libraries/jersey2/api.mustache @@ -0,0 +1,119 @@ +package {{package}}; + +import {{invokerPackage}}.ApiException; +import {{invokerPackage}}.ApiClient; +import {{invokerPackage}}.Configuration; +import {{invokerPackage}}.Pair; + +import javax.ws.rs.core.GenericType; + +{{#imports}}import {{import}}; +{{/imports}} + +{{^fullJavaUtil}} +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +{{/fullJavaUtil}} + +{{>generatedAnnotation}} +{{#operations}} +public class {{classname}} { + private ApiClient {{localVariablePrefix}}apiClient; + + public {{classname}}() { + this(Configuration.getDefaultApiClient()); + } + + public {{classname}}(ApiClient apiClient) { + this.{{localVariablePrefix}}apiClient = apiClient; + } + + public ApiClient getApiClient() { + return {{localVariablePrefix}}apiClient; + } + + public void setApiClient(ApiClient apiClient) { + this.{{localVariablePrefix}}apiClient = apiClient; + } + + {{#operation}} + {{#contents}} + /** + * {{summary}} + * {{notes}} + {{#parameters}} + * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}} + {{/parameters}} + {{#returnType}} + * @return {{returnType}} + {{/returnType}} + * @throws ApiException if fails to make API call + {{#isDeprecated}} + * @deprecated + {{/isDeprecated}} + {{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation + {{/externalDocs}} + */ + {{#isDeprecated}} + @Deprecated + {{/isDeprecated}} + public {{#returnType}}{{{returnType}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{operationId}}({{#parameters}}{{{dataType}}} {{paramName}}{{#has this 'more'}}, {{/has}}{{/parameters}}) throws ApiException { + Object {{localVariablePrefix}}localVarPostBody = {{#bodyParam}}{{paramName}}{{/bodyParam}}{{^bodyParam}}null{{/bodyParam}}; + {{#parameters}} + {{#required}} + // verify the required parameter '{{paramName}}' is set + if ({{paramName}} == null) { + throw new ApiException(400, "Missing the required parameter '{{paramName}}' when calling {{operationId}}"); + } + {{/required}} + {{/parameters}} + // create path and map variables + String {{localVariablePrefix}}localVarPath = "{{{path}}}"{{#pathParams}} + .replaceAll("\\{" + "{{baseName}}" + "\\}", {{localVariablePrefix}}apiClient.escapeString({{{paramName}}}.toString())){{/pathParams}}; + + // query params + {{javaUtilPrefix}}List {{localVariablePrefix}}localVarQueryParams = new {{javaUtilPrefix}}ArrayList(); + {{javaUtilPrefix}}Map {{localVariablePrefix}}localVarHeaderParams = new {{javaUtilPrefix}}HashMap(); + {{javaUtilPrefix}}Map {{localVariablePrefix}}localVarFormParams = new {{javaUtilPrefix}}HashMap(); + + {{#queryParams}} + {{localVariablePrefix}}localVarQueryParams.addAll({{localVariablePrefix}}apiClient.parameterToPairs("{{#collectionFormat}}{{{collectionFormat}}}{{/collectionFormat}}", "{{baseName}}", {{paramName}})); + {{/queryParams}} + + {{#headerParams}} + if ({{paramName}} != null) + {{localVariablePrefix}}localVarHeaderParams.put("{{baseName}}", {{localVariablePrefix}}apiClient.parameterToString({{paramName}})); + {{/headerParams}} + + {{#formParams}} + if ({{paramName}} != null) + {{localVariablePrefix}}localVarFormParams.put("{{baseName}}", {{paramName}}); + {{/formParams}} + + final String[] {{localVariablePrefix}}localVarAccepts = { + {{#produces}}"{{{mediaType}}}"{{#hasMore}}, {{/hasMore}}{{/produces}} + }; + final String {{localVariablePrefix}}localVarAccept = {{localVariablePrefix}}apiClient.selectHeaderAccept({{localVariablePrefix}}localVarAccepts); + + final String[] {{localVariablePrefix}}localVarContentTypes = { + {{#consumes}}"{{{mediaType}}}"{{#hasMore}}, {{/hasMore}}{{/consumes}} + }; + final String {{localVariablePrefix}}localVarContentType = {{localVariablePrefix}}apiClient.selectHeaderContentType({{localVariablePrefix}}localVarContentTypes); + + String[] {{localVariablePrefix}}localVarAuthNames = new String[] { {{#authMethods}}"{{name}}"{{#has this 'more'}}, {{/has}}{{/authMethods}} }; + + {{#returnType}} + GenericType<{{{returnType}}}> {{localVariablePrefix}}localVarReturnType = new GenericType<{{{returnType}}}>() {}; + return {{localVariablePrefix}}apiClient.invokeAPI({{localVariablePrefix}}localVarPath, "{{httpMethod}}", {{localVariablePrefix}}localVarQueryParams, {{localVariablePrefix}}localVarPostBody, {{localVariablePrefix}}localVarHeaderParams, {{localVariablePrefix}}localVarFormParams, {{localVariablePrefix}}localVarAccept, {{localVariablePrefix}}localVarContentType, {{localVariablePrefix}}localVarAuthNames, {{localVariablePrefix}}localVarReturnType); + {{/returnType}}{{^returnType}} + {{localVariablePrefix}}apiClient.invokeAPI({{localVariablePrefix}}localVarPath, "{{httpMethod}}", {{localVariablePrefix}}localVarQueryParams, {{localVariablePrefix}}localVarPostBody, {{localVariablePrefix}}localVarHeaderParams, {{localVariablePrefix}}localVarFormParams, {{localVariablePrefix}}localVarAccept, {{localVariablePrefix}}localVarContentType, {{localVariablePrefix}}localVarAuthNames, null); + {{/returnType}} + } + {{/contents}} + {{/operation}} +} +{{/operations}} diff --git a/mustacheTemplates/libraries/jersey2/build.gradle.mustache b/mustacheTemplates/libraries/jersey2/build.gradle.mustache new file mode 100644 index 0000000000..759a1603bc --- /dev/null +++ b/mustacheTemplates/libraries/jersey2/build.gradle.mustache @@ -0,0 +1,153 @@ +apply plugin: 'idea' +apply plugin: 'eclipse' + +group = '{{groupId}}' +version = '{{artifactVersion}}' + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.3.+' + classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' + } +} + +repositories { + jcenter() +} + + +if(hasProperty('target') && target == 'android') { + + apply plugin: 'com.android.library' + apply plugin: 'com.github.dcendents.android-maven' + + android { + compileSdkVersion 25 + buildToolsVersion '25.0.2' + defaultConfig { + minSdkVersion 14 + targetSdkVersion 25 + } + compileOptions { + {{#java8}} + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + {{/java8}} + {{^java8}} + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 + {{/java8}} + } + + // Rename the aar correctly + libraryVariants.all { variant -> + variant.outputs.each { output -> + def outputFile = output.outputFile + if (outputFile != null && outputFile.name.endsWith('.aar')) { + def fileName = "${project.name}-${variant.baseName}-${version}.aar" + output.outputFile = new File(outputFile.parent, fileName) + } + } + } + + dependencies { + provided 'javax.annotation:jsr250-api:1.0' + } + } + + afterEvaluate { + android.libraryVariants.all { variant -> + def task = project.tasks.create "jar${variant.name.capitalize()}", Jar + task.description = "Create jar artifact for ${variant.name}" + task.dependsOn variant.javaCompile + task.from variant.javaCompile.destinationDir + task.destinationDir = project.file("${project.buildDir}/outputs/jar") + task.archiveName = "${project.name}-${variant.baseName}-${version}.jar" + artifacts.add('archives', task); + } + } + + task sourcesJar(type: Jar) { + from android.sourceSets.main.java.srcDirs + classifier = 'sources' + } + + artifacts { + archives sourcesJar + } + +} else { + + apply plugin: 'java' + apply plugin: 'maven' + {{#java8}} + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + {{/java8}} + {{^java8}} + sourceCompatibility = JavaVersion.VERSION_1_7 + targetCompatibility = JavaVersion.VERSION_1_7 + {{/java8}} + + install { + repositories.mavenInstaller { + pom.artifactId = '{{artifactId}}' + } + } + + task execute(type:JavaExec) { + main = System.getProperty('mainClass') + classpath = sourceSets.main.runtimeClasspath + } +} + +ext { + {{#useOas2}} + swagger_annotations_version = "1.5.15" + {{/useOas2}} + {{^useOas2}} + swagger_annotations_version = "2.0.0" + {{/useOas2}} + jackson_version = "2.10.1" + jersey_version = "2.26" + {{#supportJava6}} + commons_io_version=2.5 + commons_lang3_version=3.6 + {{/supportJava6}} + junit_version = "4.12" +} + +dependencies { + {{#useOas2}} + compile "io.swagger:swagger-annotations:$swagger_annotations_version" + {{/useOas2}} + {{^useOas2}} + compile "io.swagger.core.v3:swagger-annotations:$swagger_annotations_version" + {{/useOas2}} + compile "org.glassfish.jersey.core:jersey-client:$jersey_version" + compile "org.glassfish.jersey.media:jersey-media-multipart:$jersey_version" + compile "org.glassfish.jersey.media:jersey-media-json-jackson:$jersey_version" + compile "com.fasterxml.jackson.core:jackson-core:$jackson_version" + compile "com.fasterxml.jackson.core:jackson-annotations:$jackson_version" + compile "com.fasterxml.jackson.core:jackson-databind:$jackson_version" + {{#joda}} + compile "com.fasterxml.jackson.datatype:jackson-datatype-joda:$jackson_version" + {{/joda}} + {{#java8}} + compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jackson_version" + {{/java8}} + {{#supportJava6}} + compile "commons-io:commons-io:$commons_io_version" + compile "org.apache.commons:commons-lang3:$commons_lang3_version" + {{/supportJava6}} + {{#threetenbp}} + compile "com.github.joschi.jackson:jackson-datatype-threetenbp:$jackson_version" + {{/threetenbp}} + {{^java8}} + compile "com.brsanthu:migbase64:2.2" + {{/java8}} + testCompile "junit:junit:$junit_version" +} diff --git a/mustacheTemplates/libraries/jersey2/build.sbt.mustache b/mustacheTemplates/libraries/jersey2/build.sbt.mustache new file mode 100644 index 0000000000..5c9a87d31a --- /dev/null +++ b/mustacheTemplates/libraries/jersey2/build.sbt.mustache @@ -0,0 +1,44 @@ +lazy val root = (project in file(".")). + settings( + organization := "{{groupId}}", + name := "{{artifactId}}", + version := "{{artifactVersion}}", + scalaVersion := "2.11.4", + scalacOptions ++= Seq("-feature"), + javacOptions in compile ++= Seq("-Xlint:deprecation"), + publishArtifact in (Compile, packageDoc) := false, + resolvers += Resolver.mavenLocal, + libraryDependencies ++= Seq( + {{#useOas2}} + "io.swagger" % "swagger-annotations" % "1.5.15", + {{/useOas2}} + {{^useOas2}} + "io.swagger.core.v3" % "swagger-annotations" % "2.0.0", + {{/useOas2}} + "org.glassfish.jersey.core" % "jersey-client" % "2.29.1", + "org.glassfish.jersey.media" % "jersey-media-multipart" % "2.29.1", + "org.glassfish.jersey.media" % "jersey-media-json-jackson" % "2.29.1", + "org.glassfish.jersey.inject" % "jersey-hk2" % "2.29.1", + "com.fasterxml.jackson.core" % "jackson-core" % "{{^threetenbp}}2.10.1{{/threetenbp}}{{#threetenbp}}2.6.4{{/threetenbp}}" % "compile", + "com.fasterxml.jackson.core" % "jackson-annotations" % "{{^threetenbp}}2.10.1{{/threetenbp}}{{#threetenbp}}2.6.4{{/threetenbp}}" % "compile", + "com.fasterxml.jackson.core" % "jackson-databind" % "{{^threetenbp}}2.10.1{{/threetenbp}}{{#threetenbp}}2.6.4{{/threetenbp}}" % "compile", + {{#joda}} + "com.fasterxml.jackson.datatype" % "jackson-datatype-joda" % "2.10.1" % "compile", + {{/joda}} + {{#java8}} + "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.10.1" % "compile", + {{/java8}} + {{#threetenbp}} + "com.github.joschi.jackson" % "jackson-datatype-threetenbp" % "2.6.4" % "compile", + {{/threetenbp}} + {{^java8}} + "com.brsanthu" % "migbase64" % "2.2", + {{/java8}} + {{#supportJava6}} + "org.apache.commons" % "commons-lang3" % "3.6", + "commons-io" % "commons-io" % "2.5", + {{/supportJava6}} + "junit" % "junit" % "4.12" % "test", + "com.novocode" % "junit-interface" % "0.10" % "test" + ) + ) diff --git a/mustacheTemplates/libraries/jersey2/pom.mustache b/mustacheTemplates/libraries/jersey2/pom.mustache new file mode 100644 index 0000000000..5fe4033d63 --- /dev/null +++ b/mustacheTemplates/libraries/jersey2/pom.mustache @@ -0,0 +1,326 @@ + + 4.0.0 + {{groupId}} + {{artifactId}} + jar + {{artifactId}} + {{artifactVersion}} + {{artifactUrl}} + {{artifactDescription}} + + {{scmConnection}} + {{scmDeveloperConnection}} + {{scmUrl}} + + + 2.2.0 + + + + + {{licenseName}} + {{licenseUrl}} + repo + + + + + + {{developerName}} + {{developerEmail}} + {{developerOrganization}} + {{developerOrganizationUrl}} + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.12 + + + + loggerPath + conf/log4j.properties + + + -Xms512m -Xmx1500m + methods + pertest + + + + maven-dependency-plugin + + + package + + copy-dependencies + + + ${project.build.directory}/lib + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.6 + + + + jar + test-jar + + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add_sources + generate-sources + + add-source + + + + src/main/java + + + + + add_test_sources + generate-test-sources + + add-test-source + + + + src/test/java + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.1 + + {{#java8}} + 1.8 + 1.8 + {{/java8}} + {{^java8}} + 1.7 + 1.7 + {{/java8}} + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + attach-sources + + jar-no-fork + + + + + + + + + + sign-artifacts + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + sign-artifacts + verify + + sign + + + + + + + + + + + {{#useOas2}} + + io.swagger + swagger-annotations + ${swagger-core-version} + + {{/useOas2}} + {{^useOas2}} + + io.swagger.core.v3 + swagger-annotations + ${swagger-core-version} + + {{/useOas2}} + + + + org.glassfish.jersey.core + jersey-client + ${jersey-version} + + + org.glassfish.jersey.media + jersey-media-multipart + ${jersey-version} + + + org.glassfish.jersey.media + jersey-media-json-jackson + ${jersey-version} + + + org.glassfish.jersey.inject + jersey-hk2 + ${jersey-version} + + + + + com.fasterxml.jackson.core + jackson-core + ${jackson-version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson-version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson-version} + + {{#withXml}} + + + + org.glassfish.jersey.media + jersey-media-jaxb + ${jersey-version} + + + {{/withXml}} + {{#joda}} + + com.fasterxml.jackson.datatype + jackson-datatype-joda + ${jackson-version} + + {{/joda}} + {{#java8}} + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson-version} + + {{/java8}} + {{#threetenbp}} + + com.github.joschi.jackson + jackson-datatype-threetenbp + ${jackson-version} + + {{/threetenbp}} + {{^java8}} + + + com.brsanthu + migbase64 + 2.2 + + {{/java8}} + {{#supportJava6}} + + org.apache.commons + commons-lang3 + ${commons_lang3_version} + + + commons-io + commons-io + ${commons_io_version} + + {{/supportJava6}} + {{#useBeanValidation}} + + + javax.validation + validation-api + 1.1.0.Final + provided + + {{/useBeanValidation}} + + + junit + junit + ${junit-version} + test + + + + {{#useOas2}} + 1.5.15 + {{/useOas2}} + {{^useOas2}} + 2.0.0 + {{/useOas2}} + {{^supportJava6}} + 2.29.1 + {{/supportJava6}} + {{#supportJava6}} + 2.6 + 2.5 + 3.6 + {{/supportJava6}} + {{^threetenbp}}2.10.1{{/threetenbp}}{{#threetenbp}}2.6.4{{/threetenbp}} + 1.0.0 + 4.12 + + diff --git a/mustacheTemplates/libraries/okhttp-gson/ApiCallback.mustache b/mustacheTemplates/libraries/okhttp-gson/ApiCallback.mustache new file mode 100644 index 0000000000..4cc99a76ec --- /dev/null +++ b/mustacheTemplates/libraries/okhttp-gson/ApiCallback.mustache @@ -0,0 +1,51 @@ +{{>licenseInfo}} + +package {{invokerPackage}}; + +import java.io.IOException; + +import java.util.Map; +import java.util.List; + +/** + * Callback for asynchronous API call. + * + * @param The return type + */ +public interface ApiCallback { + /** + * This is called when the API call fails. + * + * @param e The exception causing the failure + * @param statusCode Status code of the response if available, otherwise it would be 0 + * @param responseHeaders Headers of the response if available, otherwise it would be null + */ + void onFailure(ApiException e, int statusCode, Map> responseHeaders); + + /** + * This is called when the API call succeeded. + * + * @param result The result deserialized from response + * @param statusCode Status code of the response + * @param responseHeaders Headers of the response + */ + void onSuccess(T result, int statusCode, Map> responseHeaders); + + /** + * This is called when the API upload processing. + * + * @param bytesWritten bytes Written + * @param contentLength content length of request body + * @param done write end + */ + void onUploadProgress(long bytesWritten, long contentLength, boolean done); + + /** + * This is called when the API downlond processing. + * + * @param bytesRead bytes Read + * @param contentLength content lenngth of the response + * @param done Read end + */ + void onDownloadProgress(long bytesRead, long contentLength, boolean done); +} diff --git a/mustacheTemplates/libraries/okhttp-gson/ApiClient.mustache b/mustacheTemplates/libraries/okhttp-gson/ApiClient.mustache new file mode 100644 index 0000000000..ae1b32ecdb --- /dev/null +++ b/mustacheTemplates/libraries/okhttp-gson/ApiClient.mustache @@ -0,0 +1,1229 @@ +{{>licenseInfo}} + +package {{invokerPackage}}; + +import com.squareup.okhttp.*; +import com.squareup.okhttp.internal.http.HttpMethod; +import com.squareup.okhttp.logging.HttpLoggingInterceptor; +import com.squareup.okhttp.logging.HttpLoggingInterceptor.Level; +import okio.BufferedSink; +import okio.Okio; +{{#joda}} +import org.joda.time.DateTime; +import org.joda.time.LocalDate; +import org.joda.time.format.DateTimeFormatter; +{{/joda}} +{{#threetenbp}} +import org.threeten.bp.LocalDate; +import org.threeten.bp.OffsetDateTime; +import org.threeten.bp.format.DateTimeFormatter; +{{/threetenbp}} + +import javax.net.ssl.*; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Type; +import java.net.URLConnection; +import java.net.URLEncoder; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.SecureRandom; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.text.DateFormat; +{{#java8}} +import java.time.LocalDate; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +{{/java8}} +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import {{invokerPackage}}.auth.Authentication; +import {{invokerPackage}}.auth.HttpBasicAuth; +import {{invokerPackage}}.auth.ApiKeyAuth; +import {{invokerPackage}}.auth.OAuth; + +public class ApiClient { + + private String basePath = "{{{basePath}}}"; + private boolean debugging = false; + private Map defaultHeaderMap = new HashMap(); + private String tempFolderPath = null; + + private Map authentications; + + private DateFormat dateFormat; + private DateFormat datetimeFormat; + private boolean lenientDatetimeFormat; + private int dateLength; + + private InputStream sslCaCert; + private boolean verifyingSsl; + private KeyManager[] keyManagers; + + private OkHttpClient httpClient; + private JSON json; + + private HttpLoggingInterceptor loggingInterceptor; + + /* + * Constructor for ApiClient + */ + public ApiClient() { + httpClient = new OkHttpClient(); + + {{#useGzipFeature}} + // Enable gzip request compression + httpClient.interceptors().add(new GzipRequestInterceptor()); + {{/useGzipFeature}} + + verifyingSsl = true; + + json = new JSON(); + + // Set default User-Agent. + setUserAgent("{{#httpUserAgent}}{{{.}}}{{/httpUserAgent}}{{^httpUserAgent}}Swagger-Codegen/{{{artifactVersion}}}/java{{/httpUserAgent}}"); + + // Setup authentications (key: authentication name, value: authentication). + authentications = new HashMap();{{#authMethods}}{{#is this 'basic'}} + authentications.put("{{name}}", new HttpBasicAuth());{{/is}}{{#is this 'api-key'}} + authentications.put("{{name}}", new ApiKeyAuth({{#is this 'key-in-header'}}"header"{{/is}}{{#isNot this 'key-in-header'}}"query"{{/isNot}}, "{{keyParamName}}"));{{/is}}{{#is this 'oauth'}} + authentications.put("{{name}}", new OAuth());{{/is}}{{#is this 'bearer'}} + authentications.put("{{name}}", new OAuth());{{/is}}{{/authMethods}} + // Prevent the authentications from being modified. + authentications = Collections.unmodifiableMap(authentications); + } + + /** + * Get base path + * + * @return Baes path + */ + public String getBasePath() { + return basePath; + } + + /** + * Set base path + * + * @param basePath Base path of the URL (e.g {{{basePath}}} + * @return An instance of OkHttpClient + */ + public ApiClient setBasePath(String basePath) { + this.basePath = basePath; + return this; + } + + /** + * Get HTTP client + * + * @return An instance of OkHttpClient + */ + public OkHttpClient getHttpClient() { + return httpClient; + } + + /** + * Set HTTP client + * + * @param httpClient An instance of OkHttpClient + * @return Api Client + */ + public ApiClient setHttpClient(OkHttpClient httpClient) { + this.httpClient = httpClient; + return this; + } + + /** + * Get JSON + * + * @return JSON object + */ + public JSON getJSON() { + return json; + } + + /** + * Set JSON + * + * @param json JSON object + * @return Api client + */ + public ApiClient setJSON(JSON json) { + this.json = json; + return this; + } + + /** + * True if isVerifyingSsl flag is on + * + * @return True if isVerifySsl flag is on + */ + public boolean isVerifyingSsl() { + return verifyingSsl; + } + + /** + * Configure whether to verify certificate and hostname when making https requests. + * Default to true. + * NOTE: Do NOT set to false in production code, otherwise you would face multiple types of cryptographic attacks. + * + * @param verifyingSsl True to verify TLS/SSL connection + * @return ApiClient + */ + public ApiClient setVerifyingSsl(boolean verifyingSsl) { + this.verifyingSsl = verifyingSsl; + applySslSettings(); + return this; + } + + /** + * Get SSL CA cert. + * + * @return Input stream to the SSL CA cert + */ + public InputStream getSslCaCert() { + return sslCaCert; + } + + /** + * Configure the CA certificate to be trusted when making https requests. + * Use null to reset to default. + * + * @param sslCaCert input stream for SSL CA cert + * @return ApiClient + */ + public ApiClient setSslCaCert(InputStream sslCaCert) { + this.sslCaCert = sslCaCert; + applySslSettings(); + return this; + } + + public KeyManager[] getKeyManagers() { + return keyManagers; + } + + /** + * Configure client keys to use for authorization in an SSL session. + * Use null to reset to default. + * + * @param managers The KeyManagers to use + * @return ApiClient + */ + public ApiClient setKeyManagers(KeyManager[] managers) { + this.keyManagers = managers; + applySslSettings(); + return this; + } + + public DateFormat getDateFormat() { + return dateFormat; + } + + public ApiClient setDateFormat(DateFormat dateFormat) { + this.json.setDateFormat(dateFormat); + return this; + } + + public ApiClient setSqlDateFormat(DateFormat dateFormat) { + this.json.setSqlDateFormat(dateFormat); + return this; + } + + {{#joda}} + public ApiClient setDateTimeFormat(DateTimeFormatter dateFormat) { + this.json.setDateTimeFormat(dateFormat); + return this; + } + + public ApiClient setLocalDateFormat(DateTimeFormatter dateFormat) { + this.json.setLocalDateFormat(dateFormat); + return this; + } + + {{/joda}} + {{#jsr310}} + public ApiClient setOffsetDateTimeFormat(DateTimeFormatter dateFormat) { + this.json.setOffsetDateTimeFormat(dateFormat); + return this; + } + + public ApiClient setLocalDateFormat(DateTimeFormatter dateFormat) { + this.json.setLocalDateFormat(dateFormat); + return this; + } + + {{/jsr310}} + public ApiClient setLenientOnJson(boolean lenientOnJson) { + this.json.setLenientOnJson(lenientOnJson); + return this; + } + + /** + * Get authentications (key: authentication name, value: authentication). + * + * @return Map of authentication objects + */ + public Map getAuthentications() { + return authentications; + } + + /** + * Get authentication for the given name. + * + * @param authName The authentication name + * @return The authentication, null if not found + */ + public Authentication getAuthentication(String authName) { + return authentications.get(authName); + } + + /** + * Helper method to set username for the first HTTP basic authentication. + * + * @param username Username + */ + public void setUsername(String username) { + for (Authentication auth : authentications.values()) { + if (auth instanceof HttpBasicAuth) { + ((HttpBasicAuth) auth).setUsername(username); + return; + } + } + throw new RuntimeException("No HTTP basic authentication configured!"); + } + + /** + * Helper method to set password for the first HTTP basic authentication. + * + * @param password Password + */ + public void setPassword(String password) { + for (Authentication auth : authentications.values()) { + if (auth instanceof HttpBasicAuth) { + ((HttpBasicAuth) auth).setPassword(password); + return; + } + } + throw new RuntimeException("No HTTP basic authentication configured!"); + } + + /** + * Helper method to set API key value for the first API key authentication. + * + * @param apiKey API key + */ + public void setApiKey(String apiKey) { + for (Authentication auth : authentications.values()) { + if (auth instanceof ApiKeyAuth) { + ((ApiKeyAuth) auth).setApiKey(apiKey); + return; + } + } + throw new RuntimeException("No API key authentication configured!"); + } + + /** + * Helper method to set API key prefix for the first API key authentication. + * + * @param apiKeyPrefix API key prefix + */ + public void setApiKeyPrefix(String apiKeyPrefix) { + for (Authentication auth : authentications.values()) { + if (auth instanceof ApiKeyAuth) { + ((ApiKeyAuth) auth).setApiKeyPrefix(apiKeyPrefix); + return; + } + } + throw new RuntimeException("No API key authentication configured!"); + } + + /** + * Helper method to set access token for the first OAuth2 authentication. + * + * @param accessToken Access token + */ + public void setAccessToken(String accessToken) { + for (Authentication auth : authentications.values()) { + if (auth instanceof OAuth) { + ((OAuth) auth).setAccessToken(accessToken); + return; + } + } + throw new RuntimeException("No OAuth2 authentication configured!"); + } + + /** + * Set the User-Agent header's value (by adding to the default header map). + * + * @param userAgent HTTP request's user agent + * @return ApiClient + */ + public ApiClient setUserAgent(String userAgent) { + addDefaultHeader("User-Agent", userAgent); + return this; + } + + /** + * Add a default header. + * + * @param key The header's key + * @param value The header's value + * @return ApiClient + */ + public ApiClient addDefaultHeader(String key, String value) { + defaultHeaderMap.put(key, value); + return this; + } + + /** + * Check that whether debugging is enabled for this API client. + * + * @return True if debugging is enabled, false otherwise. + */ + public boolean isDebugging() { + return debugging; + } + + /** + * Enable/disable debugging for this API client. + * + * @param debugging To enable (true) or disable (false) debugging + * @return ApiClient + */ + public ApiClient setDebugging(boolean debugging) { + if (debugging != this.debugging) { + if (debugging) { + loggingInterceptor = new HttpLoggingInterceptor(); + loggingInterceptor.setLevel(Level.BODY); + httpClient.interceptors().add(loggingInterceptor); + } else { + httpClient.interceptors().remove(loggingInterceptor); + loggingInterceptor = null; + } + } + this.debugging = debugging; + return this; + } + + /** + * The path of temporary folder used to store downloaded files from endpoints + * with file response. The default value is null, i.e. using + * the system's default tempopary folder. + * + * @see createTempFile + * @return Temporary folder path + */ + public String getTempFolderPath() { + return tempFolderPath; + } + + /** + * Set the temporary folder path (for downloading files) + * + * @param tempFolderPath Temporary folder path + * @return ApiClient + */ + public ApiClient setTempFolderPath(String tempFolderPath) { + this.tempFolderPath = tempFolderPath; + return this; + } + + /** + * Get connection timeout (in milliseconds). + * + * @return Timeout in milliseconds + */ + public int getConnectTimeout() { + return httpClient.getConnectTimeout(); + } + + /** + * Sets the connect timeout (in milliseconds). + * A value of 0 means no timeout, otherwise values must be between 1 and + * + * @param connectionTimeout connection timeout in milliseconds + * @return Api client + */ + public ApiClient setConnectTimeout(int connectionTimeout) { + httpClient.setConnectTimeout(connectionTimeout, TimeUnit.MILLISECONDS); + return this; + } + + /** + * Get read timeout (in milliseconds). + * + * @return Timeout in milliseconds + */ + public int getReadTimeout() { + return httpClient.getReadTimeout(); + } + + /** + * Sets the read timeout (in milliseconds). + * A value of 0 means no timeout, otherwise values must be between 1 and + * {@link Integer#MAX_VALUE}. + * + * @param readTimeout read timeout in milliseconds + * @return Api client + */ + public ApiClient setReadTimeout(int readTimeout) { + httpClient.setReadTimeout(readTimeout, TimeUnit.MILLISECONDS); + return this; + } + + /** + * Get write timeout (in milliseconds). + * + * @return Timeout in milliseconds + */ + public int getWriteTimeout() { + return httpClient.getWriteTimeout(); + } + + /** + * Sets the write timeout (in milliseconds). + * A value of 0 means no timeout, otherwise values must be between 1 and + * {@link Integer#MAX_VALUE}. + * + * @param writeTimeout connection timeout in milliseconds + * @return Api client + */ + public ApiClient setWriteTimeout(int writeTimeout) { + httpClient.setWriteTimeout(writeTimeout, TimeUnit.MILLISECONDS); + return this; + } + + /** + * Format the given parameter object into string. + * + * @param param Parameter + * @return String representation of the parameter + */ + public String parameterToString(Object param) { + if (param == null) { + return ""; + } else if (param instanceof Date {{#joda}}|| param instanceof DateTime || param instanceof LocalDate{{/joda}}{{#jsr310}}|| param instanceof OffsetDateTime || param instanceof LocalDate{{/jsr310}}) { + //Serialize to json string and remove the " enclosing characters + String jsonStr = json.serialize(param); + return jsonStr.substring(1, jsonStr.length() - 1); + } else if (param instanceof Collection) { + StringBuilder b = new StringBuilder(); + for (Object o : (Collection)param) { + if (b.length() > 0) { + b.append(","); + } + b.append(String.valueOf(o)); + } + return b.toString(); + } else { + return String.valueOf(param); + } + } + + /** + * Formats the specified query parameter to a list containing a single {@code Pair} object. + * + * Note that {@code value} must not be a collection. + * + * @param name The name of the parameter. + * @param value The value of the parameter. + * @return A list containing a single {@code Pair} object. + */ + public List parameterToPair(String name, Object value) { + List params = new ArrayList(); + + // preconditions + if (name == null || name.isEmpty() || value == null || value instanceof Collection) return params; + + params.add(new Pair(name, parameterToString(value))); + return params; + } + + /** + * Formats the specified collection query parameters to a list of {@code Pair} objects. + * + * Note that the values of each of the returned Pair objects are percent-encoded. + * + * @param collectionFormat The collection format of the parameter. + * @param name The name of the parameter. + * @param value The value of the parameter. + * @return A list of {@code Pair} objects. + */ + public List parameterToPairs(String collectionFormat, String name, Collection value) { + List params = new ArrayList(); + + // preconditions + if (name == null || name.isEmpty() || value == null || value.isEmpty()) { + return params; + } + + // create the params based on the collection format + if ("multi".equals(collectionFormat)) { + for (Object item : value) { + params.add(new Pair(name, escapeString(parameterToString(item)))); + } + return params; + } + + // collectionFormat is assumed to be "csv" by default + String delimiter = ","; + + // escape all delimiters except commas, which are URI reserved + // characters + if ("ssv".equals(collectionFormat)) { + delimiter = escapeString(" "); + } else if ("tsv".equals(collectionFormat)) { + delimiter = escapeString("\t"); + } else if ("pipes".equals(collectionFormat)) { + delimiter = escapeString("|"); + } + + StringBuilder sb = new StringBuilder() ; + for (Object item : value) { + sb.append(delimiter); + sb.append(escapeString(parameterToString(item))); + } + + params.add(new Pair(name, sb.substring(delimiter.length()))); + + return params; + } + + /** + * Sanitize filename by removing path. + * e.g. ../../sun.gif becomes sun.gif + * + * @param filename The filename to be sanitized + * @return The sanitized filename + */ + public String sanitizeFilename(String filename) { + return filename.replaceAll(".*[/\\\\]", ""); + } + + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * application/vnd.company+json + * "* / *" is also default to JSON + * @param mime MIME (Multipurpose Internet Mail Extensions) + * @return True if the given MIME is JSON, false otherwise. + */ + public boolean isJsonMime(String mime) { + String jsonMime = "(?i)^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$"; + return mime != null && (mime.matches(jsonMime) || mime.equals("*/*")); + } + + /** + * Select the Accept header's value from the given accepts array: + * if JSON exists in the given array, use it; + * otherwise use all of them (joining into a string) + * + * @param accepts The accepts array to select from + * @return The Accept header to use. If the given array is empty, + * null will be returned (not to set the Accept header explicitly). + */ + public String selectHeaderAccept(String[] accepts) { + if (accepts.length == 0) { + return null; + } + for (String accept : accepts) { + if (isJsonMime(accept)) { + return accept; + } + } + return StringUtil.join(accepts, ","); + } + + /** + * Select the Content-Type header's value from the given array: + * if JSON exists in the given array, use it; + * otherwise use the first one of the array. + * + * @param contentTypes The Content-Type array to select from + * @return The Content-Type header to use. If the given array is empty, + * or matches "any", JSON will be used. + */ + public String selectHeaderContentType(String[] contentTypes) { + if (contentTypes.length == 0 || contentTypes[0].equals("*/*")) { + return "application/json"; + } + for (String contentType : contentTypes) { + if (isJsonMime(contentType)) { + return contentType; + } + } + return contentTypes[0]; + } + + /** + * Escape the given string to be used as URL query value. + * + * @param str String to be escaped + * @return Escaped string + */ + public String escapeString(String str) { + try { + return URLEncoder.encode(str, "utf8").replaceAll("\\+", "%20"); + } catch (UnsupportedEncodingException e) { + return str; + } + } + + /** + * Deserialize response body to Java object, according to the return type and + * the Content-Type response header. + * + * @param Type + * @param response HTTP response + * @param returnType The type of the Java object + * @return The deserialized Java object + * @throws ApiException If fail to deserialize response body, i.e. cannot read response body + * or the Content-Type of the response is not supported. + */ + @SuppressWarnings("unchecked") + public T deserialize(Response response, Type returnType) throws ApiException { + if (response == null || returnType == null) { + return null; + } + + if ("byte[]".equals(returnType.toString())) { + // Handle binary response (byte array). + try { + return (T) response.body().bytes(); + } catch (IOException e) { + throw new ApiException(e); + } + } else if (returnType.equals(File.class)) { + // Handle file downloading. + return (T) downloadFileFromResponse(response); + } + + String respBody; + try { + if (response.body() != null) + respBody = response.body().string(); + else + respBody = null; + } catch (IOException e) { + throw new ApiException(e); + } + + if (respBody == null || "".equals(respBody)) { + return null; + } + + String contentType = response.headers().get("Content-Type"); + if (contentType == null) { + // ensuring a default content type + contentType = "application/json"; + } + if (isJsonMime(contentType)) { + return json.deserialize(respBody, returnType); + } else if (returnType.equals(String.class)) { + // Expecting string, return the raw response body. + return (T) respBody; + } else { + throw new ApiException( + "Content type \"" + contentType + "\" is not supported for type: " + returnType, + response.code(), + response.headers().toMultimap(), + respBody); + } + } + + /** + * Serialize the given Java object into request body according to the object's + * class and the request Content-Type. + * + * @param obj The Java object + * @param contentType The request Content-Type + * @return The serialized request body + * @throws ApiException If fail to serialize the given object + */ + public RequestBody serialize(Object obj, String contentType) throws ApiException { + if (obj instanceof byte[]) { + // Binary (byte array) body parameter support. + return RequestBody.create(MediaType.parse(contentType), (byte[]) obj); + } else if (obj instanceof File) { + // File body parameter support. + return RequestBody.create(MediaType.parse(contentType), (File) obj); + } else if (isJsonMime(contentType)) { + String content; + if (obj != null) { + content = json.serialize(obj); + } else { + content = null; + } + return RequestBody.create(MediaType.parse(contentType), content); + } else if("text/plain".equalsIgnoreCase(contentType)) { + return RequestBody.create(MediaType.parse(contentType), Objects.toString(obj)); + } else { + throw new ApiException("Content type \"" + contentType + "\" is not supported"); + } + } + + /** + * Download file from the given response. + * + * @param response An instance of the Response object + * @throws ApiException If fail to read file content from response and write to disk + * @return Downloaded file + */ + public File downloadFileFromResponse(Response response) throws ApiException { + try { + File file = prepareDownloadFile(response); + BufferedSink sink = Okio.buffer(Okio.sink(file)); + sink.writeAll(response.body().source()); + sink.close(); + return file; + } catch (IOException e) { + throw new ApiException(e); + } + } + + /** + * Prepare file for download + * + * @param response An instance of the Response object + * @throws IOException If fail to prepare file for download + * @return Prepared file for the download + */ + public File prepareDownloadFile(Response response) throws IOException { + String filename = null; + String contentDisposition = response.header("Content-Disposition"); + if (contentDisposition != null && !"".equals(contentDisposition)) { + // Get filename from the Content-Disposition header. + Pattern pattern = Pattern.compile("filename=['\"]?([^'\"\\s]+)['\"]?"); + Matcher matcher = pattern.matcher(contentDisposition); + if (matcher.find()) { + filename = sanitizeFilename(matcher.group(1)); + } + } + + String prefix = null; + String suffix = null; + if (filename == null) { + prefix = "download-"; + suffix = ""; + } else { + int pos = filename.lastIndexOf("."); + if (pos == -1) { + prefix = filename + "-"; + } else { + prefix = filename.substring(0, pos) + "-"; + suffix = filename.substring(pos); + } + // File.createTempFile requires the prefix to be at least three characters long + if (prefix.length() < 3) + prefix = "download-"; + } + + if (tempFolderPath == null) + return File.createTempFile(prefix, suffix); + else + return File.createTempFile(prefix, suffix, new File(tempFolderPath)); + } + + /** + * {@link #execute(Call, Type)} + * + * @param Type + * @param call An instance of the Call object + * @throws ApiException If fail to execute the call + * @return ApiResponse<T> + */ + public ApiResponse execute(Call call) throws ApiException { + return execute(call, null); + } + + /** + * Execute HTTP call and deserialize the HTTP response body into the given return type. + * + * @param returnType The return type used to deserialize HTTP response body + * @param The return type corresponding to (same with) returnType + * @param call Call + * @return ApiResponse object containing response status, headers and + * data, which is a Java object deserialized from response body and would be null + * when returnType is null. + * @throws ApiException If fail to execute the call + */ + public ApiResponse execute(Call call, Type returnType) throws ApiException { + try { + Response response = call.execute(); + T data = handleResponse(response, returnType); + return new ApiResponse(response.code(), response.headers().toMultimap(), data); + } catch (IOException e) { + throw new ApiException(e); + } + } + + /** + * {@link #executeAsync(Call, Type, ApiCallback)} + * + * @param Type + * @param call An instance of the Call object + * @param callback ApiCallback<T> + */ + public void executeAsync(Call call, ApiCallback callback) { + executeAsync(call, null, callback); + } + + /** + * Execute HTTP call asynchronously. + * + * @see #execute(Call, Type) + * @param Type + * @param call The callback to be executed when the API call finishes + * @param returnType Return type + * @param callback ApiCallback + */ + @SuppressWarnings("unchecked") + public void executeAsync(Call call, final Type returnType, final ApiCallback callback) { + call.enqueue(new Callback() { + @Override + public void onFailure(Request request, IOException e) { + callback.onFailure(new ApiException(e), 0, null); + } + + @Override + public void onResponse(Response response) throws IOException { + T result; + try { + result = (T) handleResponse(response, returnType); + } catch (ApiException e) { + callback.onFailure(e, response.code(), response.headers().toMultimap()); + return; + } + callback.onSuccess(result, response.code(), response.headers().toMultimap()); + } + }); + } + + /** + * Handle the given response, return the deserialized object when the response is successful. + * + * @param Type + * @param response Response + * @param returnType Return type + * @throws ApiException If the response has a unsuccessful status code or + * fail to deserialize the response body + * @return Type + */ + public T handleResponse(Response response, Type returnType) throws ApiException { + if (response.isSuccessful()) { + if (returnType == null || response.code() == 204) { + // returning null if the returnType is not defined, + // or the status code is 204 (No Content) + if (response.body() != null) { + try { + response.body().close(); + } catch (IOException e) { + throw new ApiException(response.message(), e, response.code(), response.headers().toMultimap()); + } + } + return null; + } else { + return deserialize(response, returnType); + } + } else { + String respBody = null; + if (response.body() != null) { + try { + respBody = response.body().string(); + } catch (IOException e) { + throw new ApiException(response.message(), e, response.code(), response.headers().toMultimap()); + } + } + throw new ApiException(response.message(), response.code(), response.headers().toMultimap(), respBody); + } + } + + /** + * Build HTTP call with the given options. + * + * @param path The sub-path of the HTTP URL + * @param method The request method, one of "GET", "HEAD", "OPTIONS", "POST", "PUT", "PATCH" and "DELETE" + * @param queryParams The query parameters + * @param collectionQueryParams The collection query parameters + * @param body The request body object + * @param headerParams The header parameters + * @param formParams The form parameters + * @param authNames The authentications to apply + * @param progressRequestListener Progress request listener + * @return The HTTP call + * @throws ApiException If fail to serialize the request body object + */ + public Call buildCall(String path, String method, List queryParams, List collectionQueryParams, Object body, Map headerParams, Map formParams, String[] authNames, ProgressRequestBody.ProgressRequestListener progressRequestListener) throws ApiException { + Request request = buildRequest(path, method, queryParams, collectionQueryParams, body, headerParams, formParams, authNames, progressRequestListener); + + return httpClient.newCall(request); + } + + /** + * Build an HTTP request with the given options. + * + * @param path The sub-path of the HTTP URL + * @param method The request method, one of "GET", "HEAD", "OPTIONS", "POST", "PUT", "PATCH" and "DELETE" + * @param queryParams The query parameters + * @param collectionQueryParams The collection query parameters + * @param body The request body object + * @param headerParams The header parameters + * @param formParams The form parameters + * @param authNames The authentications to apply + * @param progressRequestListener Progress request listener + * @return The HTTP request + * @throws ApiException If fail to serialize the request body object + */ + public Request buildRequest(String path, String method, List queryParams, List collectionQueryParams, Object body, Map headerParams, Map formParams, String[] authNames, ProgressRequestBody.ProgressRequestListener progressRequestListener) throws ApiException { + updateParamsForAuth(authNames, queryParams, headerParams); + + final String url = buildUrl(path, queryParams, collectionQueryParams); + final Request.Builder reqBuilder = new Request.Builder().url(url); + processHeaderParams(headerParams, reqBuilder); + + String contentType = (String) headerParams.get("Content-Type"); + // ensuring a default content type + if (contentType == null) { + contentType = "application/json"; + } + + RequestBody reqBody; + if (!HttpMethod.permitsRequestBody(method)) { + reqBody = null; + } else if ("application/x-www-form-urlencoded".equals(contentType)) { + reqBody = buildRequestBodyFormEncoding(formParams); + } else if ("multipart/form-data".equals(contentType)) { + reqBody = buildRequestBodyMultipart(formParams); + } else if (body == null) { + if ("DELETE".equals(method)) { + // allow calling DELETE without sending a request body + reqBody = null; + } else { + // use an empty request body (for POST, PUT and PATCH) + reqBody = RequestBody.create(MediaType.parse(contentType), ""); + } + } else { + reqBody = serialize(body, contentType); + } + + Request request = null; + + if(progressRequestListener != null && reqBody != null) { + ProgressRequestBody progressRequestBody = new ProgressRequestBody(reqBody, progressRequestListener); + request = reqBuilder.method(method, progressRequestBody).build(); + } else { + request = reqBuilder.method(method, reqBody).build(); + } + + return request; + } + + /** + * Build full URL by concatenating base path, the given sub path and query parameters. + * + * @param path The sub path + * @param queryParams The query parameters + * @param collectionQueryParams The collection query parameters + * @return The full URL + */ + public String buildUrl(String path, List queryParams, List collectionQueryParams) { + final StringBuilder url = new StringBuilder(); + url.append(basePath).append(path); + + if (queryParams != null && !queryParams.isEmpty()) { + // support (constant) query string in `path`, e.g. "/posts?draft=1" + String prefix = path.contains("?") ? "&" : "?"; + for (Pair param : queryParams) { + if (param.getValue() != null) { + if (prefix != null) { + url.append(prefix); + prefix = null; + } else { + url.append("&"); + } + String value = parameterToString(param.getValue()); + url.append(escapeString(param.getName())).append("=").append(escapeString(value)); + } + } + } + + if (collectionQueryParams != null && !collectionQueryParams.isEmpty()) { + String prefix = url.toString().contains("?") ? "&" : "?"; + for (Pair param : collectionQueryParams) { + if (param.getValue() != null) { + if (prefix != null) { + url.append(prefix); + prefix = null; + } else { + url.append("&"); + } + String value = parameterToString(param.getValue()); + // collection query parameter value already escaped as part of parameterToPairs + url.append(escapeString(param.getName())).append("=").append(value); + } + } + } + + return url.toString(); + } + + /** + * Set header parameters to the request builder, including default headers. + * + * @param headerParams Header parameters in the ofrm of Map + * @param reqBuilder Reqeust.Builder + */ + public void processHeaderParams(Map headerParams, Request.Builder reqBuilder) { + for (Entry param : headerParams.entrySet()) { + reqBuilder.header(param.getKey(), parameterToString(param.getValue())); + } + for (Entry header : defaultHeaderMap.entrySet()) { + if (!headerParams.containsKey(header.getKey())) { + reqBuilder.header(header.getKey(), parameterToString(header.getValue())); + } + } + } + + /** + * Update query and header parameters based on authentication settings. + * + * @param authNames The authentications to apply + * @param queryParams List of query parameters + * @param headerParams Map of header parameters + */ + public void updateParamsForAuth(String[] authNames, List queryParams, Map headerParams) { + for (String authName : authNames) { + Authentication auth = authentications.get(authName); + if (auth == null) throw new RuntimeException("Authentication undefined: " + authName); + auth.applyToParams(queryParams, headerParams); + } + } + + /** + * Build a form-encoding request body with the given form parameters. + * + * @param formParams Form parameters in the form of Map + * @return RequestBody + */ + public RequestBody buildRequestBodyFormEncoding(Map formParams) { + FormEncodingBuilder formBuilder = new FormEncodingBuilder(); + for (Entry param : formParams.entrySet()) { + formBuilder.add(param.getKey(), parameterToString(param.getValue())); + } + return formBuilder.build(); + } + + /** + * Build a multipart (file uploading) request body with the given form parameters, + * which could contain text fields and file fields. + * + * @param formParams Form parameters in the form of Map + * @return RequestBody + */ + public RequestBody buildRequestBodyMultipart(Map formParams) { + MultipartBuilder mpBuilder = new MultipartBuilder().type(MultipartBuilder.FORM); + for (Entry param : formParams.entrySet()) { + if (param.getValue() instanceof File) { + File file = (File) param.getValue(); + Headers partHeaders = Headers.of("Content-Disposition", "form-data; name=\"" + param.getKey() + "\"; filename=\"" + file.getName() + "\""); + MediaType mediaType = MediaType.parse(guessContentTypeFromFile(file)); + mpBuilder.addPart(partHeaders, RequestBody.create(mediaType, file)); + } else { + Headers partHeaders = Headers.of("Content-Disposition", "form-data; name=\"" + param.getKey() + "\""); + mpBuilder.addPart(partHeaders, RequestBody.create(null, parameterToString(param.getValue()))); + } + } + return mpBuilder.build(); + } + + /** + * Guess Content-Type header from the given file (defaults to "application/octet-stream"). + * + * @param file The given file + * @return The guessed Content-Type + */ + public String guessContentTypeFromFile(File file) { + String contentType = URLConnection.guessContentTypeFromName(file.getName()); + if (contentType == null) { + return "application/octet-stream"; + } else { + return contentType; + } + } + + /** + * Apply SSL related settings to httpClient according to the current values of + * verifyingSsl and sslCaCert. + */ + private void applySslSettings() { + try { + TrustManager[] trustManagers = null; + HostnameVerifier hostnameVerifier = null; + if (!verifyingSsl) { + TrustManager trustAll = new X509TrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {} + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {} + @Override + public X509Certificate[] getAcceptedIssuers() { return null; } + }; + SSLContext sslContext = SSLContext.getInstance("TLS"); + trustManagers = new TrustManager[]{ trustAll }; + hostnameVerifier = new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { return true; } + }; + } else if (sslCaCert != null) { + char[] password = null; // Any password will work. + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + Collection certificates = certificateFactory.generateCertificates(sslCaCert); + if (certificates.isEmpty()) { + throw new IllegalArgumentException("expected non-empty set of trusted certificates"); + } + KeyStore caKeyStore = newEmptyKeyStore(password); + int index = 0; + for (Certificate certificate : certificates) { + String certificateAlias = "ca" + Integer.toString(index++); + caKeyStore.setCertificateEntry(certificateAlias, certificate); + } + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(caKeyStore); + trustManagers = trustManagerFactory.getTrustManagers(); + } + + if (keyManagers != null || trustManagers != null) { + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(keyManagers, trustManagers, new SecureRandom()); + httpClient.setSslSocketFactory(sslContext.getSocketFactory()); + } else { + httpClient.setSslSocketFactory(null); + } + httpClient.setHostnameVerifier(hostnameVerifier); + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } + } + + private KeyStore newEmptyKeyStore(char[] password) throws GeneralSecurityException { + try { + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(null, password); + return keyStore; + } catch (IOException e) { + throw new AssertionError(e); + } + } +} diff --git a/mustacheTemplates/libraries/okhttp-gson/ApiResponse.mustache b/mustacheTemplates/libraries/okhttp-gson/ApiResponse.mustache new file mode 100644 index 0000000000..3909393697 --- /dev/null +++ b/mustacheTemplates/libraries/okhttp-gson/ApiResponse.mustache @@ -0,0 +1,48 @@ +{{>licenseInfo}} + +package {{invokerPackage}}; + +import java.util.List; +import java.util.Map; + +/** + * API response returned by API call. + * + * @param The type of data that is deserialized from response body + */ +public class ApiResponse { + final private int statusCode; + final private Map> headers; + final private T data; + + /** + * @param statusCode The status code of HTTP response + * @param headers The headers of HTTP response + */ + public ApiResponse(int statusCode, Map> headers) { + this(statusCode, headers, null); + } + + /** + * @param statusCode The status code of HTTP response + * @param headers The headers of HTTP response + * @param data The object deserialized from response bod + */ + public ApiResponse(int statusCode, Map> headers, T data) { + this.statusCode = statusCode; + this.headers = headers; + this.data = data; + } + + public int getStatusCode() { + return statusCode; + } + + public Map> getHeaders() { + return headers; + } + + public T getData() { + return data; + } +} diff --git a/mustacheTemplates/libraries/okhttp-gson/GzipRequestInterceptor.mustache b/mustacheTemplates/libraries/okhttp-gson/GzipRequestInterceptor.mustache new file mode 100644 index 0000000000..23224cf5d5 --- /dev/null +++ b/mustacheTemplates/libraries/okhttp-gson/GzipRequestInterceptor.mustache @@ -0,0 +1,70 @@ +{{>licenseInfo}} + +package {{invokerPackage}}; + +import com.squareup.okhttp.*; +import okio.Buffer; +import okio.BufferedSink; +import okio.GzipSink; +import okio.Okio; + +import java.io.IOException; + +/** + * Encodes request bodies using gzip. + * + * Taken from https://github.com/square/okhttp/issues/350 + */ +class GzipRequestInterceptor implements Interceptor { + @Override public Response intercept(Chain chain) throws IOException { + Request originalRequest = chain.request(); + if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) { + return chain.proceed(originalRequest); + } + + Request compressedRequest = originalRequest.newBuilder() + .header("Content-Encoding", "gzip") + .method(originalRequest.method(), forceContentLength(gzip(originalRequest.body()))) + .build(); + return chain.proceed(compressedRequest); + } + + private RequestBody forceContentLength(final RequestBody requestBody) throws IOException { + final Buffer buffer = new Buffer(); + requestBody.writeTo(buffer); + return new RequestBody() { + @Override + public MediaType contentType() { + return requestBody.contentType(); + } + + @Override + public long contentLength() { + return buffer.size(); + } + + @Override + public void writeTo(BufferedSink sink) throws IOException { + sink.write(buffer.snapshot()); + } + }; + } + + private RequestBody gzip(final RequestBody body) { + return new RequestBody() { + @Override public MediaType contentType() { + return body.contentType(); + } + + @Override public long contentLength() { + return -1; // We don't know the compressed length in advance! + } + + @Override public void writeTo(BufferedSink sink) throws IOException { + BufferedSink gzipSink = Okio.buffer(new GzipSink(sink)); + body.writeTo(gzipSink); + gzipSink.close(); + } + }; + } +} \ No newline at end of file diff --git a/mustacheTemplates/libraries/okhttp-gson/JSON.mustache b/mustacheTemplates/libraries/okhttp-gson/JSON.mustache new file mode 100644 index 0000000000..7ca5a7859a --- /dev/null +++ b/mustacheTemplates/libraries/okhttp-gson/JSON.mustache @@ -0,0 +1,526 @@ +{{>licenseInfo}} + +package {{invokerPackage}}; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import io.gsonfire.GsonFireBuilder; +import io.gsonfire.PostProcessor; +import io.gsonfire.TypeSelector; +import com.google.gson.JsonParseException; +import com.google.gson.TypeAdapter; +import com.google.gson.internal.bind.util.ISO8601Utils; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +{{#joda}} +import org.joda.time.DateTime; +import org.joda.time.LocalDate; +import org.joda.time.format.DateTimeFormatter; +import org.joda.time.format.DateTimeFormatterBuilder; +import org.joda.time.format.ISODateTimeFormat; +{{/joda}} +{{#threetenbp}} +import org.threeten.bp.LocalDate; +import org.threeten.bp.OffsetDateTime; +import org.threeten.bp.format.DateTimeFormatter; +{{/threetenbp}} + +{{#hasModel}}import {{modelPackage}}.*;{{/hasModel}} + +import java.io.IOException; +import java.io.StringReader; +import java.lang.reflect.Type; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.ParsePosition; +{{#java8}} +import java.time.LocalDate; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +{{/java8}} +import java.util.Date; +import java.util.Map; +import java.util.HashMap; + +public class JSON { + private Gson gson; + private boolean isLenientOnJson = false; + private DateTypeAdapter dateTypeAdapter = new DateTypeAdapter(); + private SqlDateTypeAdapter sqlDateTypeAdapter = new SqlDateTypeAdapter(); + {{#joda}} + private DateTimeTypeAdapter dateTimeTypeAdapter = new DateTimeTypeAdapter(); + private LocalDateTypeAdapter localDateTypeAdapter = new LocalDateTypeAdapter(); + {{/joda}} + {{#jsr310}} + private OffsetDateTimeTypeAdapter offsetDateTimeTypeAdapter = new OffsetDateTimeTypeAdapter(); + private LocalDateTypeAdapter localDateTypeAdapter = new LocalDateTypeAdapter(); + {{/jsr310}} + + public static GsonBuilder createGson() { + GsonFireBuilder fireBuilder = new GsonFireBuilder() + {{#parent}} + .registerTypeSelector({{classname}}.class, new TypeSelector<{{classname}}>() { + @Override + public Class getClassForElement(JsonElement readElement) { + Map> classByDiscriminatorValue = new HashMap<>(); + {{#if discriminator.mapping}} + {{#each discriminator.mapping}} + classByDiscriminatorValue.put("{{@key}}".toUpperCase(), {{this}}.class); + {{/each}} + {{else}} + {{#children}} + classByDiscriminatorValue.put("{{name}}".toUpperCase(), {{classname}}.class); + {{/children}} + classByDiscriminatorValue.put("{{classname}}".toUpperCase(), {{classname}}.class); + {{/if}} + return getClassByDiscriminator( + classByDiscriminatorValue, + getDiscriminatorValue(readElement, "{{discriminator.propertyName}}")); + } + }) + .registerPostProcessor({{classname}}.class, new PostProcessor<{{classname}}>() { + @Override + public void postDeserialize({{classname}} result, JsonElement src, Gson gson) { + + } + + @Override + public void postSerialize(JsonElement result, {{classname}} src, Gson gson) { + Map, String> discriminatorValueByClass = new HashMap<>(); + {{#if discriminator.mapping}} + {{#each discriminator.mapping}} + discriminatorValueByClass.put({{this}}.class, "{{@key}}"); + {{/each}} + {{else}} + {{#children}} + discriminatorValueByClass.put({{classname}}.class, "{{name}}"); + {{/children}} + discriminatorValueByClass.put({{classname}}.class, "{{classname}}"); + {{/if}} + if(result instanceof JsonObject) + { + if(!((JsonObject) result).has("{{discriminator.propertyName}}")) + { + ((JsonObject) result).addProperty("{{discriminator.propertyName}}", discriminatorValueByClass.get(src.getClass())); + } + } + } + }) + {{/parent}} + ; + return fireBuilder.createGsonBuilder(); + } + + private static String getDiscriminatorValue(JsonElement readElement, String discriminatorField) { + JsonElement element = readElement.getAsJsonObject().get(discriminatorField); + if(null == element) { + throw new IllegalArgumentException("missing discriminator field: <" + discriminatorField + ">"); + } + return element.getAsString(); + } + + private static Class getClassByDiscriminator(Map> classByDiscriminatorValue, String discriminatorValue) { + Class clazz = classByDiscriminatorValue.get(discriminatorValue.toUpperCase()); + if(null == clazz) { + throw new IllegalArgumentException("cannot determine model class of name: <" + discriminatorValue + ">"); + } + return clazz; + } + + public JSON() { + gson = createGson() + .registerTypeAdapter(Date.class, dateTypeAdapter) + .registerTypeAdapter(java.sql.Date.class, sqlDateTypeAdapter) + {{#joda}} + .registerTypeAdapter(DateTime.class, dateTimeTypeAdapter) + .registerTypeAdapter(LocalDate.class, localDateTypeAdapter) + {{/joda}} + {{#jsr310}} + .registerTypeAdapter(OffsetDateTime.class, offsetDateTimeTypeAdapter) + .registerTypeAdapter(LocalDate.class, localDateTypeAdapter) + {{/jsr310}} + .create(); + } + + /** + * Get Gson. + * + * @return Gson + */ + public Gson getGson() { + return gson; + } + + /** + * Set Gson. + * + * @param gson Gson + * @return JSON + */ + public JSON setGson(Gson gson) { + this.gson = gson; + return this; + } + + public JSON setLenientOnJson(boolean lenientOnJson) { + isLenientOnJson = lenientOnJson; + return this; + } + + /** + * Serialize the given Java object into JSON string. + * + * @param obj Object + * @return String representation of the JSON + */ + public String serialize(Object obj) { + return gson.toJson(obj); + } + + /** + * Deserialize the given JSON string to Java object. + * + * @param Type + * @param body The JSON string + * @param returnType The type to deserialize into + * @return The deserialized Java object + */ + @SuppressWarnings("unchecked") + public T deserialize(String body, Type returnType) { + try { + if (isLenientOnJson) { + JsonReader jsonReader = new JsonReader(new StringReader(body)); + // see https://google-gson.googlecode.com/svn/trunk/gson/docs/javadocs/com/google/gson/stream/JsonReader.html#setLenient(boolean) + jsonReader.setLenient(true); + return gson.fromJson(jsonReader, returnType); + } else { + return gson.fromJson(body, returnType); + } + } catch (JsonParseException e) { + // Fallback processing when failed to parse JSON form response body: + // return the response body string directly for the String return type; + if (returnType.equals(String.class)) + return (T) body; + else throw (e); + } + } + + {{#joda}} + /** + * Gson TypeAdapter for Joda DateTime type + */ + public static class DateTimeTypeAdapter extends TypeAdapter { + + private DateTimeFormatter formatter; + + public DateTimeTypeAdapter() { + this(new DateTimeFormatterBuilder() + .append(ISODateTimeFormat.dateTime().getPrinter(), ISODateTimeFormat.dateOptionalTimeParser().getParser()) + .toFormatter()); + } + + public DateTimeTypeAdapter(DateTimeFormatter formatter) { + this.formatter = formatter; + } + + public void setFormat(DateTimeFormatter dateFormat) { + this.formatter = dateFormat; + } + + @Override + public void write(JsonWriter out, DateTime date) throws IOException { + if (date == null) { + out.nullValue(); + } else { + out.value(formatter.print(date)); + } + } + + @Override + public DateTime read(JsonReader in) throws IOException { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String date = in.nextString(); + return formatter.parseDateTime(date); + } + } + } + + /** + * Gson TypeAdapter for Joda LocalDate type + */ + public class LocalDateTypeAdapter extends TypeAdapter { + + private DateTimeFormatter formatter; + + public LocalDateTypeAdapter() { + this(ISODateTimeFormat.date()); + } + + public LocalDateTypeAdapter(DateTimeFormatter formatter) { + this.formatter = formatter; + } + + public void setFormat(DateTimeFormatter dateFormat) { + this.formatter = dateFormat; + } + + @Override + public void write(JsonWriter out, LocalDate date) throws IOException { + if (date == null) { + out.nullValue(); + } else { + out.value(formatter.print(date)); + } + } + + @Override + public LocalDate read(JsonReader in) throws IOException { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String date = in.nextString(); + return formatter.parseLocalDate(date); + } + } + } + + public JSON setDateTimeFormat(DateTimeFormatter dateFormat) { + dateTimeTypeAdapter.setFormat(dateFormat); + return this; + } + + public JSON setLocalDateFormat(DateTimeFormatter dateFormat) { + localDateTypeAdapter.setFormat(dateFormat); + return this; + } + + {{/joda}} + {{#jsr310}} + /** + * Gson TypeAdapter for JSR310 OffsetDateTime type + */ + public static class OffsetDateTimeTypeAdapter extends TypeAdapter { + + private DateTimeFormatter formatter; + + public OffsetDateTimeTypeAdapter() { + this(DateTimeFormatter.ISO_OFFSET_DATE_TIME); + } + + public OffsetDateTimeTypeAdapter(DateTimeFormatter formatter) { + this.formatter = formatter; + } + + public void setFormat(DateTimeFormatter dateFormat) { + this.formatter = dateFormat; + } + + @Override + public void write(JsonWriter out, OffsetDateTime date) throws IOException { + if (date == null) { + out.nullValue(); + } else { + out.value(formatter.format(date)); + } + } + + @Override + public OffsetDateTime read(JsonReader in) throws IOException { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String date = in.nextString(); + if (date.endsWith("+0000")) { + date = date.substring(0, date.length()-5) + "Z"; + } + return OffsetDateTime.parse(date, formatter); + } + } + } + + /** + * Gson TypeAdapter for JSR310 LocalDate type + */ + public class LocalDateTypeAdapter extends TypeAdapter { + + private DateTimeFormatter formatter; + + public LocalDateTypeAdapter() { + this(DateTimeFormatter.ISO_LOCAL_DATE); + } + + public LocalDateTypeAdapter(DateTimeFormatter formatter) { + this.formatter = formatter; + } + + public void setFormat(DateTimeFormatter dateFormat) { + this.formatter = dateFormat; + } + + @Override + public void write(JsonWriter out, LocalDate date) throws IOException { + if (date == null) { + out.nullValue(); + } else { + out.value(formatter.format(date)); + } + } + + @Override + public LocalDate read(JsonReader in) throws IOException { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String date = in.nextString(); + return LocalDate.parse(date, formatter); + } + } + } + + public JSON setOffsetDateTimeFormat(DateTimeFormatter dateFormat) { + offsetDateTimeTypeAdapter.setFormat(dateFormat); + return this; + } + + public JSON setLocalDateFormat(DateTimeFormatter dateFormat) { + localDateTypeAdapter.setFormat(dateFormat); + return this; + } + + {{/jsr310}} + /** + * Gson TypeAdapter for java.sql.Date type + * If the dateFormat is null, a simple "yyyy-MM-dd" format will be used + * (more efficient than SimpleDateFormat). + */ + public static class SqlDateTypeAdapter extends TypeAdapter { + + private DateFormat dateFormat; + + public SqlDateTypeAdapter() { + } + + public SqlDateTypeAdapter(DateFormat dateFormat) { + this.dateFormat = dateFormat; + } + + public void setFormat(DateFormat dateFormat) { + this.dateFormat = dateFormat; + } + + @Override + public void write(JsonWriter out, java.sql.Date date) throws IOException { + if (date == null) { + out.nullValue(); + } else { + String value; + if (dateFormat != null) { + value = dateFormat.format(date); + } else { + value = date.toString(); + } + out.value(value); + } + } + + @Override + public java.sql.Date read(JsonReader in) throws IOException { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String date = in.nextString(); + try { + if (dateFormat != null) { + return new java.sql.Date(dateFormat.parse(date).getTime()); + } + return new java.sql.Date(ISO8601Utils.parse(date, new ParsePosition(0)).getTime()); + } catch (ParseException e) { + throw new JsonParseException(e); + } + } + } + } + + /** + * Gson TypeAdapter for java.util.Date type + * If the dateFormat is null, ISO8601Utils will be used. + */ + public static class DateTypeAdapter extends TypeAdapter { + + private DateFormat dateFormat; + + public DateTypeAdapter() { + } + + public DateTypeAdapter(DateFormat dateFormat) { + this.dateFormat = dateFormat; + } + + public void setFormat(DateFormat dateFormat) { + this.dateFormat = dateFormat; + } + + @Override + public void write(JsonWriter out, Date date) throws IOException { + if (date == null) { + out.nullValue(); + } else { + String value; + if (dateFormat != null) { + value = dateFormat.format(date); + } else { + value = ISO8601Utils.format(date, true); + } + out.value(value); + } + } + + @Override + public Date read(JsonReader in) throws IOException { + try { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String date = in.nextString(); + try { + if (dateFormat != null) { + return dateFormat.parse(date); + } + return ISO8601Utils.parse(date, new ParsePosition(0)); + } catch (ParseException e) { + throw new JsonParseException(e); + } + } + } catch (IllegalArgumentException e) { + throw new JsonParseException(e); + } + } + } + + public JSON setDateFormat(DateFormat dateFormat) { + dateTypeAdapter.setFormat(dateFormat); + return this; + } + + public JSON setSqlDateFormat(DateFormat dateFormat) { + sqlDateTypeAdapter.setFormat(dateFormat); + return this; + } + +} diff --git a/mustacheTemplates/libraries/okhttp-gson/ProgressRequestBody.mustache b/mustacheTemplates/libraries/okhttp-gson/ProgressRequestBody.mustache new file mode 100644 index 0000000000..0f15c1f415 --- /dev/null +++ b/mustacheTemplates/libraries/okhttp-gson/ProgressRequestBody.mustache @@ -0,0 +1,66 @@ +{{>licenseInfo}} + +package {{invokerPackage}}; + +import com.squareup.okhttp.MediaType; +import com.squareup.okhttp.RequestBody; + +import java.io.IOException; + +import okio.Buffer; +import okio.BufferedSink; +import okio.ForwardingSink; +import okio.Okio; +import okio.Sink; + +public class ProgressRequestBody extends RequestBody { + + public interface ProgressRequestListener { + void onRequestProgress(long bytesWritten, long contentLength, boolean done); + } + + private final RequestBody requestBody; + + private final ProgressRequestListener progressListener; + + public ProgressRequestBody(RequestBody requestBody, ProgressRequestListener progressListener) { + this.requestBody = requestBody; + this.progressListener = progressListener; + } + + @Override + public MediaType contentType() { + return requestBody.contentType(); + } + + @Override + public long contentLength() throws IOException { + return requestBody.contentLength(); + } + + @Override + public void writeTo(BufferedSink sink) throws IOException { + BufferedSink bufferedSink = Okio.buffer(sink(sink)); + requestBody.writeTo(bufferedSink); + bufferedSink.flush(); + } + + private Sink sink(Sink sink) { + return new ForwardingSink(sink) { + + long bytesWritten = 0L; + long contentLength = 0L; + + @Override + public void write(Buffer source, long byteCount) throws IOException { + super.write(source, byteCount); + if (contentLength == 0) { + contentLength = contentLength(); + } + + bytesWritten += byteCount; + progressListener.onRequestProgress(bytesWritten, contentLength, bytesWritten == contentLength); + } + }; + } +} diff --git a/mustacheTemplates/libraries/okhttp-gson/ProgressResponseBody.mustache b/mustacheTemplates/libraries/okhttp-gson/ProgressResponseBody.mustache new file mode 100644 index 0000000000..3f53133697 --- /dev/null +++ b/mustacheTemplates/libraries/okhttp-gson/ProgressResponseBody.mustache @@ -0,0 +1,65 @@ +{{>licenseInfo}} + +package {{invokerPackage}}; + +import com.squareup.okhttp.MediaType; +import com.squareup.okhttp.ResponseBody; + +import java.io.IOException; + +import okio.Buffer; +import okio.BufferedSource; +import okio.ForwardingSource; +import okio.Okio; +import okio.Source; + +public class ProgressResponseBody extends ResponseBody { + + public interface ProgressListener { + void update(long bytesRead, long contentLength, boolean done); + } + + private final ResponseBody responseBody; + private final ProgressListener progressListener; + private BufferedSource bufferedSource; + + public ProgressResponseBody(ResponseBody responseBody, ProgressListener progressListener) { + this.responseBody = responseBody; + this.progressListener = progressListener; + } + + @Override + public MediaType contentType() { + return responseBody.contentType(); + } + + @Override + public long contentLength() throws IOException { + return responseBody.contentLength(); + } + + @Override + public BufferedSource source() throws IOException { + if (bufferedSource == null) { + bufferedSource = Okio.buffer(source(responseBody.source())); + } + return bufferedSource; + } + + private Source source(Source source) { + return new ForwardingSource(source) { + long totalBytesRead = 0L; + + @Override + public long read(Buffer sink, long byteCount) throws IOException { + long bytesRead = super.read(sink, byteCount); + // read() returns the number of bytes read, or -1 if this source is exhausted. + totalBytesRead += bytesRead != -1 ? bytesRead : 0; + progressListener.update(totalBytesRead, responseBody.contentLength(), bytesRead == -1); + return bytesRead; + } + }; + } +} + + diff --git a/mustacheTemplates/libraries/okhttp-gson/api.mustache b/mustacheTemplates/libraries/okhttp-gson/api.mustache new file mode 100644 index 0000000000..eb25fa7d62 --- /dev/null +++ b/mustacheTemplates/libraries/okhttp-gson/api.mustache @@ -0,0 +1,251 @@ +{{>licenseInfo}} + +package {{package}}; + +import {{invokerPackage}}.ApiCallback; +import {{invokerPackage}}.ApiClient; +import {{invokerPackage}}.ApiException; +import {{invokerPackage}}.ApiResponse; +import {{invokerPackage}}.Configuration; +import {{invokerPackage}}.Pair; +import {{invokerPackage}}.ProgressRequestBody; +import {{invokerPackage}}.ProgressResponseBody; +{{#performBeanValidation}} +import {{invokerPackage}}.BeanValidationException; +{{/performBeanValidation}} + +import com.google.gson.reflect.TypeToken; + +import java.io.IOException; + +{{#useBeanValidation}} +import javax.validation.constraints.*; +{{/useBeanValidation}} +{{#performBeanValidation}} +import javax.validation.ConstraintViolation; +import javax.validation.Validation; +import javax.validation.ValidatorFactory; +import javax.validation.executable.ExecutableValidator; +import java.util.Set; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +{{/performBeanValidation}} + +{{#imports}}import {{import}}; +{{/imports}} + +import java.lang.reflect.Type; +{{^fullJavaUtil}} +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +{{/fullJavaUtil}} + +{{#operations}} +public class {{classname}} { + private ApiClient {{localVariablePrefix}}apiClient; + + public {{classname}}() { + this(Configuration.getDefaultApiClient()); + } + + public {{classname}}(ApiClient apiClient) { + this.{{localVariablePrefix}}apiClient = apiClient; + } + + public ApiClient getApiClient() { + return {{localVariablePrefix}}apiClient; + } + + public void setApiClient(ApiClient apiClient) { + this.{{localVariablePrefix}}apiClient = apiClient; + } + + {{#operation}} + {{#contents}} + /** + * Build call for {{operationId}}{{#parameters}} + * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{/parameters}} + * @param progressListener Progress listener + * @param progressRequestListener Progress request listener + * @return Call to execute + * @throws ApiException If fail to serialize the request body object + {{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation + {{/externalDocs}} + */ + public com.squareup.okhttp.Call {{operationId}}Call({{#parameters}}{{{dataType}}} {{paramName}}, {{/parameters}}final ProgressResponseBody.ProgressListener progressListener, final ProgressRequestBody.ProgressRequestListener progressRequestListener) throws ApiException { + Object {{localVariablePrefix}}localVarPostBody = {{^isForm}}{{#bodyParam}}{{paramName}}{{/bodyParam}}{{^bodyParam}}null{{/bodyParam}}{{/isForm}}{{#isForm}}null{{/isForm}}; + + // create path and map variables + String {{localVariablePrefix}}localVarPath = "{{{path}}}"{{#pathParams}} + .replaceAll("\\{" + "{{baseName}}" + "\\}", {{localVariablePrefix}}apiClient.escapeString({{{paramName}}}.toString())){{/pathParams}}; + + {{javaUtilPrefix}}List {{localVariablePrefix}}localVarQueryParams = new {{javaUtilPrefix}}ArrayList(); + {{javaUtilPrefix}}List {{localVariablePrefix}}localVarCollectionQueryParams = new {{javaUtilPrefix}}ArrayList();{{#queryParams}} + if ({{paramName}} != null) + {{localVariablePrefix}}{{#collectionFormat}}localVarCollectionQueryParams.addAll({{localVariablePrefix}}apiClient.parameterToPairs("{{{collectionFormat}}}", {{/collectionFormat}}{{^collectionFormat}}localVarQueryParams.addAll({{localVariablePrefix}}apiClient.parameterToPair({{/collectionFormat}}"{{baseName}}", {{paramName}}));{{/queryParams}} + + {{javaUtilPrefix}}Map {{localVariablePrefix}}localVarHeaderParams = new {{javaUtilPrefix}}HashMap();{{#headerParams}} + if ({{paramName}} != null) + {{localVariablePrefix}}localVarHeaderParams.put("{{baseName}}", {{localVariablePrefix}}apiClient.parameterToString({{paramName}}));{{/headerParams}} + + {{javaUtilPrefix}}Map {{localVariablePrefix}}localVarFormParams = new {{javaUtilPrefix}}HashMap(); + {{#isForm}} + {{#formParams}} + if ({{paramName}} != null) + {{localVariablePrefix}}localVarFormParams.put("{{baseName}}", {{paramName}}); + {{/formParams}} + {{/isForm}} + + final String[] {{localVariablePrefix}}localVarAccepts = { + {{#produces}}"{{{mediaType}}}"{{#hasMore}}, {{/hasMore}}{{/produces}} + }; + final String {{localVariablePrefix}}localVarAccept = {{localVariablePrefix}}apiClient.selectHeaderAccept({{localVariablePrefix}}localVarAccepts); + if ({{localVariablePrefix}}localVarAccept != null) {{localVariablePrefix}}localVarHeaderParams.put("Accept", {{localVariablePrefix}}localVarAccept); + + final String[] {{localVariablePrefix}}localVarContentTypes = { + {{#consumes}}"{{{mediaType}}}"{{#hasMore}}, {{/hasMore}}{{/consumes}} + }; + final String {{localVariablePrefix}}localVarContentType = {{localVariablePrefix}}apiClient.selectHeaderContentType({{localVariablePrefix}}localVarContentTypes); + {{localVariablePrefix}}localVarHeaderParams.put("Content-Type", {{localVariablePrefix}}localVarContentType); + + if(progressListener != null) { + apiClient.getHttpClient().networkInterceptors().add(new com.squareup.okhttp.Interceptor() { + @Override + public com.squareup.okhttp.Response intercept(com.squareup.okhttp.Interceptor.Chain chain) throws IOException { + com.squareup.okhttp.Response originalResponse = chain.proceed(chain.request()); + return originalResponse.newBuilder() + .body(new ProgressResponseBody(originalResponse.body(), progressListener)) + .build(); + } + }); + } + + String[] {{localVariablePrefix}}localVarAuthNames = new String[] { {{#authMethods}}"{{name}}"{{#has this 'more'}}, {{/has}}{{/authMethods}} }; + return {{localVariablePrefix}}apiClient.buildCall({{localVariablePrefix}}localVarPath, "{{httpMethod}}", {{localVariablePrefix}}localVarQueryParams, {{localVariablePrefix}}localVarCollectionQueryParams, {{localVariablePrefix}}localVarPostBody, {{localVariablePrefix}}localVarHeaderParams, {{localVariablePrefix}}localVarFormParams, {{localVariablePrefix}}localVarAuthNames, progressRequestListener); + } + + @SuppressWarnings("rawtypes") + private com.squareup.okhttp.Call {{operationId}}ValidateBeforeCall({{#parameters}}{{{dataType}}} {{paramName}}, {{/parameters}}final ProgressResponseBody.ProgressListener progressListener, final ProgressRequestBody.ProgressRequestListener progressRequestListener) throws ApiException { + {{^performBeanValidation}} + {{#parameters}}{{#required}} + // verify the required parameter '{{paramName}}' is set + if ({{paramName}} == null) { + throw new ApiException("Missing the required parameter '{{paramName}}' when calling {{operationId}}(Async)"); + } + {{/required}}{{/parameters}} + + com.squareup.okhttp.Call {{localVariablePrefix}}call = {{operationId}}Call({{#parameters}}{{paramName}}, {{/parameters}}progressListener, progressRequestListener); + return {{localVariablePrefix}}call; + + {{/performBeanValidation}} + {{#performBeanValidation}} + try { + ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); + ExecutableValidator executableValidator = factory.getValidator().forExecutables(); + + Object[] parameterValues = { {{#parameters}}{{paramName}}{{#has this 'more'}}, {{/has}}{{/parameters}} }; + Method method = this.getClass().getMethod("{{operationId}}WithHttpInfo"{{#parameters}}, {{#is this 'list-container'}}java.util.List{{/is}}{{#is this 'map-container'}}java.util.Map{{/is}}{{#isNot this 'list-container'}}{{#isNot this 'map-container'}}{{{dataType}}}{{/isNot}}{{/isNot}}.class{{/parameters}}); + Set> violations = executableValidator.validateParameters(this, method, + parameterValues); + + if (violations.size() == 0) { + com.squareup.okhttp.Call {{localVariablePrefix}}call = {{operationId}}Call({{#parameters}}{{paramName}}, {{/parameters}}progressListener, progressRequestListener); + return {{localVariablePrefix}}call; + + } else { + throw new BeanValidationException((Set) violations); + } + } catch (NoSuchMethodException e) { + e.printStackTrace(); + throw new ApiException(e.getMessage()); + } catch (SecurityException e) { + e.printStackTrace(); + throw new ApiException(e.getMessage()); + } + + {{/performBeanValidation}} + + + + + } + + /** + * {{summary}} + * {{notes}}{{#parameters}} + * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{/parameters}}{{#returnType}} + * @return {{returnType}}{{/returnType}} + * @throws ApiException If fail to call the API, e.g. server error or cannot deserialize the response body + {{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation + {{/externalDocs}} + */ + public {{#returnType}}{{{returnType}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{operationId}}({{#parameters}}{{{dataType}}} {{paramName}}{{#has this 'more'}}, {{/has}}{{/parameters}}) throws ApiException { + {{#returnType}}ApiResponse<{{{returnType}}}> {{localVariablePrefix}}resp = {{/returnType}}{{operationId}}WithHttpInfo({{#parameters}}{{paramName}}{{#has this 'more'}}, {{/has}}{{/parameters}});{{#returnType}} + return {{localVariablePrefix}}resp.getData();{{/returnType}} + } + + /** + * {{summary}} + * {{notes}}{{#parameters}} + * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{/parameters}} + * @return ApiResponse<{{#returnType}}{{returnType}}{{/returnType}}{{^returnType}}Void{{/returnType}}> + * @throws ApiException If fail to call the API, e.g. server error or cannot deserialize the response body + {{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation + {{/externalDocs}} + */ + public ApiResponse<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Void{{/returnType}}> {{operationId}}WithHttpInfo({{#parameters}}{{#if useBeanValidation}}{{>beanValidationQueryParams}}{{/if}}{{{dataType}}} {{paramName}}{{#has this 'more'}}, {{/has}}{{/parameters}}) throws ApiException { + com.squareup.okhttp.Call {{localVariablePrefix}}call = {{operationId}}ValidateBeforeCall({{#parameters}}{{paramName}}, {{/parameters}}null, null); + {{#returnType}}Type {{localVariablePrefix}}localVarReturnType = new TypeToken<{{{returnType}}}>(){}.getType(); + return {{localVariablePrefix}}apiClient.execute({{localVariablePrefix}}call, {{localVariablePrefix}}localVarReturnType);{{/returnType}}{{^returnType}}return {{localVariablePrefix}}apiClient.execute({{localVariablePrefix}}call);{{/returnType}} + } + + /** + * {{summary}} (asynchronously) + * {{notes}}{{#parameters}} + * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{/parameters}} + * @param callback The callback to be executed when the API call finishes + * @return The request call + * @throws ApiException If fail to process the API call, e.g. serializing the request body object + {{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation + {{/externalDocs}} + */ + public com.squareup.okhttp.Call {{operationId}}Async({{#parameters}}{{{dataType}}} {{paramName}}, {{/parameters}}final ApiCallback<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Void{{/returnType}}> {{localVariablePrefix}}callback) throws ApiException { + + ProgressResponseBody.ProgressListener progressListener = null; + ProgressRequestBody.ProgressRequestListener progressRequestListener = null; + + if (callback != null) { + progressListener = new ProgressResponseBody.ProgressListener() { + @Override + public void update(long bytesRead, long contentLength, boolean done) { + callback.onDownloadProgress(bytesRead, contentLength, done); + } + }; + + progressRequestListener = new ProgressRequestBody.ProgressRequestListener() { + @Override + public void onRequestProgress(long bytesWritten, long contentLength, boolean done) { + callback.onUploadProgress(bytesWritten, contentLength, done); + } + }; + } + + com.squareup.okhttp.Call {{localVariablePrefix}}call = {{operationId}}ValidateBeforeCall({{#parameters}}{{paramName}}, {{/parameters}}progressListener, progressRequestListener); + {{#returnType}}Type {{localVariablePrefix}}localVarReturnType = new TypeToken<{{{returnType}}}>(){}.getType(); + {{localVariablePrefix}}apiClient.executeAsync({{localVariablePrefix}}call, {{localVariablePrefix}}localVarReturnType, {{localVariablePrefix}}callback);{{/returnType}}{{^returnType}}{{localVariablePrefix}}apiClient.executeAsync({{localVariablePrefix}}call, {{localVariablePrefix}}callback);{{/returnType}} + return {{localVariablePrefix}}call; + } + {{/contents}} + {{/operation}} +} +{{/operations}} \ No newline at end of file diff --git a/mustacheTemplates/libraries/okhttp-gson/auth/HttpBasicAuth.mustache b/mustacheTemplates/libraries/okhttp-gson/auth/HttpBasicAuth.mustache new file mode 100644 index 0000000000..320903b031 --- /dev/null +++ b/mustacheTemplates/libraries/okhttp-gson/auth/HttpBasicAuth.mustache @@ -0,0 +1,43 @@ +{{>licenseInfo}} + +package {{invokerPackage}}.auth; + +import {{invokerPackage}}.Pair; + +import com.squareup.okhttp.Credentials; + +import java.util.Map; +import java.util.List; + +import java.io.UnsupportedEncodingException; + +public class HttpBasicAuth implements Authentication { + private String username; + private String password; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + @Override + public void applyToParams(List queryParams, Map headerParams) { + if (username == null && password == null) { + return; + } + headerParams.put("Authorization", Credentials.basic( + username == null ? "" : username, + password == null ? "" : password)); + } +} diff --git a/mustacheTemplates/libraries/okhttp-gson/build.gradle.mustache b/mustacheTemplates/libraries/okhttp-gson/build.gradle.mustache new file mode 100644 index 0000000000..2bb4b26834 --- /dev/null +++ b/mustacheTemplates/libraries/okhttp-gson/build.gradle.mustache @@ -0,0 +1,120 @@ +apply plugin: 'idea' +apply plugin: 'eclipse' + +group = '{{groupId}}' +version = '{{artifactVersion}}' + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.3.+' + classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' + } +} + +repositories { + jcenter() +} + + +if(hasProperty('target') && target == 'android') { + + apply plugin: 'com.android.library' + apply plugin: 'com.github.dcendents.android-maven' + + android { + compileSdkVersion 25 + buildToolsVersion '25.0.2' + defaultConfig { + minSdkVersion 14 + targetSdkVersion 25 + } + compileOptions { + {{#java8}} + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + {{/java8}} + {{^java8}} + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 + {{/java8}} + } + + // Rename the aar correctly + libraryVariants.all { variant -> + variant.outputs.each { output -> + def outputFile = output.outputFile + if (outputFile != null && outputFile.name.endsWith('.aar')) { + def fileName = "${project.name}-${variant.baseName}-${version}.aar" + output.outputFile = new File(outputFile.parent, fileName) + } + } + } + + dependencies { + provided 'javax.annotation:jsr250-api:1.0' + } + } + + afterEvaluate { + android.libraryVariants.all { variant -> + def task = project.tasks.create "jar${variant.name.capitalize()}", Jar + task.description = "Create jar artifact for ${variant.name}" + task.dependsOn variant.javaCompile + task.from variant.javaCompile.destinationDir + task.destinationDir = project.file("${project.buildDir}/outputs/jar") + task.archiveName = "${project.name}-${variant.baseName}-${version}.jar" + artifacts.add('archives', task); + } + } + + task sourcesJar(type: Jar) { + from android.sourceSets.main.java.srcDirs + classifier = 'sources' + } + + artifacts { + archives sourcesJar + } + +} else { + + apply plugin: 'java' + apply plugin: 'maven' + + sourceCompatibility = JavaVersion.VERSION_{{^java8}}1_7{{/java8}}{{#java8}}1_8{{/java8}} + targetCompatibility = JavaVersion.VERSION_{{^java8}}1_7{{/java8}}{{#java8}}1_8{{/java8}} + + install { + repositories.mavenInstaller { + pom.artifactId = '{{artifactId}}' + } + } + + task execute(type:JavaExec) { + main = System.getProperty('mainClass') + classpath = sourceSets.main.runtimeClasspath + } +} + +dependencies { + {{#useOas2}} + compile 'io.swagger:swagger-annotations:1.5.15' + {{/useOas2}} + {{^useOas2}} + compile 'io.swagger.core.v3:swagger-annotations:2.0.0' + {{/useOas2}} + compile 'com.squareup.okhttp:okhttp:2.7.5' + compile 'com.squareup.okhttp:logging-interceptor:2.7.5' + compile 'com.google.code.gson:gson:2.8.1' + compile 'io.gsonfire:gson-fire:1.8.3' + {{#joda}} + compile 'joda-time:joda-time:2.9.9' + {{/joda}} + {{#threetenbp}} + compile 'org.threeten:threetenbp:1.3.5' + {{/threetenbp}} + testCompile 'junit:junit:4.12' +} diff --git a/mustacheTemplates/libraries/okhttp-gson/build.sbt.mustache b/mustacheTemplates/libraries/okhttp-gson/build.sbt.mustache new file mode 100644 index 0000000000..d33652cf30 --- /dev/null +++ b/mustacheTemplates/libraries/okhttp-gson/build.sbt.mustache @@ -0,0 +1,31 @@ +lazy val root = (project in file(".")). + settings( + organization := "{{groupId}}", + name := "{{artifactId}}", + version := "{{artifactVersion}}", + scalaVersion := "2.11.4", + scalacOptions ++= Seq("-feature"), + javacOptions in compile ++= Seq("-Xlint:deprecation"), + publishArtifact in (Compile, packageDoc) := false, + resolvers += Resolver.mavenLocal, + libraryDependencies ++= Seq( + {{#useOas2}} + "io.swagger" % "swagger-annotations" % "1.5.15", + {{/useOas2}} + {{^useOas2}} + "io.swagger.core.v3" % "swagger-annotations" % "2.0.0", + {{/useOas2}} + "com.squareup.okhttp" % "okhttp" % "2.7.5", + "com.squareup.okhttp" % "logging-interceptor" % "2.7.5", + "com.google.code.gson" % "gson" % "2.8.1", + "io.gsonfire" % "gson-fire" % "1.8.3" % "compile", + {{#joda}} + "joda-time" % "joda-time" % "2.9.9" % "compile", + {{/joda}} + {{#threetenbp}} + "org.threeten" % "threetenbp" % "1.3.5" % "compile", + {{/threetenbp}} + "junit" % "junit" % "4.12" % "test", + "com.novocode" % "junit-interface" % "0.10" % "test" + ) + ) diff --git a/mustacheTemplates/libraries/okhttp-gson/pom.mustache b/mustacheTemplates/libraries/okhttp-gson/pom.mustache new file mode 100644 index 0000000000..ee00eae37c --- /dev/null +++ b/mustacheTemplates/libraries/okhttp-gson/pom.mustache @@ -0,0 +1,298 @@ + + 4.0.0 + {{groupId}} + {{artifactId}} + jar + {{artifactId}} + {{artifactVersion}} + {{artifactUrl}} + {{artifactDescription}} + + {{scmConnection}} + {{scmDeveloperConnection}} + {{scmUrl}} + + + 2.2.0 + + + + + {{licenseName}} + {{licenseUrl}} + repo + + + + + + {{developerName}} + {{developerEmail}} + {{developerOrganization}} + {{developerOrganizationUrl}} + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.12 + + + + loggerPath + conf/log4j.properties + + + -Xms512m -Xmx1500m + methods + pertest + + + + maven-dependency-plugin + + + package + + copy-dependencies + + + ${project.build.directory}/lib + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.2 + + + + jar + test-jar + + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 1.10 + + + add_sources + generate-sources + + add-source + + + + src/main/java + + + + + add_test_sources + generate-test-sources + + add-test-source + + + + src/test/java + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + attach-sources + + jar-no-fork + + + + + + + + + + sign-artifacts + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + sign-artifacts + verify + + sign + + + + + + + + + + + {{#useOas2}} + + io.swagger + swagger-annotations + ${swagger-core-version} + + {{/useOas2}} + {{^useOas2}} + + io.swagger.core.v3 + swagger-annotations + ${swagger-core-version} + + {{/useOas2}} + + com.squareup.okhttp + okhttp + ${okhttp-version} + + + com.squareup.okhttp + logging-interceptor + ${okhttp-version} + + + com.google.code.gson + gson + ${gson-version} + + + io.gsonfire + gson-fire + ${gson-fire-version} + + {{#joda}} + + joda-time + joda-time + ${jodatime-version} + + {{/joda}} + {{#threetenbp}} + + org.threeten + threetenbp + ${threetenbp-version} + + {{/threetenbp}} + {{#supportJava6}} + + org.apache.commons + commons-lang3 + ${commons_lang3_version} + + + commons-io + commons-io + ${commons_io_version} + + {{/supportJava6}} + {{#useBeanValidation}} + + + javax.validation + validation-api + 1.1.0.Final + provided + + {{/useBeanValidation}} + {{#performBeanValidation}} + + + org.hibernate + hibernate-validator + 5.4.1.Final + + + javax.el + el-api + 2.2 + + {{/performBeanValidation}} + {{#parcelableModel}} + + + com.google.android + android + 4.1.1.4 + provided + + {{/parcelableModel}} + + + junit + junit + ${junit-version} + test + + + + {{#java8}}1.8{{/java8}}{{^java8}}1.7{{/java8}} + ${java.version} + ${java.version} + {{#useOas2}} + 1.5.15 + {{/useOas2}} + {{^useOas2}} + 2.0.0 + {{/useOas2}} + 2.7.5 + 2.8.1 + 1.8.3 + {{#supportJava6}} + 2.5 + 3.6 + {{/supportJava6}} + {{#joda}} + 2.9.9 + {{/joda}} + {{#threetenbp}} + 1.3.5 + {{/threetenbp}} + 1.0.0 + 4.12 + UTF-8 + + diff --git a/mustacheTemplates/libraries/resteasy/ApiClient.mustache b/mustacheTemplates/libraries/resteasy/ApiClient.mustache new file mode 100644 index 0000000000..a5929154fe --- /dev/null +++ b/mustacheTemplates/libraries/resteasy/ApiClient.mustache @@ -0,0 +1,698 @@ +package {{invokerPackage}}; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.file.Files; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TimeZone; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.Invocation; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.Form; +import javax.ws.rs.core.GenericType; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import org.jboss.logging.Logger; +import org.jboss.resteasy.client.jaxrs.internal.ClientConfiguration; +import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput; +import org.jboss.resteasy.spi.ResteasyProviderFactory; + +import {{invokerPackage}}.auth.Authentication; +import {{invokerPackage}}.auth.HttpBasicAuth; +import {{invokerPackage}}.auth.ApiKeyAuth; +import {{invokerPackage}}.auth.OAuth; + +{{>generatedAnnotation}} +public class ApiClient { + private Map defaultHeaderMap = new HashMap(); + private String basePath = "{{{basePath}}}"; + private boolean debugging = false; + + private Client httpClient; + private JSON json; + private String tempFolderPath = null; + + private Map authentications; + + private int statusCode; + private Map> responseHeaders; + + private DateFormat dateFormat; + + public ApiClient() { + json = new JSON(); + httpClient = buildHttpClient(debugging); + + this.dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); + + // Use UTC as the default time zone. + this.dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + + this.json.setDateFormat((DateFormat) dateFormat.clone()); + + // Set default User-Agent. + setUserAgent("{{#httpUserAgent}}{{{.}}}{{/httpUserAgent}}{{^httpUserAgent}}Swagger-Codegen/{{{artifactVersion}}}/java{{/httpUserAgent}}"); + + // Setup authentications (key: authentication name, value: authentication). + authentications = new HashMap();{{#authMethods}}{{#is this 'basic'}} + authentications.put("{{name}}", new HttpBasicAuth());{{/is}}{{#is this 'api-key'}} + authentications.put("{{name}}", new ApiKeyAuth({{#is this 'key-in-header'}}"header"{{/is}}{{#isNot this 'key-in-header'}}"query"{{/isNot}}, "{{keyParamName}}"));{{/is}}{{#is this 'oauth'}} + authentications.put("{{name}}", new OAuth());{{/is}}{{#is this 'bearer'}} + authentications.put("{{name}}", new OAuth());{{/is}}{{/authMethods}} + // Prevent the authentications from being modified. + authentications = Collections.unmodifiableMap(authentications); + } + + /** + * Gets the JSON instance to do JSON serialization and deserialization. + */ + public JSON getJSON() { + return json; + } + + public Client getHttpClient() { + return httpClient; + } + + public ApiClient setHttpClient(Client httpClient) { + this.httpClient = httpClient; + return this; + } + + public String getBasePath() { + return basePath; + } + + public ApiClient setBasePath(String basePath) { + this.basePath = basePath; + return this; + } + + /** + * Gets the status code of the previous request + */ + public int getStatusCode() { + return statusCode; + } + + /** + * Gets the response headers of the previous request + */ + public Map> getResponseHeaders() { + return responseHeaders; + } + + /** + * Get authentications (key: authentication name, value: authentication). + */ + public Map getAuthentications() { + return authentications; + } + + /** + * Get authentication for the given name. + * + * @param authName The authentication name + * @return The authentication, null if not found + */ + public Authentication getAuthentication(String authName) { + return authentications.get(authName); + } + + /** + * Helper method to set username for the first HTTP basic authentication. + */ + public void setUsername(String username) { + for (Authentication auth : authentications.values()) { + if (auth instanceof HttpBasicAuth) { + ((HttpBasicAuth) auth).setUsername(username); + return; + } + } + throw new RuntimeException("No HTTP basic authentication configured!"); + } + + /** + * Helper method to set password for the first HTTP basic authentication. + */ + public void setPassword(String password) { + for (Authentication auth : authentications.values()) { + if (auth instanceof HttpBasicAuth) { + ((HttpBasicAuth) auth).setPassword(password); + return; + } + } + throw new RuntimeException("No HTTP basic authentication configured!"); + } + + /** + * Helper method to set API key value for the first API key authentication. + */ + public void setApiKey(String apiKey) { + for (Authentication auth : authentications.values()) { + if (auth instanceof ApiKeyAuth) { + ((ApiKeyAuth) auth).setApiKey(apiKey); + return; + } + } + throw new RuntimeException("No API key authentication configured!"); + } + + /** + * Helper method to set API key prefix for the first API key authentication. + */ + public void setApiKeyPrefix(String apiKeyPrefix) { + for (Authentication auth : authentications.values()) { + if (auth instanceof ApiKeyAuth) { + ((ApiKeyAuth) auth).setApiKeyPrefix(apiKeyPrefix); + return; + } + } + throw new RuntimeException("No API key authentication configured!"); + } + + /** + * Helper method to set access token for the first OAuth2 authentication. + */ + public void setAccessToken(String accessToken) { + for (Authentication auth : authentications.values()) { + if (auth instanceof OAuth) { + ((OAuth) auth).setAccessToken(accessToken); + return; + } + } + throw new RuntimeException("No OAuth2 authentication configured!"); + } + + /** + * Set the User-Agent header's value (by adding to the default header map). + */ + public ApiClient setUserAgent(String userAgent) { + addDefaultHeader("User-Agent", userAgent); + return this; + } + + /** + * Add a default header. + * + * @param key The header's key + * @param value The header's value + */ + public ApiClient addDefaultHeader(String key, String value) { + defaultHeaderMap.put(key, value); + return this; + } + + /** + * Check that whether debugging is enabled for this API client. + */ + public boolean isDebugging() { + return debugging; + } + + /** + * Enable/disable debugging for this API client. + * + * @param debugging To enable (true) or disable (false) debugging + */ + public ApiClient setDebugging(boolean debugging) { + this.debugging = debugging; + // Rebuild HTTP Client according to the new "debugging" value. + this.httpClient = buildHttpClient(debugging); + return this; + } + + /** + * The path of temporary folder used to store downloaded files from endpoints + * with file response. The default value is null, i.e. using + * the system's default tempopary folder. + * + * @see https://docs.oracle.com/javase/7/docs/api/java/io/File.html#createTempFile(java.lang.String,%20java.lang.String,%20java.io.File) + */ + public String getTempFolderPath() { + return tempFolderPath; + } + + public ApiClient setTempFolderPath(String tempFolderPath) { + this.tempFolderPath = tempFolderPath; + return this; + } + + /** + * Get the date format used to parse/format date parameters. + */ + public DateFormat getDateFormat() { + return dateFormat; + } + + /** + * Set the date format used to parse/format date parameters. + */ + public ApiClient setDateFormat(DateFormat dateFormat) { + this.dateFormat = dateFormat; + // also set the date format for model (de)serialization with Date properties + this.json.setDateFormat((DateFormat) dateFormat.clone()); + return this; + } + + /** + * Parse the given string into Date object. + */ + public Date parseDate(String str) { + try { + return dateFormat.parse(str); + } catch (java.text.ParseException e) { + throw new RuntimeException(e); + } + } + + /** + * Format the given Date object into string. + */ + public String formatDate(Date date) { + return dateFormat.format(date); + } + + /** + * Format the given parameter object into string. + */ + public String parameterToString(Object param) { + if (param == null) { + return ""; + } else if (param instanceof Date) { + return formatDate((Date) param); + } else if (param instanceof Collection) { + StringBuilder b = new StringBuilder(); + for(Object o : (Collection)param) { + if(b.length() > 0) { + b.append(","); + } + b.append(String.valueOf(o)); + } + return b.toString(); + } else { + return String.valueOf(param); + } + } + + /* + Format to {@code Pair} objects. + */ + public List parameterToPairs(String collectionFormat, String name, Object value){ + List params = new ArrayList(); + + // preconditions + if (name == null || name.isEmpty() || value == null) return params; + + Collection valueCollection = null; + if (value instanceof Collection) { + valueCollection = (Collection) value; + } else { + params.add(new Pair(name, parameterToString(value))); + return params; + } + + if (valueCollection.isEmpty()){ + return params; + } + + // get the collection format + collectionFormat = (collectionFormat == null || collectionFormat.isEmpty() ? "csv" : collectionFormat); // default: csv + + // create the params based on the collection format + if (collectionFormat.equals("multi")) { + for (Object item : valueCollection) { + params.add(new Pair(name, parameterToString(item))); + } + + return params; + } + + String delimiter = ","; + + if (collectionFormat.equals("csv")) { + delimiter = ","; + } else if (collectionFormat.equals("ssv")) { + delimiter = " "; + } else if (collectionFormat.equals("tsv")) { + delimiter = "\t"; + } else if (collectionFormat.equals("pipes")) { + delimiter = "|"; + } + + StringBuilder sb = new StringBuilder() ; + for (Object item : valueCollection) { + sb.append(delimiter); + sb.append(parameterToString(item)); + } + + params.add(new Pair(name, sb.substring(1))); + + return params; + } + + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * application/vnd.company+json + * @param mime MIME (Multipurpose Internet Mail Extensions) + * @return True if the given MIME is JSON, false otherwise. + */ + public boolean isJsonMime(String mime) { + String jsonMime = "(?i)^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$"; + return mime != null && (mime.matches(jsonMime) || mime.equals("*/*")); + } + + /** + * Select the Accept header's value from the given accepts array: + * if JSON exists in the given array, use it; + * otherwise use all of them (joining into a string) + * + * @param accepts The accepts array to select from + * @return The Accept header to use. If the given array is empty, + * null will be returned (not to set the Accept header explicitly). + */ + public String selectHeaderAccept(String[] accepts) { + if (accepts.length == 0) { + return null; + } + for (String accept : accepts) { + if (isJsonMime(accept)) { + return accept; + } + } + return StringUtil.join(accepts, ","); + } + + /** + * Select the Content-Type header's value from the given array: + * if JSON exists in the given array, use it; + * otherwise use the first one of the array. + * + * @param contentTypes The Content-Type array to select from + * @return The Content-Type header to use. If the given array is empty, + * JSON will be used. + */ + public String selectHeaderContentType(String[] contentTypes) { + if (contentTypes.length == 0) { + return "application/json"; + } + for (String contentType : contentTypes) { + if (isJsonMime(contentType)) { + return contentType; + } + } + return contentTypes[0]; + } + + /** + * Escape the given string to be used as URL query value. + */ + public String escapeString(String str) { + try { + return URLEncoder.encode(str, "utf8").replaceAll("\\+", "%20"); + } catch (UnsupportedEncodingException e) { + return str; + } + } + + /** + * Serialize the given Java object into string entity according the given + * Content-Type (only JSON is supported for now). + */ + public Entity serialize(Object obj, Map formParams, String contentType) throws ApiException { + Entity entity = null; + if (contentType.startsWith("multipart/form-data")) { + MultipartFormDataOutput multipart = new MultipartFormDataOutput(); + //MultiPart multiPart = new MultiPart(); + for (Entry param: formParams.entrySet()) { + if (param.getValue() instanceof File) { + File file = (File) param.getValue(); + try { + multipart.addFormData(param.getValue().toString(),new FileInputStream(file),MediaType.APPLICATION_OCTET_STREAM_TYPE); + } catch (FileNotFoundException e) { + throw new ApiException("Could not serialize multipart/form-data "+e.getMessage()); + } + } else { + multipart.addFormData(param.getValue().toString(),param.getValue().toString(),MediaType.APPLICATION_OCTET_STREAM_TYPE); + } + } + entity = Entity.entity(multipart.getFormData(), MediaType.MULTIPART_FORM_DATA_TYPE); + } else if (contentType.startsWith("application/x-www-form-urlencoded")) { + Form form = new Form(); + for (Entry param: formParams.entrySet()) { + form.param(param.getKey(), parameterToString(param.getValue())); + } + entity = Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE); + } else { + // We let jersey handle the serialization + entity = Entity.entity(obj, contentType); + } + return entity; + } + + /** + * Deserialize response body to Java object according to the Content-Type. + */ + public T deserialize(Response response, GenericType returnType) throws ApiException { + if (response == null || returnType == null) { + return null; + } + + if ("byte[]".equals(returnType.toString())) { + // Handle binary response (byte array). + return (T) response.readEntity(byte[].class); + } else if (returnType.equals(File.class)) { + // Handle file downloading. + @SuppressWarnings("unchecked") + T file = (T) downloadFileFromResponse(response); + return file; + } + + String contentType = null; + List contentTypes = response.getHeaders().get("Content-Type"); + if (contentTypes != null && !contentTypes.isEmpty()) + contentType = String.valueOf(contentTypes.get(0)); + if (contentType == null) + throw new ApiException(500, "missing Content-Type in response"); + + return response.readEntity(returnType); + } + + /** + * Download file from the given response. + * @throws ApiException If fail to read file content from response and write to disk + */ + public File downloadFileFromResponse(Response response) throws ApiException { + try { + File file = prepareDownloadFile(response); +{{^supportJava6}} + Files.copy(response.readEntity(InputStream.class), file.toPath()); +{{/supportJava6}} +{{#supportJava6}} + // Java6 falls back to commons.io for file copying + FileUtils.copyToFile(response.readEntity(InputStream.class), file); +{{/supportJava6}} + return file; + } catch (IOException e) { + throw new ApiException(e); + } + } + + public File prepareDownloadFile(Response response) throws IOException { + String filename = null; + String contentDisposition = (String) response.getHeaders().getFirst("Content-Disposition"); + if (contentDisposition != null && !"".equals(contentDisposition)) { + // Get filename from the Content-Disposition header. + Pattern pattern = Pattern.compile("filename=['\"]?([^'\"\\s]+)['\"]?"); + Matcher matcher = pattern.matcher(contentDisposition); + if (matcher.find()) + filename = matcher.group(1); + } + + String prefix = null; + String suffix = null; + if (filename == null) { + prefix = "download-"; + suffix = ""; + } else { + int pos = filename.lastIndexOf("."); + if (pos == -1) { + prefix = filename + "-"; + } else { + prefix = filename.substring(0, pos) + "-"; + suffix = filename.substring(pos); + } + // File.createTempFile requires the prefix to be at least three characters long + if (prefix.length() < 3) + prefix = "download-"; + } + + if (tempFolderPath == null) + return File.createTempFile(prefix, suffix); + else + return File.createTempFile(prefix, suffix, new File(tempFolderPath)); + } + + /** + * Invoke API by sending HTTP request with the given options. + * + * @param path The sub-path of the HTTP URL + * @param method The request method, one of "GET", "POST", "PUT", "HEAD" and "DELETE" + * @param queryParams The query parameters + * @param body The request body object + * @param headerParams The header parameters + * @param formParams The form parameters + * @param accept The request's Accept header + * @param contentType The request's Content-Type header + * @param authNames The authentications to apply + * @param returnType The return type into which to deserialize the response + * @return The response body in type of string + */ + public T invokeAPI(String path, String method, List queryParams, Object body, Map headerParams, Map formParams, String accept, String contentType, String[] authNames, GenericType returnType) throws ApiException { + updateParamsForAuth(authNames, queryParams, headerParams); + + // Not using `.target(this.basePath).path(path)` below, + // to support (constant) query string in `path`, e.g. "/posts?draft=1" + WebTarget target = httpClient.target(this.basePath + path); + + if (queryParams != null) { + for (Pair queryParam : queryParams) { + if (queryParam.getValue() != null) { + target = target.queryParam(queryParam.getName(), queryParam.getValue()); + } + } + } + + Invocation.Builder invocationBuilder = target.request(); + + if (accept != null) { + invocationBuilder = invocationBuilder.accept(accept); + } + + for (String key : headerParams.keySet()) { + String value = headerParams.get(key); + if (value != null) { + invocationBuilder = invocationBuilder.header(key, value); + } + } + + for (String key : defaultHeaderMap.keySet()) { + if (!headerParams.containsKey(key)) { + String value = defaultHeaderMap.get(key); + if (value != null) { + invocationBuilder = invocationBuilder.header(key, value); + } + } + } + + Entity entity = serialize(body, formParams, contentType); + + Response response = null; + + if ("GET".equals(method)) { + response = invocationBuilder.get(); + } else if ("POST".equals(method)) { + response = invocationBuilder.post(entity); + } else if ("PUT".equals(method)) { + response = invocationBuilder.put(entity); + } else if ("DELETE".equals(method)) { + response = invocationBuilder.delete(); + } else if ("PATCH".equals(method)) { + response = invocationBuilder.header("X-HTTP-Method-Override", "PATCH").post(entity); + } else if ("HEAD".equals(method)) { + response = invocationBuilder.head(); + } else { + throw new ApiException(500, "unknown method type " + method); + } + + statusCode = response.getStatusInfo().getStatusCode(); + responseHeaders = buildResponseHeaders(response); + + if (response.getStatus() == Status.NO_CONTENT.getStatusCode()) { + return null; + } else if (response.getStatusInfo().getFamily().equals(Status.Family.SUCCESSFUL)) { + if (returnType == null) + return null; + else + return deserialize(response, returnType); + } else { + String message = "error"; + String respBody = null; + if (response.hasEntity()) { + try { + respBody = String.valueOf(response.readEntity(String.class)); + message = respBody; + } catch (RuntimeException e) { + // e.printStackTrace(); + } + } + throw new ApiException( + response.getStatus(), + message, + buildResponseHeaders(response), + respBody); + } + } + + /** + * Build the Client used to make HTTP requests. + */ + private Client buildHttpClient(boolean debugging) { + final ClientConfiguration clientConfig = new ClientConfiguration(ResteasyProviderFactory.getInstance()); + clientConfig.register(json); + if(debugging){ + clientConfig.register(Logger.class); + } + return ClientBuilder.newClient(clientConfig); + } + private Map> buildResponseHeaders(Response response) { + Map> responseHeaders = new HashMap>(); + for (Entry> entry: response.getHeaders().entrySet()) { + List values = entry.getValue(); + List headers = new ArrayList(); + for (Object o : values) { + headers.add(String.valueOf(o)); + } + responseHeaders.put(entry.getKey(), headers); + } + return responseHeaders; + } + + /** + * Update query and header parameters based on authentication settings. + * + * @param authNames The authentications to apply + */ + private void updateParamsForAuth(String[] authNames, List queryParams, Map headerParams) { + for (String authName : authNames) { + Authentication auth = authentications.get(authName); + if (auth == null) throw new RuntimeException("Authentication undefined: " + authName); + auth.applyToParams(queryParams, headerParams); + } + } +} diff --git a/mustacheTemplates/libraries/resteasy/JSON.mustache b/mustacheTemplates/libraries/resteasy/JSON.mustache new file mode 100644 index 0000000000..27d2aa65d2 --- /dev/null +++ b/mustacheTemplates/libraries/resteasy/JSON.mustache @@ -0,0 +1,48 @@ +package {{invokerPackage}}; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.*; +{{#java8}} +import com.fasterxml.jackson.datatype.jsr310.*; +{{/java8}} +{{^java8}} +import com.fasterxml.jackson.datatype.joda.*; +{{/java8}} + +import java.text.DateFormat; + +import javax.ws.rs.ext.ContextResolver; + +{{>generatedAnnotation}} +public class JSON implements ContextResolver { + private ObjectMapper mapper; + + public JSON() { + mapper = new ObjectMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + mapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false); + mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING); + mapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING); + mapper.setDateFormat(new RFC3339DateFormat()); + {{#java8}} + mapper.registerModule(new JavaTimeModule()); + {{/java8}} + {{^java8}} + mapper.registerModule(new JodaModule()); + {{/java8}} + } + + /** + * Set the date format for JSON (de)serialization with Date properties. + */ + public void setDateFormat(DateFormat dateFormat) { + mapper.setDateFormat(dateFormat); + } + + @Override + public ObjectMapper getContext(Class type) { + return mapper; + } +} diff --git a/mustacheTemplates/libraries/resteasy/api.mustache b/mustacheTemplates/libraries/resteasy/api.mustache new file mode 100644 index 0000000000..0016ae8779 --- /dev/null +++ b/mustacheTemplates/libraries/resteasy/api.mustache @@ -0,0 +1,111 @@ +package {{package}}; + +import {{invokerPackage}}.ApiException; +import {{invokerPackage}}.ApiClient; +import {{invokerPackage}}.Configuration; +import {{invokerPackage}}.Pair; + +import javax.ws.rs.core.GenericType; + +{{#imports}}import {{import}}; +{{/imports}} + +{{^fullJavaUtil}} +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +{{/fullJavaUtil}} + +{{>generatedAnnotation}} +{{#operations}} +public class {{classname}} { + private ApiClient {{localVariablePrefix}}apiClient; + + public {{classname}}() { + this(Configuration.getDefaultApiClient()); + } + + public {{classname}}(ApiClient apiClient) { + this.{{localVariablePrefix}}apiClient = apiClient; + } + + public ApiClient getApiClient() { + return {{localVariablePrefix}}apiClient; + } + + public void setApiClient(ApiClient apiClient) { + this.{{localVariablePrefix}}apiClient = apiClient; + } + + {{#operation}} + {{#contents}} + /** + * {{summary}} + * {{notes}}{{#parameters}} + * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{/parameters}}{{#returnType}} + * @return {{{returnType}}}{{/returnType}} + * @throws ApiException if fails to make API call + {{#isDeprecated}} + * @deprecated + {{/isDeprecated}} + {{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation + {{/externalDocs}} + */ + {{#isDeprecated}} + @Deprecated + {{/isDeprecated}} + public {{#returnType}}{{{returnType}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{operationId}}({{#parameters}}{{{dataType}}} {{paramName}}{{#has this 'more'}}, {{/has}}{{/parameters}}) throws ApiException { + Object {{localVariablePrefix}}localVarPostBody = {{#bodyParam}}{{paramName}}{{/bodyParam}}{{^bodyParam}}null{{/bodyParam}}; + {{#parameters}}{{#required}} + // verify the required parameter '{{paramName}}' is set + if ({{paramName}} == null) { + throw new ApiException(400, "Missing the required parameter '{{paramName}}' when calling {{operationId}}"); + } + {{/required}}{{/parameters}} + // create path and map variables + String {{localVariablePrefix}}localVarPath = "{{{path}}}".replaceAll("\\{format\\}","json"){{#pathParams}} + .replaceAll("\\{" + "{{baseName}}" + "\\}", {{localVariablePrefix}}apiClient.escapeString({{{paramName}}}.toString())){{/pathParams}}; + + // query params + {{javaUtilPrefix}}List {{localVariablePrefix}}localVarQueryParams = new {{javaUtilPrefix}}ArrayList(); + {{javaUtilPrefix}}Map {{localVariablePrefix}}localVarHeaderParams = new {{javaUtilPrefix}}HashMap(); + {{javaUtilPrefix}}Map {{localVariablePrefix}}localVarFormParams = new {{javaUtilPrefix}}HashMap(); + + {{#queryParams}} + {{localVariablePrefix}}localVarQueryParams.addAll({{localVariablePrefix}}apiClient.parameterToPairs("{{#collectionFormat}}{{{collectionFormat}}}{{/collectionFormat}}", "{{baseName}}", {{paramName}})); + {{/queryParams}} + + {{#headerParams}}if ({{paramName}} != null) + {{localVariablePrefix}}localVarHeaderParams.put("{{baseName}}", {{localVariablePrefix}}apiClient.parameterToString({{paramName}})); + {{/headerParams}} + + {{#formParams}}if ({{paramName}} != null) + {{localVariablePrefix}}localVarFormParams.put("{{baseName}}", {{paramName}}); + {{/formParams}} + + final String[] {{localVariablePrefix}}localVarAccepts = { + {{#produces}}"{{{mediaType}}}"{{#hasMore}}, {{/hasMore}}{{/produces}} + }; + final String {{localVariablePrefix}}localVarAccept = {{localVariablePrefix}}apiClient.selectHeaderAccept({{localVariablePrefix}}localVarAccepts); + + final String[] {{localVariablePrefix}}localVarContentTypes = { + {{#consumes}}"{{{mediaType}}}"{{#hasMore}}, {{/hasMore}}{{/consumes}} + }; + final String {{localVariablePrefix}}localVarContentType = {{localVariablePrefix}}apiClient.selectHeaderContentType({{localVariablePrefix}}localVarContentTypes); + + String[] {{localVariablePrefix}}localVarAuthNames = new String[] { {{#authMethods}}"{{name}}"{{#has this 'more'}}, {{/has}}{{/authMethods}} }; + + {{#returnType}} + GenericType<{{{returnType}}}> {{localVariablePrefix}}localVarReturnType = new GenericType<{{{returnType}}}>() {}; + return {{localVariablePrefix}}apiClient.invokeAPI({{localVariablePrefix}}localVarPath, "{{httpMethod}}", {{localVariablePrefix}}localVarQueryParams, {{localVariablePrefix}}localVarPostBody, {{localVariablePrefix}}localVarHeaderParams, {{localVariablePrefix}}localVarFormParams, {{localVariablePrefix}}localVarAccept, {{localVariablePrefix}}localVarContentType, {{localVariablePrefix}}localVarAuthNames, {{localVariablePrefix}}localVarReturnType); + {{/returnType}}{{^returnType}} + {{localVariablePrefix}}apiClient.invokeAPI({{localVariablePrefix}}localVarPath, "{{httpMethod}}", {{localVariablePrefix}}localVarQueryParams, {{localVariablePrefix}}localVarPostBody, {{localVariablePrefix}}localVarHeaderParams, {{localVariablePrefix}}localVarFormParams, {{localVariablePrefix}}localVarAccept, {{localVariablePrefix}}localVarContentType, {{localVariablePrefix}}localVarAuthNames, null); + {{/returnType}} + } + {{/contents}} + {{/operation}} +} +{{/operations}} diff --git a/mustacheTemplates/libraries/resteasy/build.gradle.mustache b/mustacheTemplates/libraries/resteasy/build.gradle.mustache new file mode 100644 index 0000000000..cb4a9f8e61 --- /dev/null +++ b/mustacheTemplates/libraries/resteasy/build.gradle.mustache @@ -0,0 +1,152 @@ +apply plugin: 'idea' +apply plugin: 'eclipse' + +group = '{{groupId}}' +version = '{{artifactVersion}}' + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.5.+' + classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3' + } +} + +repositories { + jcenter() +} + + +if(hasProperty('target') && target == 'android') { + + apply plugin: 'com.android.library' + apply plugin: 'com.github.dcendents.android-maven' + + android { + compileSdkVersion 23 + buildToolsVersion '23.0.2' + defaultConfig { + minSdkVersion 14 + targetSdkVersion 23 + } + compileOptions { + {{#java8}} + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + {{/java8}} + {{^java8}} + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 + {{/java8}} + } + + // Rename the aar correctly + libraryVariants.all { variant -> + variant.outputs.each { output -> + def outputFile = output.outputFile + if (outputFile != null && outputFile.name.endsWith('.aar')) { + def fileName = "${project.name}-${variant.baseName}-${version}.aar" + output.outputFile = new File(outputFile.parent, fileName) + } + } + } + + dependencies { + provided 'javax.annotation:jsr250-api:1.0' + } + } + + afterEvaluate { + android.libraryVariants.all { variant -> + def task = project.tasks.create "jar${variant.name.capitalize()}", Jar + task.description = "Create jar artifact for ${variant.name}" + task.dependsOn variant.javaCompile + task.from variant.javaCompile.destinationDir + task.destinationDir = project.file("${project.buildDir}/outputs/jar") + task.archiveName = "${project.name}-${variant.baseName}-${version}.jar" + artifacts.add('archives', task); + } + } + + task sourcesJar(type: Jar) { + from android.sourceSets.main.java.srcDirs + classifier = 'sources' + } + + artifacts { + archives sourcesJar + } + +} else { + + apply plugin: 'java' + apply plugin: 'maven' + {{#java8}} + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + {{/java8}} + {{^java8}} + sourceCompatibility = JavaVersion.VERSION_1_7 + targetCompatibility = JavaVersion.VERSION_1_7 + {{/java8}} + + install { + repositories.mavenInstaller { + pom.artifactId = '{{artifactId}}' + } + } + + task execute(type:JavaExec) { + main = System.getProperty('mainClass') + classpath = sourceSets.main.runtimeClasspath + } +} + +ext { + {{#useOas2}} + swagger_annotations_version = "1.5.8" + {{/useOas2}} + {{^useOas2}} + swagger_annotations_version = "2.0.0" + {{/useOas2}} + jackson_version = "2.7.5" + jersey_version = "2.22.2" + {{^java8}} + jodatime_version = "2.9.4" + {{/java8}} + {{#supportJava6}} + commons_io_version=2.5 + commons_lang3_version=3.5 + {{/supportJava6}} + junit_version = "4.12" +} + +dependencies { + {{#useOas2}} + compile "io.swagger:swagger-annotations:$swagger_annotations_version" + {{/useOas2}} + {{^useOas2}} + compile "io.swagger.core.v3:swagger-annotations:$swagger_annotations_version" + {{/useOas2}} + compile "org.glassfish.jersey.core:jersey-client:$jersey_version" + compile "org.glassfish.jersey.media:jersey-media-multipart:$jersey_version" + compile "org.glassfish.jersey.media:jersey-media-json-jackson:$jersey_version" + compile "com.fasterxml.jackson.core:jackson-core:$jackson_version" + compile "com.fasterxml.jackson.core:jackson-annotations:$jackson_version" + compile "com.fasterxml.jackson.core:jackson-databind:$jackson_version" + {{#java8}} + compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jackson_version" + {{/java8}} + {{^java8}} + compile "com.fasterxml.jackson.datatype:jackson-datatype-joda:$jackson_version" + compile "joda-time:joda-time:$jodatime_version" + compile "com.brsanthu:migbase64:2.2" + {{/java8}} + {{#supportJava6}} + compile "commons-io:commons-io:$commons_io_version" + compile "org.apache.commons:commons-lang3:$commons_lang3_version" + {{/supportJava6}} + testCompile "junit:junit:$junit_version" +} diff --git a/mustacheTemplates/libraries/resteasy/build.sbt.mustache b/mustacheTemplates/libraries/resteasy/build.sbt.mustache new file mode 100644 index 0000000000..b3d4a35843 --- /dev/null +++ b/mustacheTemplates/libraries/resteasy/build.sbt.mustache @@ -0,0 +1,39 @@ +lazy val root = (project in file(".")). + settings( + organization := "{{groupId}}", + name := "{{artifactId}}", + version := "{{artifactVersion}}", + scalaVersion := "2.11.4", + scalacOptions ++= Seq("-feature"), + javacOptions in compile ++= Seq("-Xlint:deprecation"), + publishArtifact in (Compile, packageDoc) := false, + resolvers += Resolver.mavenLocal, + libraryDependencies ++= Seq( + {{#useOas2}} + "io.swagger" % "swagger-annotations" % "1.5.8", + {{/useOas2}} + {{^useOas2}} + "io.swagger.core.v3" % "swagger-annotations" % "2.0.0", + {{/useOas2}} + "org.glassfish.jersey.core" % "jersey-client" % "2.22.2", + "org.glassfish.jersey.media" % "jersey-media-multipart" % "2.22.2", + "org.glassfish.jersey.media" % "jersey-media-json-jackson" % "2.22.2", + "com.fasterxml.jackson.core" % "jackson-core" % "2.7.5", + "com.fasterxml.jackson.core" % "jackson-annotations" % "2.7.5", + "com.fasterxml.jackson.core" % "jackson-databind" % "2.7.5", + {{#java8}} + "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.7.5", + {{/java8}} + {{^java8}} + "com.fasterxml.jackson.datatype" % "jackson-datatype-joda" % "2.7.5", + "joda-time" % "joda-time" % "2.9.4", + "com.brsanthu" % "migbase64" % "2.2", + {{/java8}} + {{#supportJava6}} + "org.apache.commons" % "commons-lang3" % "3.5", + "commons-io" % "commons-io" % "2.5", + {{/supportJava6}} + "junit" % "junit" % "4.12" % "test", + "com.novocode" % "junit-interface" % "0.10" % "test" + ) + ) diff --git a/mustacheTemplates/libraries/resteasy/pom.mustache b/mustacheTemplates/libraries/resteasy/pom.mustache new file mode 100644 index 0000000000..1d60815222 --- /dev/null +++ b/mustacheTemplates/libraries/resteasy/pom.mustache @@ -0,0 +1,254 @@ + + 4.0.0 + {{groupId}} + {{artifactId}} + jar + {{artifactId}} + {{artifactVersion}} + + scm:git:git@github.com:swagger-api/swagger-mustache.git + scm:git:git@github.com:swagger-api/swagger-codegen.git + https://github.com/swagger-api/swagger-codegen + + + 2.2.0 + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.12 + + + + loggerPath + conf/log4j.properties + + + -Xms512m -Xmx1500m + methods + pertest + + + + maven-dependency-plugin + + + package + + copy-dependencies + + + ${project.build.directory}/lib + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.6 + + + + jar + test-jar + + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add_sources + generate-sources + + add-source + + + + src/main/java + + + + + add_test_sources + generate-test-sources + + add-test-source + + + + src/test/java + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.5.1 + + {{#java8}} + 1.8 + 1.8 + {{/java8}} + {{^java8}} + 1.7 + 1.7 + {{/java8}} + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + + + + {{#useOas2}} + + io.swagger + swagger-annotations + ${swagger-core-version} + + {{/useOas2}} + {{^useOas2}} + + io.swagger.core.v3 + swagger-annotations + ${swagger-core-version} + + {{/useOas2}} + + + org.jboss.resteasy + resteasy-client + ${resteasy-version} + + + org.jboss.resteasy + resteasy-multipart-provider + ${resteasy-version} + + + + com.fasterxml.jackson.core + jackson-core + ${jackson-version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson-version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson-version} + + {{#withXml}} + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + ${jackson-version} + + + {{/withXml}} + {{#java8}} + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson-version} + + {{/java8}} + {{^java8}} + + com.fasterxml.jackson.datatype + jackson-datatype-joda + ${jackson-version} + + + joda-time + joda-time + ${jodatime-version} + + + + + com.brsanthu + migbase64 + 2.2 + + {{/java8}} + + {{#supportJava6}} + + org.apache.commons + commons-lang3 + ${commons_lang3_version} + + + + commons-io + commons-io + ${commons_io_version} + + {{/supportJava6}} + + org.jboss.resteasy + resteasy-jackson-provider + 3.1.3.Final + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson-version} + + + com.github.joschi.jackson + jackson-datatype-threetenbp + ${jackson-version} + + + + junit + junit + ${junit-version} + test + + + + {{#useOas2}} + 1.5.15 + {{/useOas2}} + {{^useOas2}} + 2.0.0 + {{/useOas2}} + 3.1.3.Final + 2.6.4 + {{^java8}} + 2.9.9 + {{/java8}} + {{#supportJava6}} + 2.5 + 3.6 + {{/supportJava6}} + 1.0.0 + 4.12 + + diff --git a/mustacheTemplates/libraries/resttemplate/ApiClient.mustache b/mustacheTemplates/libraries/resttemplate/ApiClient.mustache new file mode 100644 index 0000000000..7b4cf2abad --- /dev/null +++ b/mustacheTemplates/libraries/resttemplate/ApiClient.mustache @@ -0,0 +1,676 @@ +package {{invokerPackage}}; + +{{#withXml}} +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator; +{{/withXml}} +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpRequest; +import org.springframework.http.HttpStatus; +import org.springframework.http.InvalidMediaTypeException; +import org.springframework.http.MediaType; +import org.springframework.http.RequestEntity; +import org.springframework.http.RequestEntity.BodyBuilder; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.BufferingClientHttpRequestFactory; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; +{{#withXml}} +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; +{{/withXml}} +import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; +{{#threetenbp}} +import org.threeten.bp.*; +import com.fasterxml.jackson.datatype.threetenbp.ThreeTenModule; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter; +import com.fasterxml.jackson.databind.ObjectMapper; +{{/threetenbp}} + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.text.DateFormat; +import java.text.ParseException; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TimeZone; + +import {{invokerPackage}}.auth.Authentication; +import {{invokerPackage}}.auth.HttpBasicAuth; +import {{invokerPackage}}.auth.ApiKeyAuth; +import {{invokerPackage}}.auth.OAuth; + +{{>generatedAnnotation}} +@Component("{{invokerPackage}}.ApiClient") +public class ApiClient { + public enum CollectionFormat { + CSV(","), TSV("\t"), SSV(" "), PIPES("|"), MULTI(null); + + private final String separator; + private CollectionFormat(String separator) { + this.separator = separator; + } + + private String collectionToString(Collection collection) { + return StringUtils.collectionToDelimitedString(collection, separator); + } + } + + private boolean debugging = false; + + private HttpHeaders defaultHeaders = new HttpHeaders(); + + private String basePath = "{{basePath}}"; + + private RestTemplate restTemplate; + + private Map authentications; + + private HttpStatus statusCode; + private MultiValueMap responseHeaders; + + private DateFormat dateFormat; + + public ApiClient() { + this.restTemplate = buildRestTemplate(); + init(); + } + + @Autowired + public ApiClient(RestTemplate restTemplate) { + this.restTemplate = restTemplate; + init(); + } + + protected void init() { + // Use RFC3339 format for date and datetime. + // See http://xml2rfc.ietf.org/public/rfc/html/rfc3339.html#anchor14 + this.dateFormat = new RFC3339DateFormat(); + + // Use UTC as the default time zone. + this.dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + + // Set default User-Agent. + setUserAgent("Java-SDK"); + + // Setup authentications (key: authentication name, value: authentication). + authentications = new HashMap();{{#authMethods}}{{#is this 'basic'}} + authentications.put("{{name}}", new HttpBasicAuth());{{/is}}{{#is this 'api-key'}} + authentications.put("{{name}}", new ApiKeyAuth({{#is this 'key-in-header'}}"header"{{/is}}{{#isNot this 'key-in-header'}}"query"{{/isNot}}, "{{keyParamName}}"));{{/is}}{{#is this 'oauth'}} + authentications.put("{{name}}", new OAuth());{{/is}}{{#is this 'bearer'}} + authentications.put("{{name}}", new OAuth());{{/is}}{{/authMethods}} + // Prevent the authentications from being modified. + authentications = Collections.unmodifiableMap(authentications); + } + + /** + * Get the current base path + * @return String the base path + */ + public String getBasePath() { + return basePath; + } + + /** + * Set the base path, which should include the host + * @param basePath the base path + * @return ApiClient this client + */ + public ApiClient setBasePath(String basePath) { + this.basePath = basePath; + return this; + } + + /** + * Gets the status code of the previous request + * @return HttpStatus the status code + */ + public HttpStatus getStatusCode() { + return statusCode; + } + + /** + * Gets the response headers of the previous request + * @return MultiValueMap a map of response headers + */ + public MultiValueMap getResponseHeaders() { + return responseHeaders; + } + + /** + * Get authentications (key: authentication name, value: authentication). + * @return Map the currently configured authentication types + */ + public Map getAuthentications() { + return authentications; + } + + /** + * Get authentication for the given name. + * + * @param authName The authentication name + * @return The authentication, null if not found + */ + public Authentication getAuthentication(String authName) { + return authentications.get(authName); + } + + /** + * Helper method to set username for the first HTTP basic authentication. + * @param username the username + */ + public void setUsername(String username) { + for (Authentication auth : authentications.values()) { + if (auth instanceof HttpBasicAuth) { + ((HttpBasicAuth) auth).setUsername(username); + return; + } + } + throw new RuntimeException("No HTTP basic authentication configured!"); + } + + /** + * Helper method to set password for the first HTTP basic authentication. + * @param password the password + */ + public void setPassword(String password) { + for (Authentication auth : authentications.values()) { + if (auth instanceof HttpBasicAuth) { + ((HttpBasicAuth) auth).setPassword(password); + return; + } + } + throw new RuntimeException("No HTTP basic authentication configured!"); + } + + /** + * Helper method to set API key value for the first API key authentication. + * @param apiKey the API key + */ + public void setApiKey(String apiKey) { + for (Authentication auth : authentications.values()) { + if (auth instanceof ApiKeyAuth) { + ((ApiKeyAuth) auth).setApiKey(apiKey); + return; + } + } + throw new RuntimeException("No API key authentication configured!"); + } + + /** + * Helper method to set API key prefix for the first API key authentication. + * @param apiKeyPrefix the API key prefix + */ + public void setApiKeyPrefix(String apiKeyPrefix) { + for (Authentication auth : authentications.values()) { + if (auth instanceof ApiKeyAuth) { + ((ApiKeyAuth) auth).setApiKeyPrefix(apiKeyPrefix); + return; + } + } + throw new RuntimeException("No API key authentication configured!"); + } + + /** + * Helper method to set access token for the first OAuth2 authentication. + * @param accessToken the access token + */ + public void setAccessToken(String accessToken) { + for (Authentication auth : authentications.values()) { + if (auth instanceof OAuth) { + ((OAuth) auth).setAccessToken(accessToken); + return; + } + } + throw new RuntimeException("No OAuth2 authentication configured!"); + } + + /** + * Set the User-Agent header's value (by adding to the default header map). + * @param userAgent the user agent string + * @return ApiClient this client + */ + public ApiClient setUserAgent(String userAgent) { + addDefaultHeader("User-Agent", userAgent); + return this; + } + + /** + * Add a default header. + * + * @param name The header's name + * @param value The header's value + * @return ApiClient this client + */ + public ApiClient addDefaultHeader(String name, String value) { + defaultHeaders.add(name, value); + return this; + } + + public void setDebugging(boolean debugging) { + List currentInterceptors = this.restTemplate.getInterceptors(); + if(debugging) { + if (currentInterceptors == null) { + currentInterceptors = new ArrayList(); + } + ClientHttpRequestInterceptor interceptor = new ApiClientHttpRequestInterceptor(); + currentInterceptors.add(interceptor); + this.restTemplate.setInterceptors(currentInterceptors); + } else { + if (currentInterceptors != null && !currentInterceptors.isEmpty()) { + Iterator iter = currentInterceptors.iterator(); + while (iter.hasNext()) { + ClientHttpRequestInterceptor interceptor = iter.next(); + if (interceptor instanceof ApiClientHttpRequestInterceptor) { + iter.remove(); + } + } + this.restTemplate.setInterceptors(currentInterceptors); + } + } + this.debugging = debugging; + } + + /** + * Check that whether debugging is enabled for this API client. + * @return boolean true if this client is enabled for debugging, false otherwise + */ + public boolean isDebugging() { + return debugging; + } + + /** + * Get the date format used to parse/format date parameters. + * @return DateFormat format + */ + public DateFormat getDateFormat() { + return dateFormat; + } + + /** + * Set the date format used to parse/format date parameters. + * @param dateFormat Date format + * @return API client + */ + public ApiClient setDateFormat(DateFormat dateFormat) { + this.dateFormat = dateFormat; + {{#threetenbp}} + for(HttpMessageConverter converter:restTemplate.getMessageConverters()){ + if(converter instanceof AbstractJackson2HttpMessageConverter){ + ObjectMapper mapper = ((AbstractJackson2HttpMessageConverter)converter).getObjectMapper(); + mapper.setDateFormat(dateFormat); + } + } + {{/threetenbp}} + return this; + } + + /** + * Parse the given string into Date object. + */ + public Date parseDate(String str) { + try { + return dateFormat.parse(str); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } + + /** + * Format the given Date object into string. + */ + public String formatDate(Date date) { + return dateFormat.format(date); + } + + /** + * Format the given parameter object into string. + * @param param the object to convert + * @return String the parameter represented as a String + */ + public String parameterToString(Object param) { + if (param == null) { + return ""; + } else if (param instanceof Date) { + return formatDate( (Date) param); + } else if (param instanceof Collection) { + StringBuilder b = new StringBuilder(); + for(Object o : (Collection) param) { + if(b.length() > 0) { + b.append(","); + } + b.append(String.valueOf(o)); + } + return b.toString(); + } else { + return String.valueOf(param); + } + } + + /** + * Converts a parameter to a {@link MultiValueMap} for use in REST requests + * @param collectionFormat The format to convert to + * @param name The name of the parameter + * @param value The parameter's value + * @return a Map containing the String value(s) of the input parameter + */ + public MultiValueMap parameterToMultiValueMap(CollectionFormat collectionFormat, String name, Object value) { + final MultiValueMap params = new LinkedMultiValueMap(); + + if (name == null || name.isEmpty() || value == null) { + return params; + } + + if(collectionFormat == null) { + collectionFormat = CollectionFormat.CSV; + } + + Collection valueCollection = null; + if (value instanceof Collection) { + valueCollection = (Collection) value; + } else { + params.add(name, parameterToString(value)); + return params; + } + + if (valueCollection.isEmpty()){ + return params; + } + + if (collectionFormat.equals(CollectionFormat.MULTI)) { + for (Object item : valueCollection) { + params.add(name, parameterToString(item)); + } + return params; + } + + List values = new ArrayList(); + for(Object o : valueCollection) { + values.add(parameterToString(o)); + } + params.add(name, collectionFormat.collectionToString(values)); + + return params; + } + + /** + * Check if the given {@code String} is a JSON MIME. + * @param mediaType the input MediaType + * @return boolean true if the MediaType represents JSON, false otherwise + */ + public boolean isJsonMime(String mediaType) { + // "* / *" is default to JSON + if ("*/*".equals(mediaType)) { + return true; + } + + try { + return isJsonMime(MediaType.parseMediaType(mediaType)); + } catch (InvalidMediaTypeException e) { + } + return false; + } + + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * @param mediaType the input MediaType + * @return boolean true if the MediaType represents JSON, false otherwise + */ + public boolean isJsonMime(MediaType mediaType) { + return mediaType != null && (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType) || mediaType.getSubtype().matches("^.*\\+json[;]?\\s*$")); + } + + /** + * Select the Accept header's value from the given accepts array: + * if JSON exists in the given array, use it; + * otherwise use all of them (joining into a string) + * + * @param accepts The accepts array to select from + * @return List The list of MediaTypes to use for the Accept header + */ + public List selectHeaderAccept(String[] accepts) { + if (accepts.length == 0) { + return null; + } + for (String accept : accepts) { + MediaType mediaType = MediaType.parseMediaType(accept); + if (isJsonMime(mediaType)) { + return Collections.singletonList(mediaType); + } + } + return MediaType.parseMediaTypes(StringUtils.arrayToCommaDelimitedString(accepts)); + } + + /** + * Select the Content-Type header's value from the given array: + * if JSON exists in the given array, use it; + * otherwise use the first one of the array. + * + * @param contentTypes The Content-Type array to select from + * @return MediaType The Content-Type header to use. If the given array is empty, JSON will be used. + */ + public MediaType selectHeaderContentType(String[] contentTypes) { + if (contentTypes.length == 0) { + return MediaType.APPLICATION_JSON; + } + for (String contentType : contentTypes) { + MediaType mediaType = MediaType.parseMediaType(contentType); + if (isJsonMime(mediaType)) { + return mediaType; + } + } + return MediaType.parseMediaType(contentTypes[0]); + } + + /** + * Select the body to use for the request + * @param obj the body object + * @param formParams the form parameters + * @param contentType the content type of the request + * @return Object the selected body + */ + protected Object selectBody(Object obj, MultiValueMap formParams, MediaType contentType) { + boolean isForm = MediaType.MULTIPART_FORM_DATA.isCompatibleWith(contentType) || MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(contentType); + return isForm ? formParams : obj; + } + + /** + * Invoke API by sending HTTP request with the given options. + * + * @param the return type to use + * @param path The sub-path of the HTTP URL + * @param method The request method + * @param queryParams The query parameters + * @param body The request body object + * @param headerParams The header parameters + * @param formParams The form parameters + * @param accept The request's Accept header + * @param contentType The request's Content-Type header + * @param authNames The authentications to apply + * @param returnType The return type into which to deserialize the response + * @return The response body in chosen type + */ + public T invokeAPI(String path, HttpMethod method, MultiValueMap queryParams, Object body, HttpHeaders headerParams, MultiValueMap formParams, List accept, MediaType contentType, String[] authNames, ParameterizedTypeReference returnType) throws RestClientException { + updateParamsForAuth(authNames, queryParams, headerParams); + + final UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(basePath).path(path); + if (queryParams != null) { + builder.queryParams(queryParams); + } + + final BodyBuilder requestBuilder = RequestEntity.method(method, builder.build().toUri()); + if(accept != null) { + requestBuilder.accept(accept.toArray(new MediaType[accept.size()])); + } + if(contentType != null) { + requestBuilder.contentType(contentType); + } + + addHeadersToRequest(headerParams, requestBuilder); + addHeadersToRequest(defaultHeaders, requestBuilder); + + RequestEntity requestEntity = requestBuilder.body(selectBody(body, formParams, contentType)); + + ResponseEntity responseEntity = restTemplate.exchange(requestEntity, returnType); + + statusCode = responseEntity.getStatusCode(); + responseHeaders = responseEntity.getHeaders(); + + if (responseEntity.getStatusCode() == HttpStatus.NO_CONTENT) { + return null; + } else if (responseEntity.getStatusCode().is2xxSuccessful()) { + if (returnType == null) { + return null; + } + return responseEntity.getBody(); + } else { + // The error handler built into the RestTemplate should handle 400 and 500 series errors. + throw new RestClientException("API returned " + statusCode + " and it wasn't handled by the RestTemplate error handler"); + } + } + + /** + * Add headers to the request that is being built + * @param headers The headers to add + * @param requestBuilder The current request + */ + protected void addHeadersToRequest(HttpHeaders headers, BodyBuilder requestBuilder) { + for (Entry> entry : headers.entrySet()) { + List values = entry.getValue(); + for(String value : values) { + if (value != null) { + requestBuilder.header(entry.getKey(), value); + } + } + } + } + + /** + * Build the RestTemplate used to make HTTP requests. + * @return RestTemplate + */ + protected RestTemplate buildRestTemplate() { + {{#withXml}}List> messageConverters = new ArrayList>(); + messageConverters.add(new MappingJackson2HttpMessageConverter()); + XmlMapper xmlMapper = new XmlMapper(); + xmlMapper.configure(ToXmlGenerator.Feature.WRITE_XML_DECLARATION, true); + messageConverters.add(new MappingJackson2XmlHttpMessageConverter(xmlMapper)); + + RestTemplate restTemplate = new RestTemplate(messageConverters); + {{/withXml}}{{^withXml}}RestTemplate restTemplate = new RestTemplate();{{/withXml}} + {{#threetenbp}} + for(HttpMessageConverter converter:restTemplate.getMessageConverters()){ + if(converter instanceof AbstractJackson2HttpMessageConverter){ + ObjectMapper mapper = ((AbstractJackson2HttpMessageConverter)converter).getObjectMapper(); + ThreeTenModule module = new ThreeTenModule(); + module.addDeserializer(Instant.class, CustomInstantDeserializer.INSTANT); + module.addDeserializer(OffsetDateTime.class, CustomInstantDeserializer.OFFSET_DATE_TIME); + module.addDeserializer(ZonedDateTime.class, CustomInstantDeserializer.ZONED_DATE_TIME); + mapper.registerModule(module); + } + } + {{/threetenbp}} + // This allows us to read the response more than once - Necessary for debugging. + restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(restTemplate.getRequestFactory())); + return restTemplate; + } + + /** + * Update query and header parameters based on authentication settings. + * + * @param authNames The authentications to apply + * @param queryParams The query parameters + * @param headerParams The header parameters + */ + private void updateParamsForAuth(String[] authNames, MultiValueMap queryParams, HttpHeaders headerParams) { + for (String authName : authNames) { + Authentication auth = authentications.get(authName); + if (auth == null) { + throw new RestClientException("Authentication undefined: " + authName); + } + auth.applyToParams(queryParams, headerParams); + } + } + + private class ApiClientHttpRequestInterceptor implements ClientHttpRequestInterceptor { + private final Log log = LogFactory.getLog(ApiClientHttpRequestInterceptor.class); + + @Override + public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { + logRequest(request, body); + ClientHttpResponse response = execution.execute(request, body); + logResponse(response); + return response; + } + + private void logRequest(HttpRequest request, byte[] body) throws UnsupportedEncodingException { + log.info("URI: " + request.getURI()); + log.info("HTTP Method: " + request.getMethod()); + log.info("HTTP Headers: " + headersToString(request.getHeaders())); + log.info("Request Body: " + new String(body, StandardCharsets.UTF_8)); + } + + private void logResponse(ClientHttpResponse response) throws IOException { + log.info("HTTP Status Code: " + response.getRawStatusCode()); + log.info("Status Text: " + response.getStatusText()); + log.info("HTTP Headers: " + headersToString(response.getHeaders())); + log.info("Response Body: " + bodyToString(response.getBody())); + } + + private String headersToString(HttpHeaders headers) { + StringBuilder builder = new StringBuilder(); + for(Entry> entry : headers.entrySet()) { + builder.append(entry.getKey()).append("=["); + for(String value : entry.getValue()) { + builder.append(value).append(","); + } + builder.setLength(builder.length() - 1); // Get rid of trailing comma + builder.append("],"); + } + builder.setLength(builder.length() - 1); // Get rid of trailing comma + return builder.toString(); + } + + private String bodyToString(InputStream body) throws IOException { + StringBuilder builder = new StringBuilder(); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(body, StandardCharsets.UTF_8)); + String line = bufferedReader.readLine(); + while (line != null) { + builder.append(line).append(System.lineSeparator()); + line = bufferedReader.readLine(); + } + bufferedReader.close(); + return builder.toString(); + } + } +} diff --git a/mustacheTemplates/libraries/resttemplate/api.mustache b/mustacheTemplates/libraries/resttemplate/api.mustache new file mode 100644 index 0000000000..830bac6037 --- /dev/null +++ b/mustacheTemplates/libraries/resttemplate/api.mustache @@ -0,0 +1,127 @@ +package {{package}}; + +import {{invokerPackage}}.ApiClient; + +{{#imports}}import {{import}}; +{{/imports}} + +{{^fullJavaUtil}}import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map;{{/fullJavaUtil}} + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.util.UriComponentsBuilder; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.core.io.FileSystemResource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +{{>generatedAnnotation}} +@Component("{{package}}.{{classname}}") +{{#operations}} +public class {{classname}} { + private ApiClient {{localVariablePrefix}}apiClient; + + public {{classname}}() { + this(new ApiClient()); + } + + @Autowired + public {{classname}}(ApiClient apiClient) { + this.{{localVariablePrefix}}apiClient = apiClient; + } + + public ApiClient getApiClient() { + return {{localVariablePrefix}}apiClient; + } + + public void setApiClient(ApiClient apiClient) { + this.{{localVariablePrefix}}apiClient = apiClient; + } + + {{#operation}} + {{#contents}} + /** + * {{summary}} + * {{notes}} + {{#responses}} + *

{{code}}{{#message}} - {{message}}{{/message}} + {{/responses}} + {{#parameters}} + * @param {{paramName}} {{description}}{{^description}}The {{paramName}} parameter{{/description}} + {{/parameters}} + {{#returnType}} + * @return {{returnType}} + {{/returnType}} + * @throws RestClientException if an error occurs while attempting to invoke the API + {{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation + {{/externalDocs}} + */ + public {{#returnType}}{{{returnType}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{operationId}}({{#parameters}}{{{dataType}}} {{paramName}}{{#hasMore}}, {{/hasMore}}{{/parameters}}) throws RestClientException { + Object {{localVariablePrefix}}postBody = {{#bodyParam}}{{paramName}}{{/bodyParam}}{{^bodyParam}}null{{/bodyParam}}; + {{#parameters}} + {{#required}} + // verify the required parameter '{{paramName}}' is set + if ({{paramName}} == null) { + throw new HttpClientErrorException(HttpStatus.BAD_REQUEST, "Missing the required parameter '{{paramName}}' when calling {{operationId}}"); + } + {{/required}} + {{/parameters}} + {{#hasPathParams}} + // create path and map variables + final Map uriVariables = new HashMap(); + {{#pathParams}} + uriVariables.put("{{baseName}}", {{{paramName}}}); + {{/pathParams}} + {{/hasPathParams}} + String {{localVariablePrefix}}path = UriComponentsBuilder.fromPath("{{{path}}}"){{#hasPathParams}}.buildAndExpand(uriVariables){{/hasPathParams}}{{^hasPathParams}}.build(){{/hasPathParams}}.toUriString(); + + final MultiValueMap {{localVariablePrefix}}queryParams = new LinkedMultiValueMap(); + final HttpHeaders {{localVariablePrefix}}headerParams = new HttpHeaders(); + final MultiValueMap {{localVariablePrefix}}formParams = new LinkedMultiValueMap(); + {{#hasQueryParams}} + {{#queryParams}} + {{localVariablePrefix}}queryParams.putAll({{localVariablePrefix}}apiClient.parameterToMultiValueMap({{#collectionFormat}}ApiClient.CollectionFormat.valueOf("{{{collectionFormat}}}".toUpperCase()){{/collectionFormat}}{{^collectionFormat}}null{{/collectionFormat}}, "{{baseName}}", {{paramName}})); + {{/queryParams}} + {{/hasQueryParams}} + {{#hasHeaderParams}} + {{#headerParams}} + if ({{paramName}} != null) + {{localVariablePrefix}}headerParams.add("{{baseName}}", {{localVariablePrefix}}apiClient.parameterToString({{paramName}})); + {{/headerParams}} + {{/hasHeaderParams}} + {{#hasFormParams}} + {{#formParams}} + if ({{paramName}} != null) + {{localVariablePrefix}}formParams.add("{{baseName}}", {{#is this 'binary'}}new FileSystemResource({{paramName}}){{/is}}{{#isNot this 'binary'}}{{paramName}}{{/isNot}}); + {{/formParams}} + {{/hasFormParams}} + + final String[] {{localVariablePrefix}}accepts = { {{#hasProduces}} + {{#produces}}"{{mediaType}}"{{#hasMore}}, {{/hasMore}}{{/produces}} + {{/hasProduces}} }; + final List {{localVariablePrefix}}accept = {{localVariablePrefix}}apiClient.selectHeaderAccept({{localVariablePrefix}}accepts); + final String[] {{localVariablePrefix}}contentTypes = { {{#hasConsumes}} + {{#consumes}}"{{mediaType}}"{{#hasMore}}, {{/hasMore}}{{/consumes}} + {{/hasConsumes}} }; + final MediaType {{localVariablePrefix}}contentType = {{localVariablePrefix}}apiClient.selectHeaderContentType({{localVariablePrefix}}contentTypes); + + String[] {{localVariablePrefix}}authNames = new String[] { {{#authMethods}}"{{name}}"{{#hasMore}}, {{/hasMore}}{{/authMethods}} }; + + {{#returnType}}ParameterizedTypeReference<{{{returnType}}}> {{localVariablePrefix}}returnType = new ParameterizedTypeReference<{{{returnType}}}>() {};{{/returnType}}{{^returnType}}ParameterizedTypeReference {{localVariablePrefix}}returnType = new ParameterizedTypeReference() {};{{/returnType}} + {{#returnType}}return {{/returnType}}{{localVariablePrefix}}apiClient.invokeAPI({{localVariablePrefix}}path, HttpMethod.{{httpMethod}}, {{localVariablePrefix}}queryParams, {{localVariablePrefix}}postBody, {{localVariablePrefix}}headerParams, {{localVariablePrefix}}formParams, {{localVariablePrefix}}accept, {{localVariablePrefix}}contentType, {{localVariablePrefix}}authNames, {{localVariablePrefix}}returnType); + } + {{/contents}} + {{/operation}} +} +{{/operations}} diff --git a/mustacheTemplates/libraries/resttemplate/api_test.mustache b/mustacheTemplates/libraries/resttemplate/api_test.mustache new file mode 100644 index 0000000000..9876981c5e --- /dev/null +++ b/mustacheTemplates/libraries/resttemplate/api_test.mustache @@ -0,0 +1,44 @@ +{{>licenseInfo}} + +package {{package}}; + +{{#imports}}import {{import}}; +{{/imports}} +import org.junit.Test; +import org.junit.Ignore; + +{{^fullJavaUtil}} +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +{{/fullJavaUtil}} + +/** + * API tests for {{classname}} + */ +@Ignore +public class {{classname}}Test { + + private final {{classname}} api = new {{classname}}(); + + {{#operations}}{{#operation}}{{#contents}}{{#@first}} + /** + * {{summary}} + * + * {{notes}} + * + * @throws ApiException + * if the Api call fails + */ + @Test + public void {{operationId}}Test() { + {{#parameters}} + {{{dataType}}} {{paramName}} = null; + {{/parameters}} + {{#returnType}}{{{returnType}}} response = {{/returnType}}api.{{operationId}}({{#parameters}}{{paramName}}{{#has this 'more'}}, {{/has}}{{/parameters}}); + + // TODO: test validations + } + {{/@first}}{{/contents}}{{/operation}}{{/operations}} +} diff --git a/mustacheTemplates/libraries/resttemplate/auth/ApiKeyAuth.mustache b/mustacheTemplates/libraries/resttemplate/auth/ApiKeyAuth.mustache new file mode 100644 index 0000000000..8b8c40fde7 --- /dev/null +++ b/mustacheTemplates/libraries/resttemplate/auth/ApiKeyAuth.mustache @@ -0,0 +1,60 @@ +package {{invokerPackage}}.auth; + +import org.springframework.http.HttpHeaders; +import org.springframework.util.MultiValueMap; + +{{>generatedAnnotation}} +public class ApiKeyAuth implements Authentication { + private final String location; + private final String paramName; + + private String apiKey; + private String apiKeyPrefix; + + public ApiKeyAuth(String location, String paramName) { + this.location = location; + this.paramName = paramName; + } + + public String getLocation() { + return location; + } + + public String getParamName() { + return paramName; + } + + public String getApiKey() { + return apiKey; + } + + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } + + public String getApiKeyPrefix() { + return apiKeyPrefix; + } + + public void setApiKeyPrefix(String apiKeyPrefix) { + this.apiKeyPrefix = apiKeyPrefix; + } + + @Override + public void applyToParams(MultiValueMap queryParams, HttpHeaders headerParams) { + if (apiKey == null) { + return; + } + String value; + if (apiKeyPrefix != null) { + value = apiKeyPrefix + " " + apiKey; + } else { + value = apiKey; + } + if (location.equals("query")) { + queryParams.add(paramName, value); + } else if (location.equals("header")) { + headerParams.add(paramName, value); + } + } +} diff --git a/mustacheTemplates/libraries/resttemplate/auth/Authentication.mustache b/mustacheTemplates/libraries/resttemplate/auth/Authentication.mustache new file mode 100644 index 0000000000..586de70836 --- /dev/null +++ b/mustacheTemplates/libraries/resttemplate/auth/Authentication.mustache @@ -0,0 +1,13 @@ +package {{invokerPackage}}.auth; + +import org.springframework.http.HttpHeaders; +import org.springframework.util.MultiValueMap; + +public interface Authentication { + /** + * Apply authentication settings to header and / or query parameters. + * @param queryParams The query parameters for the request + * @param headerParams The header parameters for the request + */ + public void applyToParams(MultiValueMap queryParams, HttpHeaders headerParams); +} diff --git a/mustacheTemplates/libraries/resttemplate/auth/HttpBasicAuth.mustache b/mustacheTemplates/libraries/resttemplate/auth/HttpBasicAuth.mustache new file mode 100644 index 0000000000..5f535b3698 --- /dev/null +++ b/mustacheTemplates/libraries/resttemplate/auth/HttpBasicAuth.mustache @@ -0,0 +1,39 @@ +package {{invokerPackage}}.auth; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; + +import org.springframework.http.HttpHeaders; +import org.springframework.util.Base64Utils; +import org.springframework.util.MultiValueMap; + +{{>generatedAnnotation}} +public class HttpBasicAuth implements Authentication { + private String username; + private String password; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + @Override + public void applyToParams(MultiValueMap queryParams, HttpHeaders headerParams) { + if (username == null && password == null) { + return; + } + String str = (username == null ? "" : username) + ":" + (password == null ? "" : password); + headerParams.add(HttpHeaders.AUTHORIZATION, "Basic " + Base64Utils.encodeToString(str.getBytes(StandardCharsets.UTF_8))); + } +} diff --git a/mustacheTemplates/libraries/resttemplate/auth/OAuth.mustache b/mustacheTemplates/libraries/resttemplate/auth/OAuth.mustache new file mode 100644 index 0000000000..fbb2f7f9c7 --- /dev/null +++ b/mustacheTemplates/libraries/resttemplate/auth/OAuth.mustache @@ -0,0 +1,24 @@ +package {{invokerPackage}}.auth; + +import org.springframework.http.HttpHeaders; +import org.springframework.util.MultiValueMap; + +{{>generatedAnnotation}} +public class OAuth implements Authentication { + private String accessToken; + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + @Override + public void applyToParams(MultiValueMap queryParams, HttpHeaders headerParams) { + if (accessToken != null) { + headerParams.add(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken); + } + } +} diff --git a/mustacheTemplates/libraries/resttemplate/auth/OAuthFlow.mustache b/mustacheTemplates/libraries/resttemplate/auth/OAuthFlow.mustache new file mode 100644 index 0000000000..7ab35f6d89 --- /dev/null +++ b/mustacheTemplates/libraries/resttemplate/auth/OAuthFlow.mustache @@ -0,0 +1,5 @@ +package {{invokerPackage}}.auth; + +public enum OAuthFlow { + accessCode, implicit, password, application +} \ No newline at end of file diff --git a/mustacheTemplates/libraries/resttemplate/build.gradle.mustache b/mustacheTemplates/libraries/resttemplate/build.gradle.mustache new file mode 100644 index 0000000000..3ccd732914 --- /dev/null +++ b/mustacheTemplates/libraries/resttemplate/build.gradle.mustache @@ -0,0 +1,150 @@ +apply plugin: 'idea' +apply plugin: 'eclipse' + +group = '{{groupId}}' +version = '{{artifactVersion}}' + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.5.+' + classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3' + } +} + +repositories { + jcenter() +} + + +if(hasProperty('target') && target == 'android') { + + apply plugin: 'com.android.library' + apply plugin: 'com.github.dcendents.android-maven' + + android { + compileSdkVersion 23 + buildToolsVersion '23.0.2' + defaultConfig { + minSdkVersion 14 + targetSdkVersion 22 + } + compileOptions { + {{#java8}} + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + {{/java8}} + {{^java8}} + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 + {{/java8}} + } + + // Rename the aar correctly + libraryVariants.all { variant -> + variant.outputs.each { output -> + def outputFile = output.outputFile + if (outputFile != null && outputFile.name.endsWith('.aar')) { + def fileName = "${project.name}-${variant.baseName}-${version}.aar" + output.outputFile = new File(outputFile.parent, fileName) + } + } + } + + dependencies { + provided 'javax.annotation:jsr250-api:1.0' + } + } + + afterEvaluate { + android.libraryVariants.all { variant -> + def task = project.tasks.create "jar${variant.name.capitalize()}", Jar + task.description = "Create jar artifact for ${variant.name}" + task.dependsOn variant.javaCompile + task.from variant.javaCompile.destinationDir + task.destinationDir = project.file("${project.buildDir}/outputs/jar") + task.archiveName = "${project.name}-${variant.baseName}-${version}.jar" + artifacts.add('archives', task); + } + } + + task sourcesJar(type: Jar) { + from android.sourceSets.main.java.srcDirs + classifier = 'sources' + } + + artifacts { + archives sourcesJar + } + +} else { + + apply plugin: 'java' + apply plugin: 'maven' + + {{#java8}} + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + {{/java8}} + {{^java8}} + sourceCompatibility = JavaVersion.VERSION_1_7 + targetCompatibility = JavaVersion.VERSION_1_7 + {{/java8}} + + install { + repositories.mavenInstaller { + pom.artifactId = '{{artifactId}}' + } + } + + task execute(type:JavaExec) { + main = System.getProperty('mainClass') + classpath = sourceSets.main.runtimeClasspath + } +} + +ext { + {{#useOas2}} + swagger_annotations_version = "1.5.15" + {{/useOas2}} + {{^useOas2}} + swagger_annotations_version = "2.0.0" + {{/useOas2}} + jackson_version = "2.8.9" + spring_web_version = "4.3.9.RELEASE" + jodatime_version = "2.9.9" + junit_version = "4.12" + {{#threetenbp}} + jackson_threeten_version = "2.6.4" + {{/threetenbp}} +} + +dependencies { + {{#useOas2}} + compile "io.swagger:swagger-annotations:$swagger_annotations_version" + {{/useOas2}} + {{^useOas2}} + compile "io.swagger.core.v3:swagger-annotations:$swagger_annotations_version" + {{/useOas2}} + compile "org.springframework:spring-web:$spring_web_version" + compile "com.fasterxml.jackson.core:jackson-core:$jackson_version" + compile "com.fasterxml.jackson.core:jackson-annotations:$jackson_version" + compile "com.fasterxml.jackson.core:jackson-databind:$jackson_version" + compile "com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:$jackson_version" + {{#java8}} + compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jackson_version" + {{/java8}} + {{#joda}} + compile "com.fasterxml.jackson.datatype:jackson-datatype-joda:$jackson_version" + compile "joda-time:joda-time:$jodatime_version" + {{/joda}} + {{#threetenbp}} + compile "com.github.joschi.jackson:jackson-datatype-threetenbp:$jackson_threeten_version" + {{/threetenbp}} + {{#withXml}} + compile "com.fasterxml.jackson.dataformat:jackson-dataformat-xml:$jackson_version" + {{/withXml}} + testCompile "junit:junit:$junit_version" +} diff --git a/mustacheTemplates/libraries/resttemplate/pom.mustache b/mustacheTemplates/libraries/resttemplate/pom.mustache new file mode 100644 index 0000000000..b559516a7b --- /dev/null +++ b/mustacheTemplates/libraries/resttemplate/pom.mustache @@ -0,0 +1,294 @@ + + 4.0.0 + {{groupId}} + {{artifactId}} + jar + {{artifactId}} + {{artifactVersion}} + {{artifactUrl}} + {{artifactDescription}} + + {{scmConnection}} + {{scmDeveloperConnection}} + {{scmUrl}} + + + 2.2.0 + + + + + {{licenseName}} + {{licenseUrl}} + repo + + + + + + {{developerName}} + {{developerEmail}} + {{developerOrganization}} + {{developerOrganizationUrl}} + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.12 + + + + loggerPath + conf/log4j.properties + + + -Xms512m -Xmx1500m + methods + pertest + + + + maven-dependency-plugin + + + package + + copy-dependencies + + + ${project.build.directory}/lib + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.2 + + + + jar + test-jar + + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 1.10 + + + add_sources + generate-sources + + add-source + + + + src/main/java + + + + + add_test_sources + generate-test-sources + + add-test-source + + + + src/test/java + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.1 + + {{#java8}} + 1.8 + 1.8 + {{/java8}} + {{^java8}} + 1.7 + 1.7 + {{/java8}} + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + attach-sources + + jar-no-fork + + + + + + + + + + sign-artifacts + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + sign-artifacts + verify + + sign + + + + + + + + + + + {{#useOas2}} + + io.swagger + swagger-annotations + ${swagger-annotations-version} + + {{/useOas2}} + {{^useOas2}} + + io.swagger.core.v3 + swagger-annotations + ${swagger-annotations-version} + + {{/useOas2}} + + + + org.springframework + spring-web + ${spring-web-version} + + + + + com.fasterxml.jackson.core + jackson-core + ${jackson-version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson-version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson-version} + + + com.fasterxml.jackson.jaxrs + jackson-jaxrs-json-provider + ${jackson-version} + + {{#withXml}} + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + ${jackson-version} + + + {{/withXml}} + {{#java8}} + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson-version} + + {{/java8}} + {{#joda}} + + com.fasterxml.jackson.datatype + jackson-datatype-joda + ${jackson-version} + + + joda-time + joda-time + ${jodatime-version} + + {{/joda}} + {{#threetenbp}} + + com.github.joschi.jackson + jackson-datatype-threetenbp + ${jackson-threetenbp-version} + + {{/threetenbp}} + + + + junit + junit + ${junit-version} + test + + + + UTF-8 + {{#useOas2}} + 1.5.15 + {{/useOas2}} + {{^useOas2}} + 2.0.0 + {{/useOas2}} + 4.3.9.RELEASE + 2.8.9 + {{#joda}} + 2.9.9 + {{/joda}} + {{#threetenbp}} + 2.6.4 + {{/threetenbp}} + 1.0.0 + 4.12 + + diff --git a/mustacheTemplates/libraries/retrofit/ApiClient.mustache b/mustacheTemplates/libraries/retrofit/ApiClient.mustache new file mode 100644 index 0000000000..24fac6a4f0 --- /dev/null +++ b/mustacheTemplates/libraries/retrofit/ApiClient.mustache @@ -0,0 +1,418 @@ +package {{invokerPackage}}; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Type; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.oltu.oauth2.client.request.OAuthClientRequest.AuthenticationRequestBuilder; +import org.apache.oltu.oauth2.client.request.OAuthClientRequest.TokenRequestBuilder; +import org.joda.time.DateTime; +import org.joda.time.LocalDate; +import org.joda.time.format.DateTimeFormatter; +import org.joda.time.format.ISODateTimeFormat; + +import retrofit.RestAdapter; +import retrofit.client.OkClient; +import retrofit.converter.ConversionException; +import retrofit.converter.Converter; +import retrofit.converter.GsonConverter; +import retrofit.mime.TypedByteArray; +import retrofit.mime.TypedInput; +import retrofit.mime.TypedOutput; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonParseException; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import com.squareup.okhttp.Interceptor; +import com.squareup.okhttp.OkHttpClient; + +import {{invokerPackage}}.auth.HttpBasicAuth; +import {{invokerPackage}}.auth.ApiKeyAuth; +import {{invokerPackage}}.auth.OAuth; +import {{invokerPackage}}.auth.OAuth.AccessTokenListener; +import {{invokerPackage}}.auth.OAuthFlow; + + +public class ApiClient { + + private Map apiAuthorizations; + private OkHttpClient okClient; + private RestAdapter.Builder adapterBuilder; + + public ApiClient() { + apiAuthorizations = new LinkedHashMap(); + createDefaultAdapter(); + } + + public ApiClient(String[] authNames) { + this(); + for(String authName : authNames) { + {{#hasAuthMethods}} + Interceptor auth; + {{#authMethods}}if ("{{name}}".equals(authName)) { + {{#is this 'basic'}} + auth = new HttpBasicAuth(); + {{/is}} + {{#is this 'api-key'}} + auth = new ApiKeyAuth({{#is this 'key-in-header'}}"header"{{/is}}{{#isNot this 'key-in-header'}}"query"{{/isNot}}, "{{keyParamName}}");{{/is}} + {{#is this 'oauth'}} + auth = new OAuth(OAuthFlow.{{flow}}, "{{authorizationUrl}}", "{{tokenUrl}}", "{{#scopes}}{{scope}}{{#hasMore}}, {{/hasMore}}{{/scopes}}"); + {{/is}} + } else {{/authMethods}}{ + throw new RuntimeException("auth name \"" + authName + "\" not found in available auth names"); + } + addAuthorization(authName, auth); + {{/hasAuthMethods}} + {{^hasAuthMethods}} + throw new RuntimeException("auth name \"" + authName + "\" not found in available auth names"); + {{/hasAuthMethods}} + } + } + + /** + * Basic constructor for single auth name + * @param authName Authentication name + */ + public ApiClient(String authName) { + this(new String[]{authName}); + } + + /** + * Helper constructor for single api key + * @param authName Authentication name + * @param apiKey API key + */ + public ApiClient(String authName, String apiKey) { + this(authName); + this.setApiKey(apiKey); + } + + /** + * Helper constructor for single basic auth or password oauth2 + * @param authName Authentication name + * @param username Username + * @param password Password + */ + public ApiClient(String authName, String username, String password) { + this(authName); + this.setCredentials(username, password); + } + + /** + * Helper constructor for single password oauth2 + * @param authName Authentication name + * @param clientId Client ID + * @param secret Client secret + * @param username Username + * @param password Password + */ + public ApiClient(String authName, String clientId, String secret, String username, String password) { + this(authName); + this.getTokenEndPoint() + .setClientId(clientId) + .setClientSecret(secret) + .setUsername(username) + .setPassword(password); + } + + public void createDefaultAdapter() { + Gson gson = new GsonBuilder() + .setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ") + .registerTypeAdapter(DateTime.class, new DateTimeTypeAdapter()) + .registerTypeAdapter(LocalDate.class, new LocalDateTypeAdapter()) + .create(); + + okClient = new OkHttpClient(); + + adapterBuilder = new RestAdapter + .Builder() + .setEndpoint("{{{basePath}}}") + .setClient(new OkClient(okClient)) + .setConverter(new GsonConverterWrapper(gson)); + } + + public S createService(Class serviceClass) { + return adapterBuilder.build().create(serviceClass); + + } + + /** + * Helper method to configure the first api key found + * @param apiKey API key + */ + private void setApiKey(String apiKey) { + for(Interceptor apiAuthorization : apiAuthorizations.values()) { + if (apiAuthorization instanceof ApiKeyAuth) { + ApiKeyAuth keyAuth = (ApiKeyAuth) apiAuthorization; + keyAuth.setApiKey(apiKey); + return; + } + } + } + + /** + * Helper method to configure the username/password for basic auth or password oauth + * @param username Username + * @param password Password + */ + private void setCredentials(String username, String password) { + for(Interceptor apiAuthorization : apiAuthorizations.values()) { + if (apiAuthorization instanceof HttpBasicAuth) { + HttpBasicAuth basicAuth = (HttpBasicAuth) apiAuthorization; + basicAuth.setCredentials(username, password); + return; + } + if (apiAuthorization instanceof OAuth) { + OAuth oauth = (OAuth) apiAuthorization; + oauth.getTokenRequestBuilder().setUsername(username).setPassword(password); + return; + } + } + } + + /** + * Helper method to configure the token endpoint of the first oauth found in the apiAuthorizations (there should be only one) + * @return Token request builder + */ + public TokenRequestBuilder getTokenEndPoint() { + for(Interceptor apiAuthorization : apiAuthorizations.values()) { + if (apiAuthorization instanceof OAuth) { + OAuth oauth = (OAuth) apiAuthorization; + return oauth.getTokenRequestBuilder(); + } + } + return null; + } + + /** + * Helper method to configure authorization endpoint of the first oauth found in the apiAuthorizations (there should be only one) + * @return Authentication request builder + */ + public AuthenticationRequestBuilder getAuthorizationEndPoint() { + for(Interceptor apiAuthorization : apiAuthorizations.values()) { + if (apiAuthorization instanceof OAuth) { + OAuth oauth = (OAuth) apiAuthorization; + return oauth.getAuthenticationRequestBuilder(); + } + } + return null; + } + + /** + * Helper method to pre-set the oauth access token of the first oauth found in the apiAuthorizations (there should be only one) + * @param accessToken Access token + */ + public void setAccessToken(String accessToken) { + for(Interceptor apiAuthorization : apiAuthorizations.values()) { + if (apiAuthorization instanceof OAuth) { + OAuth oauth = (OAuth) apiAuthorization; + oauth.setAccessToken(accessToken); + return; + } + } + } + + /** + * Helper method to configure the oauth accessCode/implicit flow parameters + * @param clientId Client ID + * @param clientSecret Client secret + * @param redirectURI Redirect URI + */ + public void configureAuthorizationFlow(String clientId, String clientSecret, String redirectURI) { + for(Interceptor apiAuthorization : apiAuthorizations.values()) { + if (apiAuthorization instanceof OAuth) { + OAuth oauth = (OAuth) apiAuthorization; + oauth.getTokenRequestBuilder() + .setClientId(clientId) + .setClientSecret(clientSecret) + .setRedirectURI(redirectURI); + oauth.getAuthenticationRequestBuilder() + .setClientId(clientId) + .setRedirectURI(redirectURI); + return; + } + } + } + + /** + * Configures a listener which is notified when a new access token is received. + * @param accessTokenListener Access token listener + */ + public void registerAccessTokenListener(AccessTokenListener accessTokenListener) { + for(Interceptor apiAuthorization : apiAuthorizations.values()) { + if (apiAuthorization instanceof OAuth) { + OAuth oauth = (OAuth) apiAuthorization; + oauth.registerAccessTokenListener(accessTokenListener); + return; + } + } + } + + /** + * Adds an authorization to be used by the client + * @param authName Authentication name + * @param authorization Authorization + */ + public void addAuthorization(String authName, Interceptor authorization) { + if (apiAuthorizations.containsKey(authName)) { + throw new RuntimeException("auth name \"" + authName + "\" already in api authorizations"); + } + apiAuthorizations.put(authName, authorization); + okClient.interceptors().add(authorization); + } + + public Map getApiAuthorizations() { + return apiAuthorizations; + } + + public void setApiAuthorizations(Map apiAuthorizations) { + this.apiAuthorizations = apiAuthorizations; + } + + public RestAdapter.Builder getAdapterBuilder() { + return adapterBuilder; + } + + public void setAdapterBuilder(RestAdapter.Builder adapterBuilder) { + this.adapterBuilder = adapterBuilder; + } + + public OkHttpClient getOkClient() { + return okClient; + } + + public void addAuthsToOkClient(OkHttpClient okClient) { + for(Interceptor apiAuthorization : apiAuthorizations.values()) { + okClient.interceptors().add(apiAuthorization); + } + } + + /** + * Clones the okClient given in parameter, adds the auth interceptors and uses it to configure the RestAdapter + * @param okClient OkHttp client + */ + public void configureFromOkclient(OkHttpClient okClient) { + OkHttpClient clone = okClient.clone(); + addAuthsToOkClient(clone); + adapterBuilder.setClient(new OkClient(clone)); + } +} + +/** + * This wrapper is to take care of this case: + * when the deserialization fails due to JsonParseException and the + * expected type is String, then just return the body string. + */ +class GsonConverterWrapper implements Converter { + private GsonConverter converter; + + public GsonConverterWrapper(Gson gson) { + converter = new GsonConverter(gson); + } + + @Override public Object fromBody(TypedInput body, Type type) throws ConversionException { + byte[] bodyBytes = readInBytes(body); + TypedByteArray newBody = new TypedByteArray(body.mimeType(), bodyBytes); + try { + return converter.fromBody(newBody, type); + } catch (ConversionException e) { + if (e.getCause() instanceof JsonParseException && type.equals(String.class)) { + return new String(bodyBytes); + } else { + throw e; + } + } + } + + @Override public TypedOutput toBody(Object object) { + return converter.toBody(object); + } + + private byte[] readInBytes(TypedInput body) throws ConversionException { + InputStream in = null; + try { + in = body.in(); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + byte[] buffer = new byte[0xFFFF]; + for (int len; (len = in.read(buffer)) != -1;) + os.write(buffer, 0, len); + os.flush(); + return os.toByteArray(); + } catch (IOException e) { + throw new ConversionException(e); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException ignored) { + } + } + } + + } +} + +/** + * Gson TypeAdapter for Joda DateTime type + */ +class DateTimeTypeAdapter extends TypeAdapter { + + private final DateTimeFormatter parseFormatter = ISODateTimeFormat.dateOptionalTimeParser(); + private final DateTimeFormatter printFormatter = ISODateTimeFormat.dateTime(); + + @Override + public void write(JsonWriter out, DateTime date) throws IOException { + if (date == null) { + out.nullValue(); + } else { + out.value(printFormatter.print(date)); + } + } + + @Override + public DateTime read(JsonReader in) throws IOException { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String date = in.nextString(); + return parseFormatter.parseDateTime(date); + } + } +} + +/** + * Gson TypeAdapter for Joda DateTime type + */ +class LocalDateTypeAdapter extends TypeAdapter { + + private final DateTimeFormatter formatter = ISODateTimeFormat.date(); + + @Override + public void write(JsonWriter out, LocalDate date) throws IOException { + if (date == null) { + out.nullValue(); + } else { + out.value(formatter.print(date)); + } + } + + @Override + public LocalDate read(JsonReader in) throws IOException { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String date = in.nextString(); + return formatter.parseLocalDate(date); + } + } +} diff --git a/mustacheTemplates/libraries/retrofit/CollectionFormats.mustache b/mustacheTemplates/libraries/retrofit/CollectionFormats.mustache new file mode 100644 index 0000000000..a549ea59d1 --- /dev/null +++ b/mustacheTemplates/libraries/retrofit/CollectionFormats.mustache @@ -0,0 +1,95 @@ +package {{invokerPackage}}; + +import java.util.Arrays; +import java.util.List; + +public class CollectionFormats { + + public static class CSVParams { + + protected List params; + + public CSVParams() { + } + + public CSVParams(List params) { + this.params = params; + } + + public CSVParams(String... params) { + this.params = Arrays.asList(params); + } + + public List getParams() { + return params; + } + + public void setParams(List params) { + this.params = params; + } + + @Override + public String toString() { + return StringUtil.join(params.toArray(new String[0]), ","); + } + + } + + public static class SSVParams extends CSVParams { + + public SSVParams() { + } + + public SSVParams(List params) { + super(params); + } + + public SSVParams(String... params) { + super(params); + } + + @Override + public String toString() { + return StringUtil.join(params.toArray(new String[0]), " "); + } + } + + public static class TSVParams extends CSVParams { + + public TSVParams() { + } + + public TSVParams(List params) { + super(params); + } + + public TSVParams(String... params) { + super(params); + } + + @Override + public String toString() { + return StringUtil.join( params.toArray(new String[0]), "\t"); + } + } + + public static class PIPESParams extends CSVParams { + + public PIPESParams() { + } + + public PIPESParams(List params) { + super(params); + } + + public PIPESParams(String... params) { + super(params); + } + + @Override + public String toString() { + return StringUtil.join(params.toArray(new String[0]), "|"); + } + } + +} diff --git a/mustacheTemplates/libraries/retrofit/README.mustache b/mustacheTemplates/libraries/retrofit/README.mustache new file mode 100644 index 0000000000..56560172ee --- /dev/null +++ b/mustacheTemplates/libraries/retrofit/README.mustache @@ -0,0 +1,43 @@ +# {{artifactId}} + +## Requirements + +Building the API client library requires [Maven](https://maven.apache.org/) to be installed. + +## Installation & Usage + +To install the API client library to your local Maven repository, simply execute: + +```shell +mvn install +``` + +To deploy it to a remote Maven repository instead, configure the settings of the repository and execute: + +```shell +mvn deploy +``` + +Refer to the [official documentation](https://maven.apache.org/plugins/maven-deploy-plugin/usage.html) for more information. + +After the client library is installed/deployed, you can use it in your Maven project by adding the following to your *pom.xml*: + +```xml + + {{groupId}} + {{artifactId}} + {{artifactVersion}} + compile + + +``` + +## Recommendation + +It's recommended to create an instance of `ApiClient` per thread in a multithreaded environment to avoid any potential issues. + +## Author + +{{#apiInfo}}{{#apis}}{{^hasMore}}{{infoEmail}} +{{/hasMore}}{{/apis}}{{/apiInfo}} + diff --git a/mustacheTemplates/libraries/retrofit/api.mustache b/mustacheTemplates/libraries/retrofit/api.mustache new file mode 100644 index 0000000000..67b4a37f5f --- /dev/null +++ b/mustacheTemplates/libraries/retrofit/api.mustache @@ -0,0 +1,81 @@ +package {{package}}; + +import {{invokerPackage}}.CollectionFormats.*; + +import retrofit.Callback; +import retrofit.http.*; +import retrofit.mime.*; + +{{#imports}}import {{import}}; +{{/imports}} +{{^fullJavaUtil}} +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +{{/fullJavaUtil}} + +{{#operations}} +public interface {{classname}} { + {{#operation}} + {{#contents}} + /** + * {{summary}} + * Sync method + * {{notes}} +{{#parameters}} + * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}} +{{/parameters}} +{{#returnType}} + * @return {{returnType}} +{{/returnType}} +{{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation +{{/externalDocs}} + */ + {{#formParams}} + {{#@first}} + {{#is ../this 'multipart'}} + @retrofit.http.Multipart + {{/is}} + {{#isNot ../this 'multipart'}} + @retrofit.http.FormUrlEncoded + {{/isNot}} + {{/@first}} + {{/formParams}} + @{{httpMethod}}("{{{path}}}") + {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Object{{/returnType}} {{operationId}}({{^parameters}});{{/parameters}} + {{#parameters}}{{>libraries/retrofit/queryParams}}{{>libraries/retrofit/pathParams}}{{>libraries/retrofit/headerParams}}{{>libraries/retrofit/bodyParams}}{{>libraries/retrofit/formParams}}{{>libraries/retrofit/cookieParams}}{{#has this 'more'}}, {{/has}}{{#hasNot this 'more'}} + );{{/hasNot}}{{/parameters}} + + /** + * {{summary}} + * Async method +{{#parameters}} + * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}} +{{/parameters}} + * @param cb callback method +{{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation +{{/externalDocs}} + */ + {{#formParams}} + {{#@first}} + {{#is ../this 'multipart'}} + @retrofit.http.Multipart + {{/is}} + {{#isNot ../this 'multipart'}} + @retrofit.http.FormUrlEncoded + {{/isNot}} + {{/@first}} + {{/formParams}} + @{{httpMethod}}("{{{path}}}") + void {{operationId}}( + {{#parameters}}{{>libraries/retrofit/queryParams}}{{>libraries/retrofit/pathParams}}{{>libraries/retrofit/headerParams}}{{>libraries/retrofit/bodyParams}}{{>libraries/retrofit/formParams}}{{>libraries/retrofit/cookieParams}}, {{/parameters}}Callback<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Object{{/returnType}}> cb + ); + {{/contents}} + {{/operation}} +} +{{/operations}} diff --git a/mustacheTemplates/libraries/retrofit/api_test.mustache b/mustacheTemplates/libraries/retrofit/api_test.mustache new file mode 100644 index 0000000000..ac191b73e4 --- /dev/null +++ b/mustacheTemplates/libraries/retrofit/api_test.mustache @@ -0,0 +1,44 @@ +package {{package}}; + +import {{invokerPackage}}.ApiClient; +{{#imports}}import {{import}}; +{{/imports}} +import org.junit.Before; +import org.junit.Test; + +{{^fullJavaUtil}} +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +{{/fullJavaUtil}} + +/** + * API tests for {{classname}} + */ +public class {{classname}}Test { + + private {{classname}} api; + + @Before + public void setup() { + api = new ApiClient().createService({{classname}}.class); + } + + {{#operations}}{{#operation}}{{#contents}}{{#@first}} + /** + * {{summary}} + * + * {{notes}} + */ + @Test + public void {{operationId}}Test() { + {{#parameters}} + {{{dataType}}} {{paramName}} = null; + {{/parameters}} + // {{#returnType}}{{{returnType}}} response = {{/returnType}}api.{{operationId}}({{#parameters}}{{paramName}}{{#has this 'more'}}, {{/has}}{{/parameters}}); + + // TODO: test validations + } + {{/@first}}{{/contents}}{{/operation}}{{/operations}} +} diff --git a/mustacheTemplates/libraries/retrofit/auth/ApiKeyAuth.mustache b/mustacheTemplates/libraries/retrofit/auth/ApiKeyAuth.mustache new file mode 100644 index 0000000000..f6a86f22ab --- /dev/null +++ b/mustacheTemplates/libraries/retrofit/auth/ApiKeyAuth.mustache @@ -0,0 +1,68 @@ +package {{invokerPackage}}.auth; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +import com.squareup.okhttp.Interceptor; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.Response; + +public class ApiKeyAuth implements Interceptor { + private final String location; + private final String paramName; + + private String apiKey; + + public ApiKeyAuth(String location, String paramName) { + this.location = location; + this.paramName = paramName; + } + + public String getLocation() { + return location; + } + + public String getParamName() { + return paramName; + } + + public String getApiKey() { + return apiKey; + } + + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } + + @Override + public Response intercept(Chain chain) throws IOException { + String paramValue; + Request request = chain.request(); + + if ("query".equals(location)) { + String newQuery = request.uri().getQuery(); + paramValue = paramName + "=" + apiKey; + if (newQuery == null) { + newQuery = paramValue; + } else { + newQuery += "&" + paramValue; + } + + URI newUri; + try { + newUri = new URI(request.uri().getScheme(), request.uri().getAuthority(), + request.uri().getPath(), newQuery, request.uri().getFragment()); + } catch (URISyntaxException e) { + throw new IOException(e); + } + + request = request.newBuilder().url(newUri.toURL()).build(); + } else if ("header".equals(location)) { + request = request.newBuilder() + .addHeader(paramName, apiKey) + .build(); + } + return chain.proceed(request); + } +} diff --git a/mustacheTemplates/libraries/retrofit/auth/HttpBasicAuth.mustache b/mustacheTemplates/libraries/retrofit/auth/HttpBasicAuth.mustache new file mode 100644 index 0000000000..394592f64d --- /dev/null +++ b/mustacheTemplates/libraries/retrofit/auth/HttpBasicAuth.mustache @@ -0,0 +1,49 @@ +package {{invokerPackage}}.auth; + +import java.io.IOException; + +import com.squareup.okhttp.Credentials; +import com.squareup.okhttp.Interceptor; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.Response; + +public class HttpBasicAuth implements Interceptor { + + private String username; + private String password; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public void setCredentials(String username, String password) { + this.username = username; + this.password = password; + } + + @Override + public Response intercept(Chain chain) throws IOException { + Request request = chain.request(); + + // If the request already have an authorization (eg. Basic auth), do nothing + if (request.header("Authorization") == null) { + String credentials = Credentials.basic(username, password); + request = request.newBuilder() + .addHeader("Authorization", credentials) + .build(); + } + return chain.proceed(request); + } +} diff --git a/mustacheTemplates/libraries/retrofit/auth/OAuth.mustache b/mustacheTemplates/libraries/retrofit/auth/OAuth.mustache new file mode 100644 index 0000000000..a88378f3e2 --- /dev/null +++ b/mustacheTemplates/libraries/retrofit/auth/OAuth.mustache @@ -0,0 +1,179 @@ +package {{invokerPackage}}.auth; + +import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; +import static java.net.HttpURLConnection.HTTP_FORBIDDEN; + +import java.io.IOException; +import java.util.Map; + +import org.apache.oltu.oauth2.client.OAuthClient; +import org.apache.oltu.oauth2.client.request.OAuthBearerClientRequest; +import org.apache.oltu.oauth2.client.request.OAuthClientRequest; +import org.apache.oltu.oauth2.client.request.OAuthClientRequest.AuthenticationRequestBuilder; +import org.apache.oltu.oauth2.client.request.OAuthClientRequest.TokenRequestBuilder; +import org.apache.oltu.oauth2.client.response.OAuthJSONAccessTokenResponse; +import org.apache.oltu.oauth2.common.exception.OAuthProblemException; +import org.apache.oltu.oauth2.common.exception.OAuthSystemException; +import org.apache.oltu.oauth2.common.message.types.GrantType; +import org.apache.oltu.oauth2.common.token.BasicOAuthToken; + +import com.squareup.okhttp.Interceptor; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.Request.Builder; +import com.squareup.okhttp.Response; + +public class OAuth implements Interceptor { + + public interface AccessTokenListener { + public void notify(BasicOAuthToken token); + } + + private volatile String accessToken; + private OAuthClient oauthClient; + + private TokenRequestBuilder tokenRequestBuilder; + private AuthenticationRequestBuilder authenticationRequestBuilder; + + private AccessTokenListener accessTokenListener; + + public OAuth( OkHttpClient client, TokenRequestBuilder requestBuilder ) { + this.oauthClient = new OAuthClient(new OAuthOkHttpClient(client)); + this.tokenRequestBuilder = requestBuilder; + } + + public OAuth(TokenRequestBuilder requestBuilder ) { + this(new OkHttpClient(), requestBuilder); + } + + public OAuth(OAuthFlow flow, String authorizationUrl, String tokenUrl, String scopes) { + this(OAuthClientRequest.tokenLocation(tokenUrl).setScope(scopes)); + setFlow(flow); + authenticationRequestBuilder = OAuthClientRequest.authorizationLocation(authorizationUrl); + } + + public void setFlow(OAuthFlow flow) { + switch(flow) { + case accessCode: + case implicit: + tokenRequestBuilder.setGrantType(GrantType.AUTHORIZATION_CODE); + break; + case password: + tokenRequestBuilder.setGrantType(GrantType.PASSWORD); + break; + case application: + tokenRequestBuilder.setGrantType(GrantType.CLIENT_CREDENTIALS); + break; + default: + break; + } + } + + @Override + public Response intercept(Chain chain) + throws IOException { + + return retryingIntercept(chain, true); + } + + private Response retryingIntercept(Chain chain, boolean updateTokenAndRetryOnAuthorizationFailure) throws IOException { + Request request = chain.request(); + + // If the request already have an authorization (eg. Basic auth), do nothing + if (request.header("Authorization") != null) { + return chain.proceed(request); + } + + // If first time, get the token + OAuthClientRequest oAuthRequest; + if (getAccessToken() == null) { + updateAccessToken(null); + } + + if (getAccessToken() != null) { + // Build the request + Builder rb = request.newBuilder(); + + String requestAccessToken = new String(getAccessToken()); + try { + oAuthRequest = new OAuthBearerClientRequest(request.urlString()) + .setAccessToken(requestAccessToken) + .buildHeaderMessage(); + } catch (OAuthSystemException e) { + throw new IOException(e); + } + + for ( Map.Entry header : oAuthRequest.getHeaders().entrySet() ) { + rb.addHeader(header.getKey(), header.getValue()); + } + rb.url( oAuthRequest.getLocationUri()); + + //Execute the request + Response response = chain.proceed(rb.build()); + + // 401/403 most likely indicates that access token has expired. Unless it happens two times in a row. + if ( response != null && (response.code() == HTTP_UNAUTHORIZED || response.code() == HTTP_FORBIDDEN) && updateTokenAndRetryOnAuthorizationFailure ) { + if (updateAccessToken(requestAccessToken)) { + return retryingIntercept( chain, false ); + } + } + return response; + } else { + return chain.proceed(chain.request()); + } + } + + /* + * Returns true if the access token has been updated + */ + public synchronized boolean updateAccessToken(String requestAccessToken) throws IOException { + if (getAccessToken() == null || getAccessToken().equals(requestAccessToken)) { + try { + OAuthJSONAccessTokenResponse accessTokenResponse = oauthClient.accessToken(this.tokenRequestBuilder.buildBodyMessage()); + if (accessTokenResponse != null && accessTokenResponse.getAccessToken() != null) { + setAccessToken(accessTokenResponse.getAccessToken()); + if (accessTokenListener != null) { + accessTokenListener.notify((BasicOAuthToken) accessTokenResponse.getOAuthToken()); + } + return !getAccessToken().equals(requestAccessToken); + } else { + return false; + } + } catch (OAuthSystemException e) { + throw new IOException(e); + } catch (OAuthProblemException e) { + throw new IOException(e); + } + } + return true; + } + + public void registerAccessTokenListener(AccessTokenListener accessTokenListener) { + this.accessTokenListener = accessTokenListener; + } + + public synchronized String getAccessToken() { + return accessToken; + } + + public synchronized void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public TokenRequestBuilder getTokenRequestBuilder() { + return tokenRequestBuilder; + } + + public void setTokenRequestBuilder(TokenRequestBuilder tokenRequestBuilder) { + this.tokenRequestBuilder = tokenRequestBuilder; + } + + public AuthenticationRequestBuilder getAuthenticationRequestBuilder() { + return authenticationRequestBuilder; + } + + public void setAuthenticationRequestBuilder(AuthenticationRequestBuilder authenticationRequestBuilder) { + this.authenticationRequestBuilder = authenticationRequestBuilder; + } + +} diff --git a/mustacheTemplates/libraries/retrofit/auth/OAuthOkHttpClient.mustache b/mustacheTemplates/libraries/retrofit/auth/OAuthOkHttpClient.mustache new file mode 100644 index 0000000000..ceaac34b9e --- /dev/null +++ b/mustacheTemplates/libraries/retrofit/auth/OAuthOkHttpClient.mustache @@ -0,0 +1,70 @@ +package {{invokerPackage}}.auth; + +import java.io.IOException; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.oltu.oauth2.client.HttpClient; +import org.apache.oltu.oauth2.client.request.OAuthClientRequest; +import org.apache.oltu.oauth2.client.response.OAuthClientResponse; +import org.apache.oltu.oauth2.client.response.OAuthClientResponseFactory; +import org.apache.oltu.oauth2.common.exception.OAuthProblemException; +import org.apache.oltu.oauth2.common.exception.OAuthSystemException; + +import com.squareup.okhttp.MediaType; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.RequestBody; +import com.squareup.okhttp.Response; + + +public class OAuthOkHttpClient implements HttpClient { + + private OkHttpClient client; + + public OAuthOkHttpClient() { + this.client = new OkHttpClient(); + } + + public OAuthOkHttpClient(OkHttpClient client) { + this.client = client; + } + + public T execute(OAuthClientRequest request, Map headers, + String requestMethod, Class responseClass) + throws OAuthSystemException, OAuthProblemException { + + MediaType mediaType = MediaType.parse("application/json"); + Request.Builder requestBuilder = new Request.Builder().url(request.getLocationUri()); + + if(headers != null) { + for (Entry entry : headers.entrySet()) { + if (entry.getKey().equalsIgnoreCase("Content-Type")) { + mediaType = MediaType.parse(entry.getValue()); + } else { + requestBuilder.addHeader(entry.getKey(), entry.getValue()); + } + } + } + + RequestBody body = request.getBody() != null ? RequestBody.create(mediaType, request.getBody()) : null; + requestBuilder.method(requestMethod, body); + + try { + Response response = client.newCall(requestBuilder.build()).execute(); + return OAuthClientResponseFactory.createCustomResponse( + response.body().string(), + response.body().contentType().toString(), + response.code(), + response.headers().toMultimap(), + responseClass); + } catch (IOException e) { + throw new OAuthSystemException(e); + } + } + + public void shutdown() { + // Nothing to do here + } + +} diff --git a/mustacheTemplates/libraries/retrofit/bodyParams.mustache b/mustacheTemplates/libraries/retrofit/bodyParams.mustache new file mode 100644 index 0000000000..cfbef408bc --- /dev/null +++ b/mustacheTemplates/libraries/retrofit/bodyParams.mustache @@ -0,0 +1 @@ +{{~#is this 'body-param'}}@retrofit.http.Body {{{dataType}}} {{paramName}}{{/is}} \ No newline at end of file diff --git a/mustacheTemplates/libraries/retrofit/build.gradle.mustache b/mustacheTemplates/libraries/retrofit/build.gradle.mustache new file mode 100644 index 0000000000..0473350b52 --- /dev/null +++ b/mustacheTemplates/libraries/retrofit/build.gradle.mustache @@ -0,0 +1,128 @@ +apply plugin: 'idea' +apply plugin: 'eclipse' + +group = '{{groupId}}' +version = '{{artifactVersion}}' + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.3.+' + classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' + } +} + +repositories { + jcenter() +} + + +if(hasProperty('target') && target == 'android') { + + apply plugin: 'com.android.library' + apply plugin: 'com.github.dcendents.android-maven' + + android { + compileSdkVersion 25 + buildToolsVersion '25.0.2' + defaultConfig { + minSdkVersion 14 + targetSdkVersion 25 + } + compileOptions { + {{#java8}} + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + {{/java8}} + {{^java8}} + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 + {{/java8}} + } + + // Rename the aar correctly + libraryVariants.all { variant -> + variant.outputs.each { output -> + def outputFile = output.outputFile + if (outputFile != null && outputFile.name.endsWith('.aar')) { + def fileName = "${project.name}-${variant.baseName}-${version}.aar" + output.outputFile = new File(outputFile.parent, fileName) + } + } + } + + dependencies { + provided 'javax.annotation:jsr250-api:1.0' + } + } + + afterEvaluate { + android.libraryVariants.all { variant -> + def task = project.tasks.create "jar${variant.name.capitalize()}", Jar + task.description = "Create jar artifact for ${variant.name}" + task.dependsOn variant.javaCompile + task.from variant.javaCompile.destinationDir + task.destinationDir = project.file("${project.buildDir}/outputs/jar") + task.archiveName = "${project.name}-${variant.baseName}-${version}.jar" + artifacts.add('archives', task); + } + } + + task sourcesJar(type: Jar) { + from android.sourceSets.main.java.srcDirs + classifier = 'sources' + } + + artifacts { + archives sourcesJar + } + +} else { + + apply plugin: 'java' + apply plugin: 'maven' + + sourceCompatibility = JavaVersion.VERSION_1_7 + targetCompatibility = JavaVersion.VERSION_1_7 + + install { + repositories.mavenInstaller { + pom.artifactId = '{{artifactId}}' + } + } + + task execute(type:JavaExec) { + main = System.getProperty('mainClass') + classpath = sourceSets.main.runtimeClasspath + } +} + +ext { + okhttp_version = "2.7.5" + oltu_version = "1.0.2" + retrofit_version = "1.9.0" + {{#useOas2}} + swagger_annotations_version = "1.5.8" + {{/useOas2}} + {{^useOas2}} + swagger_annotations_version = "2.0.0" + {{/useOas2}} + junit_version = "4.12" + jodatime_version = "2.9.3" +} + +dependencies { + compile "com.squareup.okhttp:okhttp:$okhttp_version" + compile "com.squareup.retrofit:retrofit:$retrofit_version" + {{#useOas2}} + compile "io.swagger:swagger-annotations:$swagger_annotations_version" + {{/useOas2}} + {{^useOas2}} + compile "io.swagger.core.v3:swagger-annotations:$swagger_annotations_version" + {{/useOas2}} + compile "org.apache.oltu.oauth2:org.apache.oltu.oauth2.client:$oltu_version" + compile "joda-time:joda-time:$jodatime_version" + testCompile "junit:junit:$junit_version" +} diff --git a/mustacheTemplates/libraries/retrofit/build.sbt.mustache b/mustacheTemplates/libraries/retrofit/build.sbt.mustache new file mode 100644 index 0000000000..6bed751503 --- /dev/null +++ b/mustacheTemplates/libraries/retrofit/build.sbt.mustache @@ -0,0 +1,25 @@ +lazy val root = (project in file(".")). + settings( + organization := "{{groupId}}", + name := "{{artifactId}}", + version := "{{artifactVersion}}", + scalaVersion := "2.11.4", + scalacOptions ++= Seq("-feature"), + javacOptions in compile ++= Seq("-Xlint:deprecation"), + publishArtifact in (Compile, packageDoc) := false, + resolvers += Resolver.mavenLocal, + libraryDependencies ++= Seq( + "com.squareup.okhttp" % "okhttp" % "2.7.5" % "compile", + "com.squareup.retrofit" % "retrofit" % "1.9.0" % "compile", + {{#useOas2}} + "io.swagger" % "swagger-annotations" % "1.5.8" % "compile", + {{/useOas2}} + {{^useOas2}} + "io.swagger.core.v3" % "swagger-annotations" % "2.0.0" % "compile", + {{/useOas2}} + "org.apache.oltu.oauth2" % "org.apache.oltu.oauth2.client" % "1.0.2" % "compile", + "joda-time" % "joda-time" % "2.9.3" % "compile", + "junit" % "junit" % "4.12" % "test", + "com.novocode" % "junit-interface" % "0.10" % "test" + ) + ) diff --git a/mustacheTemplates/libraries/retrofit/cookieParams.mustache b/mustacheTemplates/libraries/retrofit/cookieParams.mustache new file mode 100644 index 0000000000..cdb1e2289f --- /dev/null +++ b/mustacheTemplates/libraries/retrofit/cookieParams.mustache @@ -0,0 +1 @@ +{{~#is this 'cookie-param'}}{{{dataType}}} {{paramName}}{{/is}} \ No newline at end of file diff --git a/mustacheTemplates/libraries/retrofit/formParams.mustache b/mustacheTemplates/libraries/retrofit/formParams.mustache new file mode 100644 index 0000000000..0bfdce65cd --- /dev/null +++ b/mustacheTemplates/libraries/retrofit/formParams.mustache @@ -0,0 +1 @@ +{{~#is this 'form-param'}}{{#isNot this 'binary'}}{{#is ../this 'multipart'}}@retrofit.http.Part{{/is}}{{#isNot ../this 'multipart'}}@retrofit.http.Field{{/isNot}}("{{baseName}}") {{{dataType}}} {{paramName}}{{/isNot}}{{#is this 'binary'}}{{#is ../this 'multipart'}}@retrofit.http.Part{{/is}}{{#isNot ../this 'multipart'}}@retrofit.http.Field{{/isNot}}("{{baseName}}") TypedFile {{paramName}}{{/is}}{{/is}} \ No newline at end of file diff --git a/mustacheTemplates/libraries/retrofit/headerParams.mustache b/mustacheTemplates/libraries/retrofit/headerParams.mustache new file mode 100644 index 0000000000..a7d2c47b34 --- /dev/null +++ b/mustacheTemplates/libraries/retrofit/headerParams.mustache @@ -0,0 +1 @@ +{{~#is this 'header-param'}}@retrofit.http.Header("{{baseName}}") {{{dataType}}} {{paramName}}{{/is}} \ No newline at end of file diff --git a/mustacheTemplates/libraries/retrofit/pathParams.mustache b/mustacheTemplates/libraries/retrofit/pathParams.mustache new file mode 100644 index 0000000000..14400603ea --- /dev/null +++ b/mustacheTemplates/libraries/retrofit/pathParams.mustache @@ -0,0 +1 @@ +{{~#is this 'path-param'}}@retrofit.http.Path("{{baseName}}") {{{dataType}}} {{paramName}}{{/is}} \ No newline at end of file diff --git a/mustacheTemplates/libraries/retrofit/pom.mustache b/mustacheTemplates/libraries/retrofit/pom.mustache new file mode 100644 index 0000000000..82c3f531d6 --- /dev/null +++ b/mustacheTemplates/libraries/retrofit/pom.mustache @@ -0,0 +1,247 @@ + + 4.0.0 + {{groupId}} + {{artifactId}} + jar + {{artifactId}} + {{artifactVersion}} + {{artifactUrl}} + {{artifactDescription}} + + {{scmConnection}} + {{scmDeveloperConnection}} + {{scmUrl}} + + + 2.2.0 + + + + + {{licenseName}} + {{licenseUrl}} + repo + + + + + + {{developerName}} + {{developerEmail}} + {{developerOrganization}} + {{developerOrganizationUrl}} + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.12 + + + + loggerPath + conf/log4j.properties + + + -Xms512m -Xmx1500m + methods + pertest + + + + maven-dependency-plugin + + + package + + copy-dependencies + + + ${project.build.directory}/lib + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.2 + + + + jar + test-jar + + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add_sources + generate-sources + + add-source + + + + src/main/java + + + + + add_test_sources + generate-test-sources + + add-test-source + + + + src/test/java + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.1 + + {{#java8}}1.8{{/java8}}{{^java8}}1.7{{/java8}} + {{#java8}}1.8{{/java8}}{{^java8}}1.7{{/java8}} + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + attach-sources + + jar-no-fork + + + + + + + + + + sign-artifacts + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + sign-artifacts + verify + + sign + + + + + + + + + + + {{#useOas2}} + + io.swagger + swagger-annotations + ${swagger-core-version} + + {{/useOas2}} + {{^useOas2}} + + io.swagger.core.v3 + swagger-annotations + ${swagger-core-version} + + {{/useOas2}} + + com.squareup.retrofit + retrofit + ${retrofit-version} + + + org.apache.oltu.oauth2 + org.apache.oltu.oauth2.client + ${oltu-version} + + + com.squareup.okhttp + okhttp + ${okhttp-version} + + + joda-time + joda-time + ${jodatime-version} + + + {{#parcelableModel}} + + + com.google.android + android + 4.1.1.4 + provided + + {{/parcelableModel}} + + + + junit + junit + ${junit-version} + test + + + + {{#useOas2}} + 1.5.15 + {{/useOas2}} + {{^useOas2}} + 2.0.0 + {{/useOas2}} + 1.9.0 + 2.7.5 + 2.9.9 + 1.0.2 + 1.0.0 + 4.12 + + diff --git a/mustacheTemplates/libraries/retrofit/queryParams.mustache b/mustacheTemplates/libraries/retrofit/queryParams.mustache new file mode 100644 index 0000000000..bff9a16d3c --- /dev/null +++ b/mustacheTemplates/libraries/retrofit/queryParams.mustache @@ -0,0 +1 @@ +{{~#is this 'query-param'}}@retrofit.http.Query("{{baseName}}") {{{dataType}}} {{paramName}}{{/is}} \ No newline at end of file diff --git a/mustacheTemplates/libraries/retrofit2/ApiClient.mustache b/mustacheTemplates/libraries/retrofit2/ApiClient.mustache new file mode 100644 index 0000000000..8e81e572cd --- /dev/null +++ b/mustacheTemplates/libraries/retrofit2/ApiClient.mustache @@ -0,0 +1,420 @@ +package {{invokerPackage}}; + +import com.google.gson.Gson; +import com.google.gson.JsonParseException; +import com.google.gson.JsonElement; +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; +import okhttp3.RequestBody; +import okhttp3.ResponseBody; +import org.apache.oltu.oauth2.client.request.OAuthClientRequest.AuthenticationRequestBuilder; +import org.apache.oltu.oauth2.client.request.OAuthClientRequest.TokenRequestBuilder; +{{#joda}} +import org.joda.time.format.DateTimeFormatter; +{{/joda}} +{{#threetenbp}} +import org.threeten.bp.format.DateTimeFormatter; +{{/threetenbp}} +import retrofit2.Converter; +import retrofit2.Retrofit; +{{#useRxJava}} +import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; +{{/useRxJava}} +{{#useRxJava2}} +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; +{{/useRxJava2}} +import retrofit2.converter.gson.GsonConverterFactory; +import retrofit2.converter.scalars.ScalarsConverterFactory; +import {{invokerPackage}}.auth.HttpBasicAuth; +import {{invokerPackage}}.auth.ApiKeyAuth; +import {{invokerPackage}}.auth.OAuth; +import {{invokerPackage}}.auth.OAuth.AccessTokenListener; +import {{invokerPackage}}.auth.OAuthFlow; + +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.text.DateFormat; +{{#java8}} +import java.time.format.DateTimeFormatter; +{{/java8}} +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.HashMap; + +public class ApiClient { + + private Map apiAuthorizations; + private OkHttpClient.Builder okBuilder; + private Retrofit.Builder adapterBuilder; + private JSON json; + + public ApiClient() { + apiAuthorizations = new LinkedHashMap(); + createDefaultAdapter(); + } + + public ApiClient(String[] authNames) { + this(); + for(String authName : authNames) { + {{#hasAuthMethods}} + Interceptor auth; + {{#authMethods}}if ("{{name}}".equals(authName)) { + {{#is this 'basic'}} + auth = new HttpBasicAuth(); + {{/is}} + {{#is this 'api-key'}} + auth = new ApiKeyAuth({{#is this 'key-in-header'}}"header"{{/is}}{{#isNot this 'key-in-header'}}"query"{{/isNot}}, "{{keyParamName}}"); + {{/is}} + {{#is this 'oauth'}} + auth = new OAuth(OAuthFlow.{{flow}}, "{{authorizationUrl}}", "{{tokenUrl}}", "{{#scopes}}{{scope}}{{#hasMore}}, {{/hasMore}}{{/scopes}}"); + {{/is}} + } else {{/authMethods}}{ + throw new RuntimeException("auth name \"" + authName + "\" not found in available auth names"); + } + + addAuthorization(authName, auth); + {{/hasAuthMethods}} + {{^hasAuthMethods}} + throw new RuntimeException("auth name \"" + authName + "\" not found in available auth names"); + {{/hasAuthMethods}} + } + } + + /** + * Basic constructor for single auth name + * @param authName Authentication name + */ + public ApiClient(String authName) { + this(new String[]{authName}); + } + + /** + * Helper constructor for single api key + * @param authName Authentication name + * @param apiKey API key + */ + public ApiClient(String authName, String apiKey) { + this(authName); + this.setApiKey(apiKey); + } + + /** + * Helper constructor for single basic auth or password oauth2 + * @param authName Authentication name + * @param username Username + * @param password Password + */ + public ApiClient(String authName, String username, String password) { + this(authName); + this.setCredentials(username, password); + } + + /** + * Helper constructor for single password oauth2 + * @param authName Authentication name + * @param clientId Client ID + * @param secret Client Secret + * @param username Username + * @param password Password + */ + public ApiClient(String authName, String clientId, String secret, String username, String password) { + this(authName); + this.getTokenEndPoint() + .setClientId(clientId) + .setClientSecret(secret) + .setUsername(username) + .setPassword(password); + } + + public void createDefaultAdapter() { + json = new JSON(); + okBuilder = new OkHttpClient.Builder(); + + String baseUrl = "{{{basePath}}}"; + if (!baseUrl.endsWith("/")) + baseUrl = baseUrl + "/"; + + adapterBuilder = new Retrofit + .Builder() + .baseUrl(baseUrl) + {{#useRxJava}} + .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) + {{/useRxJava}}{{#useRxJava2}} + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + {{/useRxJava2}} + .addConverterFactory(ScalarsConverterFactory.create()) + .addConverterFactory(GsonCustomConverterFactory.create(json.getGson())); + } + + public S createService(Class serviceClass) { + return adapterBuilder + .client(okBuilder.build()) + .build() + .create(serviceClass); + } + + public ApiClient setDateFormat(DateFormat dateFormat) { + this.json.setDateFormat(dateFormat); + return this; + } + + public ApiClient setSqlDateFormat(DateFormat dateFormat) { + this.json.setSqlDateFormat(dateFormat); + return this; + } + + {{#joda}} + public ApiClient setDateTimeFormat(DateTimeFormatter dateFormat) { + this.json.setDateTimeFormat(dateFormat); + return this; + } + + public ApiClient setLocalDateFormat(DateTimeFormatter dateFormat) { + this.json.setLocalDateFormat(dateFormat); + return this; + } + + {{/joda}} + {{#jsr310}} + public ApiClient setOffsetDateTimeFormat(DateTimeFormatter dateFormat) { + this.json.setOffsetDateTimeFormat(dateFormat); + return this; + } + + public ApiClient setLocalDateFormat(DateTimeFormatter dateFormat) { + this.json.setLocalDateFormat(dateFormat); + return this; + } + + {{/jsr310}} + + /** + * Helper method to configure the first api key found + * @param apiKey API key + * @return ApiClient + */ + public ApiClient setApiKey(String apiKey) { + for(Interceptor apiAuthorization : apiAuthorizations.values()) { + if (apiAuthorization instanceof ApiKeyAuth) { + ApiKeyAuth keyAuth = (ApiKeyAuth) apiAuthorization; + keyAuth.setApiKey(apiKey); + return this; + } + } + return this; + } + + /** + * Helper method to configure the username/password for basic auth or password oauth + * @param username Username + * @param password Password + * @return ApiClient + */ + public ApiClient setCredentials(String username, String password) { + for(Interceptor apiAuthorization : apiAuthorizations.values()) { + if (apiAuthorization instanceof HttpBasicAuth) { + HttpBasicAuth basicAuth = (HttpBasicAuth) apiAuthorization; + basicAuth.setCredentials(username, password); + return this; + } + if (apiAuthorization instanceof OAuth) { + OAuth oauth = (OAuth) apiAuthorization; + oauth.getTokenRequestBuilder().setUsername(username).setPassword(password); + return this; + } + } + return this; + } + + /** + * Helper method to configure the token endpoint of the first oauth found in the apiAuthorizations (there should be only one) + * @return Token request builder + */ + public TokenRequestBuilder getTokenEndPoint() { + for(Interceptor apiAuthorization : apiAuthorizations.values()) { + if (apiAuthorization instanceof OAuth) { + OAuth oauth = (OAuth) apiAuthorization; + return oauth.getTokenRequestBuilder(); + } + } + return null; + } + + /** + * Helper method to configure authorization endpoint of the first oauth found in the apiAuthorizations (there should be only one) + * @return Authentication request builder + */ + public AuthenticationRequestBuilder getAuthorizationEndPoint() { + for(Interceptor apiAuthorization : apiAuthorizations.values()) { + if (apiAuthorization instanceof OAuth) { + OAuth oauth = (OAuth) apiAuthorization; + return oauth.getAuthenticationRequestBuilder(); + } + } + return null; + } + + /** + * Helper method to pre-set the oauth access token of the first oauth found in the apiAuthorizations (there should be only one) + * @param accessToken Access token + * @return ApiClient + */ + public ApiClient setAccessToken(String accessToken) { + for(Interceptor apiAuthorization : apiAuthorizations.values()) { + if (apiAuthorization instanceof OAuth) { + OAuth oauth = (OAuth) apiAuthorization; + oauth.setAccessToken(accessToken); + return this; + } + } + return this; + } + + /** + * Helper method to configure the oauth accessCode/implicit flow parameters + * @param clientId Client ID + * @param clientSecret Client secret + * @param redirectURI Redirect URI + * @return ApiClient + */ + public ApiClient configureAuthorizationFlow(String clientId, String clientSecret, String redirectURI) { + for(Interceptor apiAuthorization : apiAuthorizations.values()) { + if (apiAuthorization instanceof OAuth) { + OAuth oauth = (OAuth) apiAuthorization; + oauth.getTokenRequestBuilder() + .setClientId(clientId) + .setClientSecret(clientSecret) + .setRedirectURI(redirectURI); + oauth.getAuthenticationRequestBuilder() + .setClientId(clientId) + .setRedirectURI(redirectURI); + return this; + } + } + return this; + } + + /** + * Configures a listener which is notified when a new access token is received. + * @param accessTokenListener Access token listener + * @return ApiClient + */ + public ApiClient registerAccessTokenListener(AccessTokenListener accessTokenListener) { + for(Interceptor apiAuthorization : apiAuthorizations.values()) { + if (apiAuthorization instanceof OAuth) { + OAuth oauth = (OAuth) apiAuthorization; + oauth.registerAccessTokenListener(accessTokenListener); + return this; + } + } + return this; + } + + /** + * Adds an authorization to be used by the client + * @param authName Authentication name + * @param authorization Authorization interceptor + * @return ApiClient + */ + public ApiClient addAuthorization(String authName, Interceptor authorization) { + if (apiAuthorizations.containsKey(authName)) { + throw new RuntimeException("auth name \"" + authName + "\" already in api authorizations"); + } + apiAuthorizations.put(authName, authorization); + okBuilder.addInterceptor(authorization); + return this; + } + + public Map getApiAuthorizations() { + return apiAuthorizations; + } + + public ApiClient setApiAuthorizations(Map apiAuthorizations) { + this.apiAuthorizations = apiAuthorizations; + return this; + } + + public Retrofit.Builder getAdapterBuilder() { + return adapterBuilder; + } + + public ApiClient setAdapterBuilder(Retrofit.Builder adapterBuilder) { + this.adapterBuilder = adapterBuilder; + return this; + } + + public OkHttpClient.Builder getOkBuilder() { + return okBuilder; + } + + public void addAuthsToOkBuilder(OkHttpClient.Builder okBuilder) { + for(Interceptor apiAuthorization : apiAuthorizations.values()) { + okBuilder.addInterceptor(apiAuthorization); + } + } + + /** + * Clones the okBuilder given in parameter, adds the auth interceptors and uses it to configure the Retrofit + * @param okClient An instance of OK HTTP client + */ + public void configureFromOkclient(OkHttpClient okClient) { + this.okBuilder = okClient.newBuilder(); + addAuthsToOkBuilder(this.okBuilder); + } +} + +/** + * This wrapper is to take care of this case: + * when the deserialization fails due to JsonParseException and the + * expected type is String, then just return the body string. + */ +class GsonResponseBodyConverterToString implements Converter { + private final Gson gson; + private final Type type; + + GsonResponseBodyConverterToString(Gson gson, Type type) { + this.gson = gson; + this.type = type; + } + + @Override public T convert(ResponseBody value) throws IOException { + String returned = value.string(); + try { + return gson.fromJson(returned, type); + } + catch (JsonParseException e) { + return (T) returned; + } + } +} + +class GsonCustomConverterFactory extends Converter.Factory +{ + private final Gson gson; + private final GsonConverterFactory gsonConverterFactory; + + public static GsonCustomConverterFactory create(Gson gson) { + return new GsonCustomConverterFactory(gson); + } + + private GsonCustomConverterFactory(Gson gson) { + if (gson == null) + throw new NullPointerException("gson == null"); + this.gson = gson; + this.gsonConverterFactory = GsonConverterFactory.create(gson); + } + + @Override + public Converter responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { + if (type.equals(String.class)) + return new GsonResponseBodyConverterToString(gson, type); + else + return gsonConverterFactory.responseBodyConverter(type, annotations, retrofit); + } + + @Override + public Converter requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { + return gsonConverterFactory.requestBodyConverter(type, parameterAnnotations, methodAnnotations, retrofit); + } +} diff --git a/mustacheTemplates/libraries/retrofit2/CollectionFormats.mustache b/mustacheTemplates/libraries/retrofit2/CollectionFormats.mustache new file mode 100644 index 0000000000..8c91c335df --- /dev/null +++ b/mustacheTemplates/libraries/retrofit2/CollectionFormats.mustache @@ -0,0 +1,95 @@ +package {{invokerPackage}}; + +import java.util.Arrays; +import java.util.List; + +public class CollectionFormats { + + public static class CSVParams { + + protected List params; + + public CSVParams() { + } + + public CSVParams(List params) { + this.params = params; + } + + public CSVParams(String... params) { + this.params = Arrays.asList(params); + } + + public List getParams() { + return params; + } + + public void setParams(List params) { + this.params = params; + } + + @Override + public String toString() { + return StringUtil.join(params.toArray(new String[0]), ","); + } + + } + + public static class SSVParams extends CSVParams { + + public SSVParams() { + } + + public SSVParams(List params) { + super(params); + } + + public SSVParams(String... params) { + super(params); + } + + @Override + public String toString() { + return StringUtil.join(params.toArray(new String[0]), " "); + } + } + + public static class TSVParams extends CSVParams { + + public TSVParams() { + } + + public TSVParams(List params) { + super(params); + } + + public TSVParams(String... params) { + super(params); + } + + @Override + public String toString() { + return StringUtil.join( params.toArray(new String[0]), "\t"); + } + } + + public static class PIPESParams extends CSVParams { + + public PIPESParams() { + } + + public PIPESParams(List params) { + super(params); + } + + public PIPESParams(String... params) { + super(params); + } + + @Override + public String toString() { + return StringUtil.join(params.toArray(new String[0]), "|"); + } + } + +} diff --git a/mustacheTemplates/libraries/retrofit2/JSON.mustache b/mustacheTemplates/libraries/retrofit2/JSON.mustache new file mode 100644 index 0000000000..cc73a66ed2 --- /dev/null +++ b/mustacheTemplates/libraries/retrofit2/JSON.mustache @@ -0,0 +1,446 @@ +{{>licenseInfo}} + +package {{invokerPackage}}; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonParseException; +import com.google.gson.TypeAdapter; +import com.google.gson.internal.bind.util.ISO8601Utils; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import com.google.gson.JsonElement; +import io.gsonfire.GsonFireBuilder; +import io.gsonfire.TypeSelector; +{{#joda}} +import org.joda.time.DateTime; +import org.joda.time.LocalDate; +import org.joda.time.format.DateTimeFormatter; +import org.joda.time.format.DateTimeFormatterBuilder; +import org.joda.time.format.ISODateTimeFormat; +{{/joda}} +{{#threetenbp}} +import org.threeten.bp.LocalDate; +import org.threeten.bp.OffsetDateTime; +import org.threeten.bp.format.DateTimeFormatter; +{{/threetenbp}} + +{{#hasModel}}import {{modelPackage}}.*;{{/hasModel}} + +import java.io.IOException; +import java.io.StringReader; +import java.lang.reflect.Type; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.ParsePosition; +{{#java8}} +import java.time.LocalDate; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +{{/java8}} +import java.util.Date; +import java.util.Map; +import java.util.HashMap; + +public class JSON { + private Gson gson; + private DateTypeAdapter dateTypeAdapter = new DateTypeAdapter(); + private SqlDateTypeAdapter sqlDateTypeAdapter = new SqlDateTypeAdapter(); + {{#joda}} + private DateTimeTypeAdapter dateTimeTypeAdapter = new DateTimeTypeAdapter(); + private LocalDateTypeAdapter localDateTypeAdapter = new LocalDateTypeAdapter(); + {{/joda}} + {{#jsr310}} + private OffsetDateTimeTypeAdapter offsetDateTimeTypeAdapter = new OffsetDateTimeTypeAdapter(); + private LocalDateTypeAdapter localDateTypeAdapter = new LocalDateTypeAdapter(); + {{/jsr310}} + + public static GsonBuilder createGson() { + GsonFireBuilder fireBuilder = new GsonFireBuilder() + {{#parent}} + .registerTypeSelector({{classname}}.class, new TypeSelector() { + @Override + public Class getClassForElement(JsonElement readElement) { + Map classByDiscriminatorValue = new HashMap(); + {{#children}} + classByDiscriminatorValue.put("{{name}}".toUpperCase(), {{classname}}.class); + {{/children}} + classByDiscriminatorValue.put("{{classname}}".toUpperCase(), {{classname}}.class); + return getClassByDiscriminator( + classByDiscriminatorValue, + getDiscriminatorValue(readElement, "{{discriminator.propertyName}}")); + } + }) + {{/parent}} + ; + return fireBuilder.createGsonBuilder(); + } + + private static String getDiscriminatorValue(JsonElement readElement, String discriminatorField) { + JsonElement element = readElement.getAsJsonObject().get(discriminatorField); + if(null == element) { + throw new IllegalArgumentException("missing discriminator field: <" + discriminatorField + ">"); + } + return element.getAsString(); + } + + private static Class getClassByDiscriminator(Map classByDiscriminatorValue, String discriminatorValue) { + Class clazz = (Class) classByDiscriminatorValue.get(discriminatorValue.toUpperCase()); + if(null == clazz) { + throw new IllegalArgumentException("cannot determine model class of name: <" + discriminatorValue + ">"); + } + return clazz; + } + + public JSON() { + gson = createGson() + .registerTypeAdapter(Date.class, dateTypeAdapter) + .registerTypeAdapter(java.sql.Date.class, sqlDateTypeAdapter) + {{#joda}} + .registerTypeAdapter(DateTime.class, dateTimeTypeAdapter) + .registerTypeAdapter(LocalDate.class, localDateTypeAdapter) + {{/joda}} + {{#jsr310}} + .registerTypeAdapter(OffsetDateTime.class, offsetDateTimeTypeAdapter) + .registerTypeAdapter(LocalDate.class, localDateTypeAdapter) + {{/jsr310}} + .create(); + } + + /** + * Get Gson. + * + * @return Gson + */ + public Gson getGson() { + return gson; + } + + /** + * Set Gson. + * + * @param gson Gson + * @return JSON + */ + public JSON setGson(Gson gson) { + this.gson = gson; + return this; + } + + {{#joda}} + /** + * Gson TypeAdapter for Joda DateTime type + */ + public static class DateTimeTypeAdapter extends TypeAdapter { + + private DateTimeFormatter formatter; + + public DateTimeTypeAdapter() { + this(new DateTimeFormatterBuilder() + .append(ISODateTimeFormat.dateTime().getPrinter(), ISODateTimeFormat.dateOptionalTimeParser().getParser()) + .toFormatter()); + } + + public DateTimeTypeAdapter(DateTimeFormatter formatter) { + this.formatter = formatter; + } + + public void setFormat(DateTimeFormatter dateFormat) { + this.formatter = dateFormat; + } + + @Override + public void write(JsonWriter out, DateTime date) throws IOException { + if (date == null) { + out.nullValue(); + } else { + out.value(formatter.print(date)); + } + } + + @Override + public DateTime read(JsonReader in) throws IOException { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String date = in.nextString(); + return formatter.parseDateTime(date); + } + } + } + + /** + * Gson TypeAdapter for Joda LocalDate type + */ + public class LocalDateTypeAdapter extends TypeAdapter { + + private DateTimeFormatter formatter; + + public LocalDateTypeAdapter() { + this(ISODateTimeFormat.date()); + } + + public LocalDateTypeAdapter(DateTimeFormatter formatter) { + this.formatter = formatter; + } + + public void setFormat(DateTimeFormatter dateFormat) { + this.formatter = dateFormat; + } + + @Override + public void write(JsonWriter out, LocalDate date) throws IOException { + if (date == null) { + out.nullValue(); + } else { + out.value(formatter.print(date)); + } + } + + @Override + public LocalDate read(JsonReader in) throws IOException { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String date = in.nextString(); + return formatter.parseLocalDate(date); + } + } + } + + public JSON setDateTimeFormat(DateTimeFormatter dateFormat) { + dateTimeTypeAdapter.setFormat(dateFormat); + return this; + } + + public JSON setLocalDateFormat(DateTimeFormatter dateFormat) { + localDateTypeAdapter.setFormat(dateFormat); + return this; + } + + {{/joda}} + {{#jsr310}} + /** + * Gson TypeAdapter for JSR310 OffsetDateTime type + */ + public static class OffsetDateTimeTypeAdapter extends TypeAdapter { + + private DateTimeFormatter formatter; + + public OffsetDateTimeTypeAdapter() { + this(DateTimeFormatter.ISO_OFFSET_DATE_TIME); + } + + public OffsetDateTimeTypeAdapter(DateTimeFormatter formatter) { + this.formatter = formatter; + } + + public void setFormat(DateTimeFormatter dateFormat) { + this.formatter = dateFormat; + } + + @Override + public void write(JsonWriter out, OffsetDateTime date) throws IOException { + if (date == null) { + out.nullValue(); + } else { + out.value(formatter.format(date)); + } + } + + @Override + public OffsetDateTime read(JsonReader in) throws IOException { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String date = in.nextString(); + if (date.endsWith("+0000")) { + date = date.substring(0, date.length()-5) + "Z"; + } + return OffsetDateTime.parse(date, formatter); + } + } + } + + /** + * Gson TypeAdapter for JSR310 LocalDate type + */ + public class LocalDateTypeAdapter extends TypeAdapter { + + private DateTimeFormatter formatter; + + public LocalDateTypeAdapter() { + this(DateTimeFormatter.ISO_LOCAL_DATE); + } + + public LocalDateTypeAdapter(DateTimeFormatter formatter) { + this.formatter = formatter; + } + + public void setFormat(DateTimeFormatter dateFormat) { + this.formatter = dateFormat; + } + + @Override + public void write(JsonWriter out, LocalDate date) throws IOException { + if (date == null) { + out.nullValue(); + } else { + out.value(formatter.format(date)); + } + } + + @Override + public LocalDate read(JsonReader in) throws IOException { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String date = in.nextString(); + return LocalDate.parse(date, formatter); + } + } + } + + public JSON setOffsetDateTimeFormat(DateTimeFormatter dateFormat) { + offsetDateTimeTypeAdapter.setFormat(dateFormat); + return this; + } + + public JSON setLocalDateFormat(DateTimeFormatter dateFormat) { + localDateTypeAdapter.setFormat(dateFormat); + return this; + } + + {{/jsr310}} + /** + * Gson TypeAdapter for java.sql.Date type + * If the dateFormat is null, a simple "yyyy-MM-dd" format will be used + * (more efficient than SimpleDateFormat). + */ + public static class SqlDateTypeAdapter extends TypeAdapter { + + private DateFormat dateFormat; + + public SqlDateTypeAdapter() { + } + + public SqlDateTypeAdapter(DateFormat dateFormat) { + this.dateFormat = dateFormat; + } + + public void setFormat(DateFormat dateFormat) { + this.dateFormat = dateFormat; + } + + @Override + public void write(JsonWriter out, java.sql.Date date) throws IOException { + if (date == null) { + out.nullValue(); + } else { + String value; + if (dateFormat != null) { + value = dateFormat.format(date); + } else { + value = date.toString(); + } + out.value(value); + } + } + + @Override + public java.sql.Date read(JsonReader in) throws IOException { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String date = in.nextString(); + try { + if (dateFormat != null) { + return new java.sql.Date(dateFormat.parse(date).getTime()); + } + return new java.sql.Date(ISO8601Utils.parse(date, new ParsePosition(0)).getTime()); + } catch (ParseException e) { + throw new JsonParseException(e); + } + } + } + } + + /** + * Gson TypeAdapter for java.util.Date type + * If the dateFormat is null, ISO8601Utils will be used. + */ + public static class DateTypeAdapter extends TypeAdapter { + + private DateFormat dateFormat; + + public DateTypeAdapter() { + } + + public DateTypeAdapter(DateFormat dateFormat) { + this.dateFormat = dateFormat; + } + + public void setFormat(DateFormat dateFormat) { + this.dateFormat = dateFormat; + } + + @Override + public void write(JsonWriter out, Date date) throws IOException { + if (date == null) { + out.nullValue(); + } else { + String value; + if (dateFormat != null) { + value = dateFormat.format(date); + } else { + value = ISO8601Utils.format(date, true); + } + out.value(value); + } + } + + @Override + public Date read(JsonReader in) throws IOException { + try { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String date = in.nextString(); + try { + if (dateFormat != null) { + return dateFormat.parse(date); + } + return ISO8601Utils.parse(date, new ParsePosition(0)); + } catch (ParseException e) { + throw new JsonParseException(e); + } + } + } catch (IllegalArgumentException e) { + throw new JsonParseException(e); + } + } + } + + public JSON setDateFormat(DateFormat dateFormat) { + dateTypeAdapter.setFormat(dateFormat); + return this; + } + + public JSON setSqlDateFormat(DateFormat dateFormat) { + sqlDateTypeAdapter.setFormat(dateFormat); + return this; + } + +} diff --git a/mustacheTemplates/libraries/retrofit2/README.mustache b/mustacheTemplates/libraries/retrofit2/README.mustache new file mode 100644 index 0000000000..9d744b8589 --- /dev/null +++ b/mustacheTemplates/libraries/retrofit2/README.mustache @@ -0,0 +1,39 @@ +# {{artifactId}} + +## Requirements + +Building the API client library requires [Maven](https://maven.apache.org/) to be installed. + +## Installation & Usage + +To install the API client library to your local Maven repository, simply execute: + +```shell +mvn install +``` + +To deploy it to a remote Maven repository instead, configure the settings of the repository and execute: + +```shell +mvn deploy +``` + +Refer to the [official documentation](https://maven.apache.org/plugins/maven-deploy-plugin/usage.html) for more information. + +After the client library is installed/deployed, you can use it in your Maven project by adding the following to your *pom.xml*: + +```xml + + {{groupId}} + {{artifactId}} + {{artifactVersion}} + compile + + +``` + +## Author + +{{#apiInfo}}{{#apis}}{{^hasMore}}{{infoEmail}} +{{/hasMore}}{{/apis}}{{/apiInfo}} + diff --git a/mustacheTemplates/libraries/retrofit2/api.mustache b/mustacheTemplates/libraries/retrofit2/api.mustache new file mode 100644 index 0000000000..ccfe0df071 --- /dev/null +++ b/mustacheTemplates/libraries/retrofit2/api.mustache @@ -0,0 +1,67 @@ +package {{package}}; + +import {{invokerPackage}}.CollectionFormats.*; + +{{#useRxJava}} +import rx.Observable; +{{/useRxJava}} +{{#useRxJava2}} +import io.reactivex.Observable; +{{/useRxJava2}} +{{#doNotUseRx}} +import retrofit2.Call; +{{/doNotUseRx}} +import retrofit2.http.*; + +import okhttp3.RequestBody; +import okhttp3.ResponseBody; + +{{#imports}}import {{import}}; +{{/imports}} + +{{^fullJavaUtil}} +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +{{/fullJavaUtil}} + +{{#operations}} +public interface {{classname}} { + {{#operation}} + {{#contents}} + /** + * {{summary}} + * {{notes}} +{{#parameters}} + * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}} +{{/parameters}} + * @return Call<{{#returnType}}{{returnType}}{{/returnType}}{{^returnType}}Object{{/returnType}}> +{{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation +{{/externalDocs}} + */ + {{#formParams}} + {{#@first}} + {{#is ../this 'multipart'}}@retrofit2.http.Multipart{{/is}}{{#isNot ../this 'multipart'}}@retrofit2.http.FormUrlEncoded{{/isNot}} + {{/@first}} + {{/formParams}} + {{^formParams}} + {{#prioritizedContentTypes}} + {{#@first}} + @Headers({ + "Content-Type:{{mediaType}}" + }) + {{/@first}} + {{/prioritizedContentTypes}} + {{/formParams}} + @{{httpMethod}}("{{{path}}}") + {{^doNotUseRx}}Observable{{/doNotUseRx}}{{#doNotUseRx}}Call{{/doNotUseRx}}<{{#isResponseFile}}ResponseBody{{/isResponseFile}}{{^isResponseFile}}{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Object{{/returnType}}{{/isResponseFile}}> {{operationId}}({{^parameters}});{{/parameters}} + {{#parameters}}{{>libraries/retrofit2/queryParams}}{{>libraries/retrofit2/pathParams}}{{>libraries/retrofit2/headerParams}}{{>libraries/retrofit2/bodyParams}}{{>libraries/retrofit2/formParams}}{{>libraries/retrofit2/cookieParams}}{{#has this 'more'}}, {{/has}}{{#hasNot this 'more'}} + );{{/hasNot}}{{/parameters}} + + {{/contents}} + {{/operation}} +} +{{/operations}} diff --git a/mustacheTemplates/libraries/retrofit2/api_test.mustache b/mustacheTemplates/libraries/retrofit2/api_test.mustache new file mode 100644 index 0000000000..7c4e8e21ce --- /dev/null +++ b/mustacheTemplates/libraries/retrofit2/api_test.mustache @@ -0,0 +1,51 @@ +package {{package}}; + +import {{invokerPackage}}.ApiClient; +{{#imports}}import {{import}}; +{{/imports}} +import org.junit.Before; +import org.junit.Test; + +{{^fullJavaUtil}} +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +{{/fullJavaUtil}} + +/** + * API tests for {{classname}} + */ +public class {{classname}}Test { + + private {{classname}} api; + + @Before + public void setup() { + api = new ApiClient().createService({{classname}}.class); + } + + {{#operations}} + {{#operation}} + {{#contents}} + {{#@first}} + + /** + * {{summary}} + * + * {{notes}} + */ + @Test + public void {{operationId}}Test() { + {{#parameters}} + {{{dataType}}} {{paramName}} = null; + {{/parameters}} + // {{#returnType}}{{{returnType}}} response = {{/returnType}}api.{{operationId}}({{#parameters}}{{paramName}}{{#has this 'more'}}, {{/has}}{{/parameters}}); + + // TODO: test validations + } + {{/@first}} + {{/contents}} + {{/operation}} + {{/operations}} +} diff --git a/mustacheTemplates/libraries/retrofit2/auth/ApiKeyAuth.mustache b/mustacheTemplates/libraries/retrofit2/auth/ApiKeyAuth.mustache new file mode 100644 index 0000000000..2f4bae3dd0 --- /dev/null +++ b/mustacheTemplates/libraries/retrofit2/auth/ApiKeyAuth.mustache @@ -0,0 +1,68 @@ +package {{invokerPackage}}.auth; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +public class ApiKeyAuth implements Interceptor { + private final String location; + private final String paramName; + + private String apiKey; + + public ApiKeyAuth(String location, String paramName) { + this.location = location; + this.paramName = paramName; + } + + public String getLocation() { + return location; + } + + public String getParamName() { + return paramName; + } + + public String getApiKey() { + return apiKey; + } + + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } + + @Override + public Response intercept(Chain chain) throws IOException { + String paramValue; + Request request = chain.request(); + + if ("query".equals(location)) { + String newQuery = request.url().uri().getQuery(); + paramValue = paramName + "=" + apiKey; + if (newQuery == null) { + newQuery = paramValue; + } else { + newQuery += "&" + paramValue; + } + + URI newUri; + try { + newUri = new URI(request.url().uri().getScheme(), request.url().uri().getAuthority(), + request.url().uri().getPath(), newQuery, request.url().uri().getFragment()); + } catch (URISyntaxException e) { + throw new IOException(e); + } + + request = request.newBuilder().url(newUri.toURL()).build(); + } else if ("header".equals(location)) { + request = request.newBuilder() + .addHeader(paramName, apiKey) + .build(); + } + return chain.proceed(request); + } +} diff --git a/mustacheTemplates/libraries/retrofit2/auth/HttpBasicAuth.mustache b/mustacheTemplates/libraries/retrofit2/auth/HttpBasicAuth.mustache new file mode 100644 index 0000000000..d458842666 --- /dev/null +++ b/mustacheTemplates/libraries/retrofit2/auth/HttpBasicAuth.mustache @@ -0,0 +1,50 @@ +package {{invokerPackage}}.auth; + +import java.io.IOException; + +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.Credentials; + +public class HttpBasicAuth implements Interceptor { + + private String username; + private String password; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public void setCredentials(String username, String password) { + this.username = username; + this.password = password; + } + + @Override + public Response intercept(Chain chain) throws IOException { + Request request = chain.request(); + + // If the request already have an authorization (eg. Basic auth), do nothing + if (request.header("Authorization") == null) { + String credentials = Credentials.basic(username, password); + request = request.newBuilder() + .addHeader("Authorization", credentials) + .build(); + } + return chain.proceed(request); + } +} diff --git a/mustacheTemplates/libraries/retrofit2/auth/OAuth.mustache b/mustacheTemplates/libraries/retrofit2/auth/OAuth.mustache new file mode 100644 index 0000000000..e3b3727cd8 --- /dev/null +++ b/mustacheTemplates/libraries/retrofit2/auth/OAuth.mustache @@ -0,0 +1,179 @@ +package {{invokerPackage}}.auth; + +import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; +import static java.net.HttpURLConnection.HTTP_FORBIDDEN; + +import java.io.IOException; +import java.util.Map; + +import org.apache.oltu.oauth2.client.OAuthClient; +import org.apache.oltu.oauth2.client.request.OAuthBearerClientRequest; +import org.apache.oltu.oauth2.client.request.OAuthClientRequest; +import org.apache.oltu.oauth2.client.request.OAuthClientRequest.AuthenticationRequestBuilder; +import org.apache.oltu.oauth2.client.request.OAuthClientRequest.TokenRequestBuilder; +import org.apache.oltu.oauth2.client.response.OAuthJSONAccessTokenResponse; +import org.apache.oltu.oauth2.common.exception.OAuthProblemException; +import org.apache.oltu.oauth2.common.exception.OAuthSystemException; +import org.apache.oltu.oauth2.common.message.types.GrantType; +import org.apache.oltu.oauth2.common.token.BasicOAuthToken; + +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Request.Builder; +import okhttp3.Response; + +public class OAuth implements Interceptor { + + public interface AccessTokenListener { + public void notify(BasicOAuthToken token); + } + + private volatile String accessToken; + private OAuthClient oauthClient; + + private TokenRequestBuilder tokenRequestBuilder; + private AuthenticationRequestBuilder authenticationRequestBuilder; + + private AccessTokenListener accessTokenListener; + + public OAuth( OkHttpClient client, TokenRequestBuilder requestBuilder ) { + this.oauthClient = new OAuthClient(new OAuthOkHttpClient(client)); + this.tokenRequestBuilder = requestBuilder; + } + + public OAuth(TokenRequestBuilder requestBuilder ) { + this(new OkHttpClient(), requestBuilder); + } + + public OAuth(OAuthFlow flow, String authorizationUrl, String tokenUrl, String scopes) { + this(OAuthClientRequest.tokenLocation(tokenUrl).setScope(scopes)); + setFlow(flow); + authenticationRequestBuilder = OAuthClientRequest.authorizationLocation(authorizationUrl); + } + + public void setFlow(OAuthFlow flow) { + switch(flow) { + case accessCode: + case implicit: + tokenRequestBuilder.setGrantType(GrantType.AUTHORIZATION_CODE); + break; + case password: + tokenRequestBuilder.setGrantType(GrantType.PASSWORD); + break; + case application: + tokenRequestBuilder.setGrantType(GrantType.CLIENT_CREDENTIALS); + break; + default: + break; + } + } + + @Override + public Response intercept(Chain chain) + throws IOException { + + return retryingIntercept(chain, true); + } + + private Response retryingIntercept(Chain chain, boolean updateTokenAndRetryOnAuthorizationFailure) throws IOException { + Request request = chain.request(); + + // If the request already have an authorization (eg. Basic auth), do nothing + if (request.header("Authorization") != null) { + return chain.proceed(request); + } + + // If first time, get the token + OAuthClientRequest oAuthRequest; + if (getAccessToken() == null) { + updateAccessToken(null); + } + + if (getAccessToken() != null) { + // Build the request + Builder rb = request.newBuilder(); + + String requestAccessToken = new String(getAccessToken()); + try { + oAuthRequest = new OAuthBearerClientRequest(request.url().toString()) + .setAccessToken(requestAccessToken) + .buildHeaderMessage(); + } catch (OAuthSystemException e) { + throw new IOException(e); + } + + for ( Map.Entry header : oAuthRequest.getHeaders().entrySet() ) { + rb.addHeader(header.getKey(), header.getValue()); + } + rb.url( oAuthRequest.getLocationUri()); + + //Execute the request + Response response = chain.proceed(rb.build()); + + // 401/403 most likely indicates that access token has expired. Unless it happens two times in a row. + if ( response != null && (response.code() == HTTP_UNAUTHORIZED || response.code() == HTTP_FORBIDDEN) && updateTokenAndRetryOnAuthorizationFailure ) { + if (updateAccessToken(requestAccessToken)) { + return retryingIntercept( chain, false ); + } + } + return response; + } else { + return chain.proceed(chain.request()); + } + } + + /* + * Returns true if the access token has been updated + */ + public synchronized boolean updateAccessToken(String requestAccessToken) throws IOException { + if (getAccessToken() == null || getAccessToken().equals(requestAccessToken)) { + try { + OAuthJSONAccessTokenResponse accessTokenResponse = oauthClient.accessToken(this.tokenRequestBuilder.buildBodyMessage()); + if (accessTokenResponse != null && accessTokenResponse.getAccessToken() != null) { + setAccessToken(accessTokenResponse.getAccessToken()); + if (accessTokenListener != null) { + accessTokenListener.notify((BasicOAuthToken) accessTokenResponse.getOAuthToken()); + } + return !getAccessToken().equals(requestAccessToken); + } else { + return false; + } + } catch (OAuthSystemException e) { + throw new IOException(e); + } catch (OAuthProblemException e) { + throw new IOException(e); + } + } + return true; + } + + public void registerAccessTokenListener(AccessTokenListener accessTokenListener) { + this.accessTokenListener = accessTokenListener; + } + + public synchronized String getAccessToken() { + return accessToken; + } + + public synchronized void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public TokenRequestBuilder getTokenRequestBuilder() { + return tokenRequestBuilder; + } + + public void setTokenRequestBuilder(TokenRequestBuilder tokenRequestBuilder) { + this.tokenRequestBuilder = tokenRequestBuilder; + } + + public AuthenticationRequestBuilder getAuthenticationRequestBuilder() { + return authenticationRequestBuilder; + } + + public void setAuthenticationRequestBuilder(AuthenticationRequestBuilder authenticationRequestBuilder) { + this.authenticationRequestBuilder = authenticationRequestBuilder; + } + +} diff --git a/mustacheTemplates/libraries/retrofit2/auth/OAuthOkHttpClient.mustache b/mustacheTemplates/libraries/retrofit2/auth/OAuthOkHttpClient.mustache new file mode 100644 index 0000000000..b03edebf12 --- /dev/null +++ b/mustacheTemplates/libraries/retrofit2/auth/OAuthOkHttpClient.mustache @@ -0,0 +1,73 @@ +package {{invokerPackage}}.auth; + +import java.io.IOException; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.oltu.oauth2.client.HttpClient; +import org.apache.oltu.oauth2.client.request.OAuthClientRequest; +import org.apache.oltu.oauth2.client.response.OAuthClientResponse; +import org.apache.oltu.oauth2.client.response.OAuthClientResponseFactory; +import org.apache.oltu.oauth2.common.exception.OAuthProblemException; +import org.apache.oltu.oauth2.common.exception.OAuthSystemException; + + +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Request.Builder; +import okhttp3.Response; +import okhttp3.MediaType; +import okhttp3.RequestBody; + + +public class OAuthOkHttpClient implements HttpClient { + + private OkHttpClient client; + + public OAuthOkHttpClient() { + this.client = new OkHttpClient(); + } + + public OAuthOkHttpClient(OkHttpClient client) { + this.client = client; + } + + public T execute(OAuthClientRequest request, Map headers, + String requestMethod, Class responseClass) + throws OAuthSystemException, OAuthProblemException { + + MediaType mediaType = MediaType.parse("application/json"); + Request.Builder requestBuilder = new Request.Builder().url(request.getLocationUri()); + + if(headers != null) { + for (Entry entry : headers.entrySet()) { + if (entry.getKey().equalsIgnoreCase("Content-Type")) { + mediaType = MediaType.parse(entry.getValue()); + } else { + requestBuilder.addHeader(entry.getKey(), entry.getValue()); + } + } + } + + RequestBody body = request.getBody() != null ? RequestBody.create(mediaType, request.getBody()) : null; + requestBuilder.method(requestMethod, body); + + try { + Response response = client.newCall(requestBuilder.build()).execute(); + return OAuthClientResponseFactory.createCustomResponse( + response.body().string(), + response.body().contentType().toString(), + response.code(), + response.headers().toMultimap(), + responseClass); + } catch (IOException e) { + throw new OAuthSystemException(e); + } + } + + public void shutdown() { + // Nothing to do here + } + +} diff --git a/mustacheTemplates/libraries/retrofit2/bodyParams.mustache b/mustacheTemplates/libraries/retrofit2/bodyParams.mustache new file mode 100644 index 0000000000..9f2b6b0956 --- /dev/null +++ b/mustacheTemplates/libraries/retrofit2/bodyParams.mustache @@ -0,0 +1 @@ +{{#is this 'body-param'}}@retrofit2.http.Body {{{dataType}}} {{paramName}}{{/is}} \ No newline at end of file diff --git a/mustacheTemplates/libraries/retrofit2/build.gradle.mustache b/mustacheTemplates/libraries/retrofit2/build.gradle.mustache new file mode 100644 index 0000000000..523bf65124 --- /dev/null +++ b/mustacheTemplates/libraries/retrofit2/build.gradle.mustache @@ -0,0 +1,172 @@ +apply plugin: 'idea' +apply plugin: 'eclipse' + +group = '{{groupId}}' +version = '{{artifactVersion}}' + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.3.+' + classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' + } +} + +repositories { + jcenter() +} + + +if(hasProperty('target') && target == 'android') { + + apply plugin: 'com.android.library' + apply plugin: 'com.github.dcendents.android-maven' + + android { + compileSdkVersion 25 + buildToolsVersion '25.0.2' + defaultConfig { + minSdkVersion 14 + targetSdkVersion 25 + } + compileOptions { + {{#java8}} + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + {{/java8}} + {{^java8}} + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 + {{/java8}} + } + + // Rename the aar correctly + libraryVariants.all { variant -> + variant.outputs.each { output -> + def outputFile = output.outputFile + if (outputFile != null && outputFile.name.endsWith('.aar')) { + def fileName = "${project.name}-${variant.baseName}-${version}.aar" + output.outputFile = new File(outputFile.parent, fileName) + } + } + } + + dependencies { + provided 'javax.annotation:jsr250-api:1.0' + } + } + + afterEvaluate { + android.libraryVariants.all { variant -> + def task = project.tasks.create "jar${variant.name.capitalize()}", Jar + task.description = "Create jar artifact for ${variant.name}" + task.dependsOn variant.javaCompile + task.from variant.javaCompile.destinationDir + task.destinationDir = project.file("${project.buildDir}/outputs/jar") + task.archiveName = "${project.name}-${variant.baseName}-${version}.jar" + artifacts.add('archives', task); + } + } + + task sourcesJar(type: Jar) { + from android.sourceSets.main.java.srcDirs + classifier = 'sources' + } + + artifacts { + archives sourcesJar + } + +} else { + + apply plugin: 'java' + apply plugin: 'maven' + + sourceCompatibility = JavaVersion.VERSION_{{^java8}}1_7{{/java8}}{{#java8}}1_8{{/java8}} + targetCompatibility = JavaVersion.VERSION_{{^java8}}1_7{{/java8}}{{#java8}}1_8{{/java8}} + + install { + repositories.mavenInstaller { + pom.artifactId = '{{artifactId}}' + } + } + + task execute(type:JavaExec) { + main = System.getProperty('mainClass') + classpath = sourceSets.main.runtimeClasspath + } +} + +ext { + oltu_version = "1.0.2" + retrofit_version = "2.3.0" + {{#usePlayWS}} + {{#play24}} + jackson_version = "2.6.6" + play_version = "2.4.11" + {{/play24}} + {{#play25}} + jackson_version = "2.7.8" + play_version = "2.5.14" + {{/play25}} + {{/usePlayWS}} + {{#useOas2}} + swagger_annotations_version = "1.5.15" + {{/useOas2}} + {{^useOas2}} + swagger_annotations_version = "2.0.0" + {{/useOas2}} + junit_version = "4.12" + {{#useRxJava}} + rx_java_version = "1.3.0" + {{/useRxJava}} + {{#useRxJava2}} + rx_java_version = "2.1.1" + {{/useRxJava2}} + {{#joda}} + jodatime_version = "2.9.9" + {{/joda}} + {{#threetenbp}} + threetenbp_version = "1.3.5" + {{/threetenbp}} + json_fire_version = "1.8.0" +} + +dependencies { + compile "com.squareup.retrofit2:retrofit:$retrofit_version" + compile "com.squareup.retrofit2:converter-scalars:$retrofit_version" + compile "com.squareup.retrofit2:converter-gson:$retrofit_version" + {{#useRxJava}} + compile "com.squareup.retrofit2:adapter-rxjava:$retrofit_version" + compile "io.reactivex:rxjava:$rx_java_version" + {{/useRxJava}} + {{#useRxJava2}} + compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' + compile "io.reactivex.rxjava2:rxjava:$rx_java_version" + {{/useRxJava2}} + {{#useOas2}} + compile "io.swagger:swagger-annotations:$swagger_annotations_version" + {{/useOas2}} + {{^useOas2}} + compile "io.swagger.core.v3:swagger-annotations:$swagger_annotations_version" + {{/useOas2}} + compile "org.apache.oltu.oauth2:org.apache.oltu.oauth2.client:$oltu_version" + compile "io.gsonfire:gson-fire:$json_fire_version" + {{#joda}} + compile "joda-time:joda-time:$jodatime_version" + {{/joda}} + {{#threetenbp}} + compile "org.threeten:threetenbp:$threetenbp_version" + {{/threetenbp}} + {{#usePlayWS}} + compile "com.typesafe.play:play-java-ws_2.11:$play_version" + compile "com.squareup.retrofit2:converter-jackson:$retrofit_version" + compile "com.fasterxml.jackson.core:jackson-core:$jackson_version" + compile "com.fasterxml.jackson.core:jackson-annotations:$jackson_version" + compile "com.fasterxml.jackson.datatype:jackson-datatype-{{^java8}}joda{{/java8}}{{#java8}}jsr310{{/java8}}:$jackson_version" + {{/usePlayWS}} + + testCompile "junit:junit:$junit_version" +} diff --git a/mustacheTemplates/libraries/retrofit2/build.sbt.mustache b/mustacheTemplates/libraries/retrofit2/build.sbt.mustache new file mode 100644 index 0000000000..facba4f764 --- /dev/null +++ b/mustacheTemplates/libraries/retrofit2/build.sbt.mustache @@ -0,0 +1,57 @@ +lazy val root = (project in file(".")). + settings( + organization := "{{groupId}}", + name := "{{artifactId}}", + version := "{{artifactVersion}}", + scalaVersion := "2.11.4", + scalacOptions ++= Seq("-feature"), + javacOptions in compile ++= Seq("-Xlint:deprecation"), + publishArtifact in (Compile, packageDoc) := false, + resolvers += Resolver.mavenLocal, + libraryDependencies ++= Seq( + "com.squareup.retrofit2" % "retrofit" % "2.3.0" % "compile", + "com.squareup.retrofit2" % "converter-scalars" % "2.3.0" % "compile", + {{^usePlayWS}} + "com.squareup.retrofit2" % "converter-gson" % "2.3.0" % "compile", + {{/usePlayWS}} + {{#usePlayWS}} + {{#play24}} + "com.typesafe.play" % "play-java-ws_2.11" % "2.4.11" % "compile", + "com.fasterxml.jackson.core" % "jackson-core" % "2.6.6" % "compile", + "com.fasterxml.jackson.core" % "jackson-annotations" % "2.6.6" % "compile", + "com.fasterxml.jackson.core" % "jackson-databind" % "2.6.6" % "compile", + {{/play24}} + {{#play25}} + "com.typesafe.play" % "play-java-ws_2.11" % "2.5.15" % "compile", + "com.fasterxml.jackson.core" % "jackson-core" % "2.7.8" % "compile", + "com.fasterxml.jackson.core" % "jackson-annotations" % "2.7.8" % "compile", + "com.fasterxml.jackson.core" % "jackson-databind" % "2.7.8" % "compile", + {{/play25}} + "com.squareup.retrofit2" % "converter-jackson" % "2.3.0" % "compile", + {{/usePlayWS}} + {{#useRxJava}} + "com.squareup.retrofit2" % "adapter-rxjava" % "2.3.0" % "compile", + "io.reactivex" % "rxjava" % "1.3.0" % "compile", + {{/useRxJava}} + {{#useRxJava2}} + "com.squareup.retrofit2" % "adapter-rxjava2" % "2.3.0" % "compile", + "io.reactivex.rxjava2" % "rxjava" % "2.1.1" % "compile", + {{/useRxJava2}} + {{#useOas2}} + "io.swagger" % "swagger-annotations" % "1.5.15" % "compile", + {{/useOas2}} + {{^useOas2}} + "io.swagger.core.v3" % "swagger-annotations" % "2.0.0" % "compile", + {{/useOas2}} + "org.apache.oltu.oauth2" % "org.apache.oltu.oauth2.client" % "1.0.2" % "compile", + {{#joda}} + "joda-time" % "joda-time" % "2.9.9" % "compile", + {{/joda}} + {{#threetenbp}} + "org.threeten" % "threetenbp" % "1.3.5" % "compile", + {{/threetenbp}} + "io.gsonfire" % "gson-fire" % "1.8.0" % "compile", + "junit" % "junit" % "4.12" % "test", + "com.novocode" % "junit-interface" % "0.11" % "test" + ) + ) diff --git a/mustacheTemplates/libraries/retrofit2/cookieParams.mustache b/mustacheTemplates/libraries/retrofit2/cookieParams.mustache new file mode 100644 index 0000000000..cdb1e2289f --- /dev/null +++ b/mustacheTemplates/libraries/retrofit2/cookieParams.mustache @@ -0,0 +1 @@ +{{~#is this 'cookie-param'}}{{{dataType}}} {{paramName}}{{/is}} \ No newline at end of file diff --git a/mustacheTemplates/libraries/retrofit2/formParams.mustache b/mustacheTemplates/libraries/retrofit2/formParams.mustache new file mode 100644 index 0000000000..0b58db0b40 --- /dev/null +++ b/mustacheTemplates/libraries/retrofit2/formParams.mustache @@ -0,0 +1 @@ +{{#is this 'form-param'}}{{#isNot this 'binary'}}{{#is this 'multipart'}}@retrofit2.http.Part{{/is}}{{#isNot this 'multipart'}}@retrofit2.http.Field{{/isNot}}("{{baseName}}") {{{dataType}}} {{paramName}}{{/isNot}}{{#is this 'binary'}}{{#is this 'multipart'}}@retrofit2.http.Part{{/is}}{{#isNot this 'multipart'}}@retrofit2.http.Field{{/isNot}}{{#usePlayWS}} okhttp3.MultipartBody.Part {{/usePlayWS}}{{^usePlayWS}}("{{baseName}}\"; filename=\"{{baseName}}") RequestBody {{/usePlayWS}}{{paramName}}{{/is}}{{/is}} \ No newline at end of file diff --git a/mustacheTemplates/libraries/retrofit2/headerParams.mustache b/mustacheTemplates/libraries/retrofit2/headerParams.mustache new file mode 100644 index 0000000000..d5ffdb4144 --- /dev/null +++ b/mustacheTemplates/libraries/retrofit2/headerParams.mustache @@ -0,0 +1 @@ +{{#is this 'header-param'}}@retrofit2.http.Header("{{baseName}}") {{{dataType}}} {{paramName}}{{/is}} \ No newline at end of file diff --git a/mustacheTemplates/libraries/retrofit2/pathParams.mustache b/mustacheTemplates/libraries/retrofit2/pathParams.mustache new file mode 100644 index 0000000000..56ea06a337 --- /dev/null +++ b/mustacheTemplates/libraries/retrofit2/pathParams.mustache @@ -0,0 +1 @@ +{{#is this 'path-param'}}@retrofit2.http.Path("{{baseName}}") {{{dataType}}} {{paramName}}{{/is}} \ No newline at end of file diff --git a/mustacheTemplates/libraries/retrofit2/play-common/auth/ApiKeyAuth.mustache b/mustacheTemplates/libraries/retrofit2/play-common/auth/ApiKeyAuth.mustache new file mode 100644 index 0000000000..5652db326b --- /dev/null +++ b/mustacheTemplates/libraries/retrofit2/play-common/auth/ApiKeyAuth.mustache @@ -0,0 +1,67 @@ +{{>licenseInfo}} + +package {{invokerPackage}}.auth; + +import {{invokerPackage}}.Pair; + +import java.util.Map; +import java.util.List; + +/** + * Holds ApiKey auth info + */ +{{>generatedAnnotation}} +public class ApiKeyAuth implements Authentication { + private final String location; + private final String paramName; + + private String apiKey; + private String apiKeyPrefix; + + public ApiKeyAuth(String location, String paramName) { + this.location = location; + this.paramName = paramName; + } + + public String getLocation() { + return location; + } + + public String getParamName() { + return paramName; + } + + public String getApiKey() { + return apiKey; + } + + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } + + public String getApiKeyPrefix() { + return apiKeyPrefix; + } + + public void setApiKeyPrefix(String apiKeyPrefix) { + this.apiKeyPrefix = apiKeyPrefix; + } + + @Override + public void applyToParams(List queryParams, Map headerParams) { + if (apiKey == null) { + return; + } + String value; + if (apiKeyPrefix != null) { + value = apiKeyPrefix + " " + apiKey; + } else { + value = apiKey; + } + if ("query".equals(location)) { + queryParams.add(new Pair(paramName, value)); + } else if ("header".equals(location)) { + headerParams.put(paramName, value); + } + } +} diff --git a/mustacheTemplates/libraries/retrofit2/play24/ApiClient.mustache b/mustacheTemplates/libraries/retrofit2/play24/ApiClient.mustache new file mode 100644 index 0000000000..796fbcce26 --- /dev/null +++ b/mustacheTemplates/libraries/retrofit2/play24/ApiClient.mustache @@ -0,0 +1,136 @@ +package {{invokerPackage}}; + +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.*; + +import retrofit2.Retrofit; +import retrofit2.converter.scalars.ScalarsConverterFactory; +import retrofit2.converter.jackson.JacksonConverterFactory; + +import play.libs.Json; +import play.libs.ws.WSClient; + +import {{invokerPackage}}.Play24CallAdapterFactory; +import {{invokerPackage}}.Play24CallFactory; + +import okhttp3.Interceptor; +import {{invokerPackage}}.auth.ApiKeyAuth; +import {{invokerPackage}}.auth.Authentication; + +/** + * API client + */ +public class ApiClient { + + /** Underlying HTTP-client */ + private WSClient wsClient; + + /** Supported auths */ + private Map authentications; + + /** API base path */ + private String basePath = "{{{basePath}}}"; + + public ApiClient(WSClient wsClient) { + this(); + this.wsClient = wsClient; + } + + public ApiClient() { + // Setup authentications (key: authentication name, value: authentication). + authentications = new HashMap<>();{{#authMethods}}{{#is this 'basic'}} + // authentications.put("{{name}}", new HttpBasicAuth());{{/is}}{{#is this 'api-key'}} + authentications.put("{{name}}", new ApiKeyAuth({{#is this 'key-in-header'}}"header"{{/is}}{{#isNot this 'key-in-header'}}"query"{{/isNot}}, "{{keyParamName}}"));{{/is}}{{#is this 'oauth'}} + // authentications.put("{{name}}", new OAuth());{{/is}}{{/authMethods}} + // Prevent the authentications from being modified. + authentications = Collections.unmodifiableMap(authentications); + + } + + /** + * Creates a retrofit2 client for given API interface + */ + public S createService(Class serviceClass) { + if(!basePath.endsWith("/")) { + basePath = basePath + "/"; + } + + Map extraHeaders = new HashMap<>(); + List extraQueryParams = new ArrayList<>(); + + for (String authName : authentications.keySet()) { + Authentication auth = authentications.get(authName); + if (auth == null) throw new RuntimeException("Authentication undefined: " + authName); + + auth.applyToParams(extraQueryParams, extraHeaders); + } + + return new Retrofit.Builder() + .baseUrl(basePath) + .addConverterFactory(ScalarsConverterFactory.create()) + .addConverterFactory(JacksonConverterFactory.create(Json.mapper())) + .callFactory(new Play24CallFactory(wsClient, extraHeaders, extraQueryParams)) + .addCallAdapterFactory(new Play24CallAdapterFactory()) + .build() + .create(serviceClass); + } + + /** + * Helper method to set API base path + */ + public ApiClient setBasePath(String basePath) { + this.basePath = basePath; + return this; + } + + /** + * Get authentications (key: authentication name, value: authentication). + */ + public Map getAuthentications() { + return authentications; + } + + /** + * Get authentication for the given name. + * + * @param authName The authentication name + * @return The authentication, null if not found + */ + public Authentication getAuthentication(String authName) { + return authentications.get(authName); + } + + /** + * Helper method to set API key value for the first API key authentication. + */ + public ApiClient setApiKey(String apiKey) { + for (Authentication auth : authentications.values()) { + if (auth instanceof ApiKeyAuth) { + ((ApiKeyAuth) auth).setApiKey(apiKey); + return this; + } + } + + throw new RuntimeException("No API key authentication configured!"); + } + + /** + * Helper method to set API key prefix for the first API key authentication. + */ + public ApiClient setApiKeyPrefix(String apiKeyPrefix) { + for (Authentication auth : authentications.values()) { + if (auth instanceof ApiKeyAuth) { + ((ApiKeyAuth) auth).setApiKeyPrefix(apiKeyPrefix); + return this; + } + } + + throw new RuntimeException("No API key authentication configured!"); + } + + +} + + diff --git a/mustacheTemplates/libraries/retrofit2/play24/Play24CallAdapterFactory.mustache b/mustacheTemplates/libraries/retrofit2/play24/Play24CallAdapterFactory.mustache new file mode 100644 index 0000000000..aa4391a9f1 --- /dev/null +++ b/mustacheTemplates/libraries/retrofit2/play24/Play24CallAdapterFactory.mustache @@ -0,0 +1,100 @@ +package {{invokerPackage}}; + +import play.libs.F; +import retrofit2.*; + +import java.lang.annotation.Annotation; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.WildcardType; + +/** + * Creates {@link CallAdapter} instances that convert {@link Call} into {@link play.libs.F.Promise} + */ +public class Play24CallAdapterFactory extends CallAdapter.Factory { + + @Override + public CallAdapter get(Type returnType, Annotation[] annotations, Retrofit retrofit) { + if (!(returnType instanceof ParameterizedType)) { + return null; + } + + ParameterizedType type = (ParameterizedType) returnType; + if (type.getRawType() != F.Promise.class) { + return null; + } + + return createAdapter((ParameterizedType) returnType); + } + + private CallAdapter> createAdapter(ParameterizedType returnType) { + Type[] types = returnType.getActualTypeArguments(); + if (types.length != 1) { + throw new IllegalStateException("Must be exactly one type parameter"); + } + + Type resultType = types[0]; + Class rawTypeParam = getRawType(resultType); + + boolean includeResponse = false; + if (rawTypeParam == Response.class) { + if (!(resultType instanceof ParameterizedType)) { + throw new IllegalStateException("Response must be parameterized" + + " as Response"); + } + resultType = ((ParameterizedType) resultType).getActualTypeArguments()[0]; + includeResponse = true; + } + + return new ValueAdapter(resultType, includeResponse); + } + + /** + * Adpater that coverts values returned by API interface into CompletionStage + */ + private static final class ValueAdapter implements CallAdapter> { + + private final Type responseType; + private final boolean includeResponse; + + ValueAdapter(Type responseType, boolean includeResponse) { + this.responseType = responseType; + this.includeResponse = includeResponse; + } + + @Override + public Type responseType() { + return responseType; + } + + @Override + public F.Promise adapt(final Call call) { + final F.RedeemablePromise promise = F.RedeemablePromise.empty(); + + call.enqueue(new Callback() { + + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + if (includeResponse) { + promise.success((R) response); + } else { + promise.success(response.body()); + } + } else { + promise.failure(new HttpException(response)); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + promise.failure(t); + } + + }); + + return promise; + } + } + +} diff --git a/mustacheTemplates/libraries/retrofit2/play24/Play24CallFactory.mustache b/mustacheTemplates/libraries/retrofit2/play24/Play24CallFactory.mustache new file mode 100644 index 0000000000..5572ac4761 --- /dev/null +++ b/mustacheTemplates/libraries/retrofit2/play24/Play24CallFactory.mustache @@ -0,0 +1,219 @@ +package {{invokerPackage}}; + +import okhttp3.*; +import okio.Buffer; +import okio.BufferedSource; +import play.libs.F; +import play.libs.ws.WSClient; +import play.libs.ws.WSRequest; +import play.libs.ws.WSResponse; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * Creates {@link Call} instances that invoke underlying {@link WSClient} + */ +public class Play24CallFactory implements okhttp3.Call.Factory { + + /** PlayWS http client */ + private final WSClient wsClient; + + /** Extra headers to add to request */ + private Map extraHeaders = new HashMap<>(); + + /** Extra query parameters to add to request */ + private List extraQueryParams = new ArrayList<>(); + + public Play24CallFactory(WSClient wsClient) { + this.wsClient = wsClient; + } + + public Play24CallFactory(WSClient wsClient, Map extraHeaders, + List extraQueryParams) { + this.wsClient = wsClient; + + this.extraHeaders.putAll(extraHeaders); + this.extraQueryParams.addAll(extraQueryParams); + } + + @Override + public Call newCall(Request request) { + // add extra headers + Request.Builder rb = request.newBuilder(); + for (Map.Entry header : this.extraHeaders.entrySet()) { + rb.addHeader(header.getKey(), header.getValue()); + } + + // add extra query params + if (!this.extraQueryParams.isEmpty()) { + String newQuery = request.url().uri().getQuery(); + for (Pair queryParam : this.extraQueryParams) { + String param = String.format("%s=%s", queryParam.getName(), queryParam.getValue()); + if (newQuery == null) { + newQuery = param; + } else { + newQuery += "&" + param; + } + } + + URI newUri; + try { + newUri = new URI(request.url().uri().getScheme(), request.url().uri().getAuthority(), + request.url().uri().getPath(), newQuery, request.url().uri().getFragment()); + rb.url(newUri.toURL()); + } catch (MalformedURLException | URISyntaxException e) { + throw new RuntimeException("Error while updating an url", e); + } + } + + return new PlayWSCall(wsClient, rb.build()); + } + + /** + * Call implementation that delegates to Play WS Client + */ + static class PlayWSCall implements Call { + + private final WSClient wsClient; + private WSRequest wsRequest; + + private final Request request; + + public PlayWSCall(WSClient wsClient, Request request) { + this.wsClient = wsClient; + this.request = request; + } + + @Override + public Request request() { + return request; + } + + @Override + public void enqueue(final okhttp3.Callback responseCallback) { + final Call call = this; + final F.Promise promise = executeAsync(); + + promise.onRedeem(new F.Callback() { + + @Override + public void invoke(WSResponse wsResponse) throws Throwable { + responseCallback.onResponse(call, PlayWSCall.this.toWSResponse(wsResponse)); + } + + }); + + promise.onFailure(new F.Callback() { + + @Override + public void invoke(Throwable throwable) throws Throwable { + if (throwable instanceof IOException) { + responseCallback.onFailure(call, (IOException) throwable); + } else { + responseCallback.onFailure(call, new IOException(throwable)); + } + } + + }); + + } + + F.Promise executeAsync() { + try { + wsRequest = wsClient.url(request.url().uri().toString()); + addHeaders(wsRequest); + if (request.body() != null) { + addBody(wsRequest); + } + + return wsRequest.execute(request.method()); + } catch (Exception e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + private void addHeaders(WSRequest wsRequest) { + for(Map.Entry> entry : request.headers().toMultimap().entrySet()) { + List values = entry.getValue(); + for (String value : values) { + wsRequest.setHeader(entry.getKey(), value); + } + } + } + + private void addBody(WSRequest wsRequest) throws IOException { + Buffer buffer = new Buffer(); + request.body().writeTo(buffer); + wsRequest.setBody(buffer.inputStream()); + wsRequest.setContentType(request.body().contentType().toString()); + } + + private Response toWSResponse(final WSResponse r) { + final Response.Builder builder = new Response.Builder(); + builder.request(request) + .code(r.getStatus()) + .body(new ResponseBody() { + + @Override + public MediaType contentType() { + return Optional.ofNullable(r.getHeader("Content-Type")) + .map(MediaType::parse) + .orElse(null); + } + + @Override + public long contentLength() { + return r.asByteArray().length; + } + + @Override + public BufferedSource source() { + return new Buffer().write(r.asByteArray()); + } + + }); + + for (Map.Entry> entry : r.getAllHeaders().entrySet()) { + for (String value : entry.getValue()) { + builder.addHeader(entry.getKey(), value); + } + } + + builder.protocol(Protocol.HTTP_1_1); + return builder.build(); + } + + @Override + public Response execute() throws IOException { + throw new UnsupportedOperationException("Not supported"); + } + + @Override + public void cancel() { + throw new UnsupportedOperationException("Not supported"); + } + + @Override + public PlayWSCall clone() { + throw new UnsupportedOperationException("Not supported"); + } + + @Override + public boolean isExecuted() { + return false; + } + + @Override + public boolean isCanceled() { + return false; + } + } +} diff --git a/mustacheTemplates/libraries/retrofit2/play24/api.mustache b/mustacheTemplates/libraries/retrofit2/play24/api.mustache new file mode 100644 index 0000000000..2f968ee56c --- /dev/null +++ b/mustacheTemplates/libraries/retrofit2/play24/api.mustache @@ -0,0 +1,59 @@ +package {{package}}; + +import {{invokerPackage}}.CollectionFormats.*; + +{{#useRxJava}}import rx.Observable;{{/useRxJava}} +{{#useRxJava2}}import io.reactivex.Observable;{{/useRxJava2}} +{{#doNotUseRx}}import retrofit2.Call;{{/doNotUseRx}} +import retrofit2.http.*; + +import okhttp3.RequestBody; + +{{#imports}}import {{import}}; +{{/imports}} + +{{^fullJavaUtil}} +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +{{/fullJavaUtil}} + +import play.libs.F; +import retrofit2.Response; + +{{#operations}} +public interface {{classname}} { + {{#operation}} + {{#contents}} + /** + * {{summary}} + * {{notes}} +{{#parameters}} + * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}} +{{/parameters}} + * @return Call<{{#returnType}}{{returnType}}{{/returnType}}{{^returnType}}Object{{/returnType}}> + */ + {{#formParams}} + {{#@first}} + {{#isMultipart}}@retrofit2.http.Multipart{{/isMultipart}}{{^isMultipart}}@retrofit2.http.FormUrlEncoded{{/isMultipart}} + {{/@first}} + {{/formParams}} + {{^formParams}} + {{#prioritizedContentTypes}} + {{#@first}} + @Headers({ + "Content-Type:{{mediaType}}" + }) + {{/@first}} + {{/prioritizedContentTypes}} + {{/formParams}} + @{{httpMethod}}("{{{path}}}") + F.Promise> {{operationId}}({{^parameters}});{{/parameters}} + {{#parameters}}{{>libraries/retrofit2/queryParams}}{{>libraries/retrofit2/pathParams}}{{>libraries/retrofit2/headerParams}}{{>libraries/retrofit2/bodyParams}}{{>libraries/retrofit2/formParams}}{{>libraries/retrofit2/cookieParams}}{{#hasMore}}, {{/hasMore}}{{^hasMore}} + );{{/hasMore}}{{/parameters}} + + {{/contents}} + {{/operation}} +} +{{/operations}} diff --git a/mustacheTemplates/libraries/retrofit2/play25/ApiClient.mustache b/mustacheTemplates/libraries/retrofit2/play25/ApiClient.mustache new file mode 100644 index 0000000000..120dcf5cc6 --- /dev/null +++ b/mustacheTemplates/libraries/retrofit2/play25/ApiClient.mustache @@ -0,0 +1,136 @@ +package {{invokerPackage}}; + +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.*; + +import retrofit2.Retrofit; +import retrofit2.converter.scalars.ScalarsConverterFactory; +import retrofit2.converter.jackson.JacksonConverterFactory; + +import play.libs.Json; +import play.libs.ws.WSClient; + +import {{invokerPackage}}.Play25CallAdapterFactory; +import {{invokerPackage}}.Play25CallFactory; + +import okhttp3.Interceptor; +import {{invokerPackage}}.auth.ApiKeyAuth; +import {{invokerPackage}}.auth.Authentication; + +/** + * API client + */ +public class ApiClient { + + /** Underlying HTTP-client */ + private WSClient wsClient; + + /** Supported auths */ + private Map authentications; + + /** API base path */ + private String basePath = "{{{basePath}}}"; + + public ApiClient(WSClient wsClient) { + this(); + this.wsClient = wsClient; + } + + public ApiClient() { + // Setup authentications (key: authentication name, value: authentication). + authentications = new HashMap<>();{{#authMethods}}{{#is this 'basic'}} + // authentications.put("{{name}}", new HttpBasicAuth());{{/is}}{{#is this 'api-key'}} + authentications.put("{{name}}", new ApiKeyAuth({{#is this 'key-in-header'}}"header"{{/is}}{{#isNot this 'key-in-header'}}"query"{{/isNot}}, "{{keyParamName}}"));{{/is}}{{#is this 'oauth'}} + // authentications.put("{{name}}", new OAuth());{{/is}}{{/authMethods}} + // Prevent the authentications from being modified. + authentications = Collections.unmodifiableMap(authentications); + + } + + /** + * Creates a retrofit2 client for given API interface + */ + public S createService(Class serviceClass) { + if(!basePath.endsWith("/")) { + basePath = basePath + "/"; + } + + Map extraHeaders = new HashMap<>(); + List extraQueryParams = new ArrayList<>(); + + for (String authName : authentications.keySet()) { + Authentication auth = authentications.get(authName); + if (auth == null) throw new RuntimeException("Authentication undefined: " + authName); + + auth.applyToParams(extraQueryParams, extraHeaders); + } + + return new Retrofit.Builder() + .baseUrl(basePath) + .addConverterFactory(ScalarsConverterFactory.create()) + .addConverterFactory(JacksonConverterFactory.create(Json.mapper())) + .callFactory(new Play25CallFactory(wsClient, extraHeaders, extraQueryParams)) + .addCallAdapterFactory(new Play25CallAdapterFactory()) + .build() + .create(serviceClass); + } + + /** + * Helper method to set API base path + */ + public ApiClient setBasePath(String basePath) { + this.basePath = basePath; + return this; + } + + /** + * Get authentications (key: authentication name, value: authentication). + */ + public Map getAuthentications() { + return authentications; + } + + /** + * Get authentication for the given name. + * + * @param authName The authentication name + * @return The authentication, null if not found + */ + public Authentication getAuthentication(String authName) { + return authentications.get(authName); + } + + /** + * Helper method to set API key value for the first API key authentication. + */ + public ApiClient setApiKey(String apiKey) { + for (Authentication auth : authentications.values()) { + if (auth instanceof ApiKeyAuth) { + ((ApiKeyAuth) auth).setApiKey(apiKey); + return this; + } + } + + throw new RuntimeException("No API key authentication configured!"); + } + + /** + * Helper method to set API key prefix for the first API key authentication. + */ + public ApiClient setApiKeyPrefix(String apiKeyPrefix) { + for (Authentication auth : authentications.values()) { + if (auth instanceof ApiKeyAuth) { + ((ApiKeyAuth) auth).setApiKeyPrefix(apiKeyPrefix); + return this; + } + } + + throw new RuntimeException("No API key authentication configured!"); + } + + +} + + diff --git a/mustacheTemplates/libraries/retrofit2/play25/Play25CallAdapterFactory.mustache b/mustacheTemplates/libraries/retrofit2/play25/Play25CallAdapterFactory.mustache new file mode 100644 index 0000000000..e72ee4aa7a --- /dev/null +++ b/mustacheTemplates/libraries/retrofit2/play25/Play25CallAdapterFactory.mustache @@ -0,0 +1,117 @@ +package {{invokerPackage}}; + +import java.util.concurrent.CompletionStage; +import retrofit2.*; + +import java.lang.annotation.Annotation; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.WildcardType; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.Function; + +/** + * Creates {@link CallAdapter} instances that convert {@link Call} into {@link java.util.concurrent.CompletionStage} + */ +public class Play25CallAdapterFactory extends CallAdapter.Factory { + + private Function exceptionConverter = Function.identity(); + + public Play25CallAdapterFactory() { + } + + public Play25CallAdapterFactory( + Function exceptionConverter) { + this.exceptionConverter = exceptionConverter; + } + + @Override + public CallAdapter get(Type returnType, Annotation[] annotations, Retrofit retrofit) { + if (!(returnType instanceof ParameterizedType)) { + return null; + } + + ParameterizedType type = (ParameterizedType) returnType; + if (type.getRawType() != CompletionStage.class) { + return null; + } + + return createAdapter((ParameterizedType) returnType); + } + + private CallAdapter> createAdapter(ParameterizedType returnType) { + // Get CompletionStage type argument + Type[] types = returnType.getActualTypeArguments(); + if (types.length != 1) { + throw new IllegalStateException("Must be exactly one type parameter"); + } + + Type resultType = types[0]; + Class rawTypeParam = getRawType(resultType); + + boolean includeResponse = false; + if (rawTypeParam == Response.class) { + if (!(resultType instanceof ParameterizedType)) { + throw new IllegalStateException("Response must be parameterized" + + " as Response"); + } + resultType = ((ParameterizedType) resultType).getActualTypeArguments()[0]; + includeResponse = true; + } + + return new ValueAdapter(resultType, includeResponse, exceptionConverter); + } + + /** + * Adpater that coverts values returned by API interface into CompletionStage + */ + private static final class ValueAdapter implements CallAdapter> { + + private final Type responseType; + private final boolean includeResponse; + private Function exceptionConverter; + + ValueAdapter(Type responseType, boolean includeResponse, + Function exceptionConverter) { + this.responseType = responseType; + this.includeResponse = includeResponse; + this.exceptionConverter = exceptionConverter; + } + + @Override + public Type responseType() { + return responseType; + } + + @Override + public CompletionStage adapt(final Call call) { + final CompletableFuture promise = new CompletableFuture(); + + call.enqueue(new Callback() { + + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + if (includeResponse) { + promise.complete((R) response); + } else { + promise.complete(response.body()); + } + } else { + promise.completeExceptionally(exceptionConverter.apply(new HttpException(response))); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + promise.completeExceptionally(t); + } + + }); + + return promise; + } + } +} + diff --git a/mustacheTemplates/libraries/retrofit2/play25/Play25CallFactory.mustache b/mustacheTemplates/libraries/retrofit2/play25/Play25CallFactory.mustache new file mode 100644 index 0000000000..93df7a2718 --- /dev/null +++ b/mustacheTemplates/libraries/retrofit2/play25/Play25CallFactory.mustache @@ -0,0 +1,228 @@ +package {{invokerPackage}}; + +import okhttp3.*; +import okio.Buffer; +import okio.BufferedSource; +import play.libs.ws.WSClient; +import play.libs.ws.WSRequest; +import play.libs.ws.WSResponse; +import play.libs.ws.WSRequestFilter; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletionStage; + +/** + * Creates {@link Call} instances that invoke underlying {@link WSClient} + */ +public class Play25CallFactory implements okhttp3.Call.Factory { + + /** PlayWS http client */ + private final WSClient wsClient; + + /** Extra headers to add to request */ + private Map extraHeaders = new HashMap<>(); + + /** Extra query parameters to add to request */ + private List extraQueryParams = new ArrayList<>(); + + /** Filters (interceptors) */ + private List filters = new ArrayList<>(); + + public Play25CallFactory(WSClient wsClient) { + this.wsClient = wsClient; + } + + public Play25CallFactory(WSClient wsClient, List filters) { + this.wsClient = wsClient; + this.filters.addAll(filters); + } + + public Play25CallFactory(WSClient wsClient, Map extraHeaders, + List extraQueryParams) { + this.wsClient = wsClient; + + this.extraHeaders.putAll(extraHeaders); + this.extraQueryParams.addAll(extraQueryParams); + } + + @Override + public Call newCall(Request request) { + // add extra headers + Request.Builder rb = request.newBuilder(); + for (Map.Entry header : this.extraHeaders.entrySet()) { + rb.addHeader(header.getKey(), header.getValue()); + } + + // add extra query params + if (!this.extraQueryParams.isEmpty()) { + String newQuery = request.url().uri().getQuery(); + for (Pair queryParam : this.extraQueryParams) { + String param = String.format("%s=%s", queryParam.getName(), queryParam.getValue()); + if (newQuery == null) { + newQuery = param; + } else { + newQuery += "&" + param; + } + } + + URI newUri; + try { + newUri = new URI(request.url().uri().getScheme(), request.url().uri().getAuthority(), + request.url().uri().getPath(), newQuery, request.url().uri().getFragment()); + rb.url(newUri.toURL()); + } catch (MalformedURLException | URISyntaxException e) { + throw new RuntimeException("Error while updating an url", e); + } + } + + return new PlayWSCall(wsClient, this.filters, rb.build()); + } + + /** + * Call implementation that delegates to Play WS Client + */ + static class PlayWSCall implements Call { + + private final WSClient wsClient; + private WSRequest wsRequest; + private List filters; + + private final Request request; + + public PlayWSCall(WSClient wsClient, List filters, Request request) { + this.wsClient = wsClient; + this.request = request; + this.filters = filters; + } + + @Override + public Request request() { + return request; + } + + @Override + public void enqueue(final okhttp3.Callback responseCallback) { + final Call call = this; + final CompletionStage promise = executeAsync(); + + promise.whenCompleteAsync((v, t) -> { + if (t != null) { + if (t instanceof IOException) { + responseCallback.onFailure(call, (IOException) t); + } else { + responseCallback.onFailure(call, new IOException(t)); + } + } else { + try { + responseCallback.onResponse(call, PlayWSCall.this.toWSResponse(v)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + }, play.libs.concurrent.HttpExecution.defaultContext()); + } + + CompletionStage executeAsync() { + try { + wsRequest = wsClient.url(request.url().uri().toString()); + addHeaders(wsRequest); + if (request.body() != null) { + addBody(wsRequest); + } + filters.stream().forEach(f -> wsRequest.withRequestFilter(f)); + + return wsRequest.execute(request.method()); + } catch (Exception e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + private void addHeaders(WSRequest wsRequest) { + for(Map.Entry> entry : request.headers().toMultimap().entrySet()) { + List values = entry.getValue(); + for (String value : values) { + wsRequest.setHeader(entry.getKey(), value); + } + } + } + + private void addBody(WSRequest wsRequest) throws IOException { + Buffer buffer = new Buffer(); + request.body().writeTo(buffer); + wsRequest.setBody(buffer.inputStream()); + + MediaType mediaType = request.body().contentType(); + if (mediaType != null) { + wsRequest.setContentType(mediaType.toString()); + } + } + + private Response toWSResponse(final WSResponse r) { + final Response.Builder builder = new Response.Builder(); + builder.request(request) + .code(r.getStatus()) + .body(new ResponseBody() { + + @Override + public MediaType contentType() { + return Optional.ofNullable(r.getHeader("Content-Type")) + .map(MediaType::parse) + .orElse(null); + } + + @Override + public long contentLength() { + return r.asByteArray().length; + } + + @Override + public BufferedSource source() { + return new Buffer().write(r.asByteArray()); + } + + }); + + for (Map.Entry> entry : r.getAllHeaders().entrySet()) { + for (String value : entry.getValue()) { + builder.addHeader(entry.getKey(), value); + } + } + + builder.protocol(Protocol.HTTP_1_1); + return builder.build(); + } + + @Override + public Response execute() throws IOException { + throw new UnsupportedOperationException("Not supported"); + } + + @Override + public void cancel() { + throw new UnsupportedOperationException("Not supported"); + } + + @Override + public PlayWSCall clone() { + throw new UnsupportedOperationException("Not supported"); + } + + @Override + public boolean isExecuted() { + return false; + } + + @Override + public boolean isCanceled() { + return false; + } + } +} diff --git a/mustacheTemplates/libraries/retrofit2/play25/api.mustache b/mustacheTemplates/libraries/retrofit2/play25/api.mustache new file mode 100644 index 0000000000..0e61b74cc6 --- /dev/null +++ b/mustacheTemplates/libraries/retrofit2/play25/api.mustache @@ -0,0 +1,59 @@ +package {{package}}; + +import {{invokerPackage}}.CollectionFormats.*; + +{{#useRxJava}}import rx.Observable;{{/useRxJava}} +{{#useRxJava2}}import io.reactivex.Observable;{{/useRxJava2}} +{{#doNotUseRx}}import retrofit2.Call;{{/doNotUseRx}} +import retrofit2.http.*; + +import okhttp3.RequestBody; + +{{#imports}}import {{import}}; +{{/imports}} + +{{^fullJavaUtil}} +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +{{/fullJavaUtil}} + +import java.util.concurrent.*; +import retrofit2.Response; + +{{#operations}} +public interface {{classname}} { + {{#operation}} + {{#contents}} + /** + * {{summary}} + * {{notes}} +{{#parameters}} + * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}} +{{/parameters}} + * @return Call<{{#returnType}}{{returnType}}{{/returnType}}{{^returnType}}Void{{/returnType}}> + */ + {{#formParams}} + {{#@first}} + {{#isMultipart}}@retrofit2.http.Multipart{{/isMultipart}}{{^isMultipart}}@retrofit2.http.FormUrlEncoded{{/isMultipart}} + {{/@first}} + {{/formParams}} + {{^formParams}} + {{#prioritizedContentTypes}} + {{#@first}} + @Headers({ + "Content-Type:{{{mediaType}}}" + }) + {{/@first}} + {{/prioritizedContentTypes}} + {{/formParams}} + @{{httpMethod}}("{{{path}}}") + CompletionStage> {{operationId}}({{^parameters}});{{/parameters}} + {{#parameters}}{{>libraries/retrofit2/queryParams}}{{>libraries/retrofit2/pathParams}}{{>libraries/retrofit2/headerParams}}{{>libraries/retrofit2/bodyParams}}{{>libraries/retrofit2/formParams}}{{>libraries/retrofit2/cookieParams}}{{#hasMore}}, {{/hasMore}}{{^hasMore}} + );{{/hasMore}}{{/parameters}} + + {{/contents}} + {{/operation}} +} +{{/operations}} diff --git a/mustacheTemplates/libraries/retrofit2/pom.mustache b/mustacheTemplates/libraries/retrofit2/pom.mustache new file mode 100644 index 0000000000..34c74a6107 --- /dev/null +++ b/mustacheTemplates/libraries/retrofit2/pom.mustache @@ -0,0 +1,350 @@ + + 4.0.0 + {{groupId}} + {{artifactId}} + jar + {{artifactId}} + {{artifactVersion}} + {{artifactUrl}} + {{artifactDescription}} + + {{scmConnection}} + {{scmDeveloperConnection}} + {{scmUrl}} + + + 2.2.0 + + + + + {{licenseName}} + {{licenseUrl}} + repo + + + + + + {{developerName}} + {{developerEmail}} + {{developerOrganization}} + {{developerOrganizationUrl}} + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.12 + + + + loggerPath + conf/log4j.properties + + + -Xms512m -Xmx1500m + methods + pertest + + + + maven-dependency-plugin + + + package + + copy-dependencies + + + ${project.build.directory}/lib + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.2 + + + + jar + test-jar + + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 1.10 + + + add_sources + generate-sources + + add-source + + + + src/main/java + + + + + add_test_sources + generate-test-sources + + add-test-source + + + + src/test/java + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + attach-sources + + jar-no-fork + + + + + + + + + + sign-artifacts + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + sign-artifacts + verify + + sign + + + + + + + + + + + {{#useOas2}} + + io.swagger + swagger-annotations + ${swagger-core-version} + + {{/useOas2}} + {{^useOas2}} + + io.swagger.core.v3 + swagger-annotations + ${swagger-core-version} + + {{/useOas2}} + + com.squareup.retrofit2 + converter-gson + ${retrofit-version} + + + com.squareup.retrofit2 + retrofit + ${retrofit-version} + + + com.squareup.retrofit2 + converter-scalars + ${retrofit-version} + + + org.apache.oltu.oauth2 + org.apache.oltu.oauth2.client + ${oltu-version} + + + io.gsonfire + gson-fire + ${gson-fire-version} + + {{#joda}} + + joda-time + joda-time + ${jodatime-version} + + {{/joda}} + {{#threetenbp}} + + org.threeten + threetenbp + ${threetenbp-version} + + {{/threetenbp}} + {{#useRxJava}} + + io.reactivex + rxjava + ${rxjava-version} + + + com.squareup.retrofit2 + adapter-rxjava + ${retrofit-version} + + {{/useRxJava}} + {{#useRxJava2}} + + io.reactivex.rxjava2 + rxjava + ${rxjava-version} + + + com.squareup.retrofit2 + adapter-rxjava2 + ${retrofit-version} + + {{/useRxJava2}} + + {{#usePlayWS}} + + + com.squareup.retrofit2 + converter-jackson + ${retrofit-version} + + + com.fasterxml.jackson.core + jackson-core + ${jackson-version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson-version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson-version} + + + com.fasterxml.jackson.datatype + jackson-datatype-{{^java8}}joda{{/java8}}{{#java8}}jsr310{{/java8}} + ${jackson-version} + + {{#withXml}} + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + ${jackson-version} + + + {{/withXml}} + + com.typesafe.play + play-java-ws_2.11 + ${play-version} + + {{/usePlayWS}} + + {{#parcelableModel}} + + + com.google.android + android + 4.1.1.4 + provided + + {{/parcelableModel}} + + + + junit + junit + ${junit-version} + test + + + + UTF-8 + {{#java8}}1.8{{/java8}}{{^java8}}1.7{{/java8}} + ${java.version} + ${java.version} + 1.8.0 + {{#useOas2}} + 1.5.15 + {{/useOas2}} + {{^useOas2}} + 2.0.0 + {{/useOas2}} + {{#usePlayWS}} + {{#play24}} + 2.6.6 + 2.4.11 + {{/play24}} + {{#play25}} + 2.7.8 + 2.5.15 + {{/play25}} + {{/usePlayWS}} + 2.3.0 + {{#useRxJava}} + 1.3.0 + {{/useRxJava}} + {{#useRxJava2}} + 2.1.1 + {{/useRxJava2}} + {{#joda}} + 2.9.9 + {{/joda}} + {{#threetenbp}} + 1.3.5 + {{/threetenbp}} + 1.0.2 + 4.12 + + diff --git a/mustacheTemplates/libraries/retrofit2/queryParams.mustache b/mustacheTemplates/libraries/retrofit2/queryParams.mustache new file mode 100644 index 0000000000..f624114ff4 --- /dev/null +++ b/mustacheTemplates/libraries/retrofit2/queryParams.mustache @@ -0,0 +1 @@ +{{#is this 'query-param'}}@retrofit2.http.Query("{{baseName}}") {{{dataType}}} {{paramName}}{{/is}} \ No newline at end of file diff --git a/mustacheTemplates/licenseInfo.mustache b/mustacheTemplates/licenseInfo.mustache new file mode 100644 index 0000000000..94c36dda3a --- /dev/null +++ b/mustacheTemplates/licenseInfo.mustache @@ -0,0 +1,11 @@ +/* + * {{{appName}}} + * {{{appDescription}}} + * + * {{#version}}OpenAPI spec version: {{{version}}}{{/version}} + * {{#infoEmail}}Contact: {{{infoEmail}}}{{/infoEmail}} + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ diff --git a/mustacheTemplates/manifest.mustache b/mustacheTemplates/manifest.mustache new file mode 100644 index 0000000000..f44bd07d0a --- /dev/null +++ b/mustacheTemplates/manifest.mustache @@ -0,0 +1,3 @@ + + + diff --git a/mustacheTemplates/model.mustache b/mustacheTemplates/model.mustache new file mode 100644 index 0000000000..6c006f921b --- /dev/null +++ b/mustacheTemplates/model.mustache @@ -0,0 +1,45 @@ +{{>licenseInfo}} + +package {{package}}; + +{{^x-is-composed-model}} +{{^supportJava6}} +import java.util.Objects; +import java.util.Arrays; +{{/supportJava6}} +{{#supportJava6}} +import org.apache.commons.lang3.ObjectUtils; +{{/supportJava6}} +{{#imports}} +import {{import}}; +{{/imports}} +{{#serializableModel}} +import java.io.Serializable; +{{/serializableModel}} +{{#jackson}} +{{#withXml}} +import com.fasterxml.jackson.dataformat.xml.annotation.*; +{{/withXml}} +{{/jackson}} +{{#withXml}} +import javax.xml.bind.annotation.*; +{{/withXml}} +{{#parcelableModel}} +import android.os.Parcelable; +import android.os.Parcel; +{{/parcelableModel}} +{{#useBeanValidation}} +import javax.validation.constraints.*; +import javax.validation.Valid; +{{/useBeanValidation}} +{{/x-is-composed-model}} +{{#models}} +{{#model}} +{{#isComposedModel}} +{{>interface}} +{{/isComposedModel}} +{{^isComposedModel}} +{{#is this 'enum'}}{{>modelEnum}}{{/is}}{{#isNot this 'enum'}}{{>pojo}}{{/isNot}} +{{/isComposedModel}} +{{/model}} +{{/models}} diff --git a/mustacheTemplates/modelEnum.mustache b/mustacheTemplates/modelEnum.mustache new file mode 100644 index 0000000000..f151c2cac8 --- /dev/null +++ b/mustacheTemplates/modelEnum.mustache @@ -0,0 +1,70 @@ +{{#jackson}} +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +{{/jackson}} +{{#gson}} +import java.io.IOException; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +{{/gson}} + +/** + * {{^description}}Gets or Sets {{{name}}}{{/description}}{{#description}}{{description}}{{/description}} + */ +{{#gson}} +@JsonAdapter({{#datatypeWithEnum}}{{{.}}}{{/datatypeWithEnum}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.Adapter.class) +{{/gson}} +public enum {{#datatypeWithEnum}}{{{.}}}{{/datatypeWithEnum}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} { + {{#allowableValues}} + {{#enumVars}} + {{{name}}}({{#value}}{{{value}}}{{/value}}{{^value}}null{{/value}}){{^@last}},{{/@last}}{{#@last}};{{/@last}} + {{/enumVars}} + {{/allowableValues}} + + private {{{dataType}}} value; + + {{#datatypeWithEnum}}{{{.}}}{{/datatypeWithEnum}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}({{{dataType}}} value) { + this.value = value; + } + + {{#jackson}} + @JsonValue + {{/jackson}} + public {{{dataType}}} getValue() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + +{{#jackson}} + @JsonCreator +{{/jackson}} + public static {{#datatypeWithEnum}}{{{.}}}{{/datatypeWithEnum}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} fromValue(String text) { + for ({{#datatypeWithEnum}}{{{.}}}{{/datatypeWithEnum}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} b : {{#datatypeWithEnum}}{{{.}}}{{/datatypeWithEnum}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.values()) { + if (String.valueOf(b.value).equals(text)) { + return b; + } + } + return null; + } +{{#if gson}} + + public static class Adapter extends TypeAdapter<{{#datatypeWithEnum}}{{{.}}}{{/datatypeWithEnum}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}> { + @Override + public void write(final JsonWriter jsonWriter, final {{#datatypeWithEnum}}{{{.}}}{{/datatypeWithEnum}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} enumeration) throws IOException { + jsonWriter.value(enumeration.getValue()); + } + + @Override + public {{#datatypeWithEnum}}{{{.}}}{{/datatypeWithEnum}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} read(final JsonReader jsonReader) throws IOException { + {{{dataType}}} value = jsonReader.{{#is this 'integer'}}nextInt(){{/is}}{{#isNot this 'integer'}}next{{{dataType}}}(){{/isNot}}; + return {{#datatypeWithEnum}}{{{.}}}{{/datatypeWithEnum}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.fromValue(String.valueOf(value)); + } + } +{{/if}} +} diff --git a/mustacheTemplates/modelInnerEnum.mustache b/mustacheTemplates/modelInnerEnum.mustache new file mode 100644 index 0000000000..bf6965b113 --- /dev/null +++ b/mustacheTemplates/modelInnerEnum.mustache @@ -0,0 +1,52 @@ + /** + * {{^description}}Gets or Sets {{{name}}}{{/description}}{{#description}}{{description}}{{/description}} + */{{#gson}} + @JsonAdapter({{#datatypeWithEnum}}{{{.}}}{{/datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}.Adapter.class){{/gson}} + public enum {{#datatypeWithEnum}}{{{.}}}{{/datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}} { + {{#allowableValues}} + {{#enumVars}} + {{{name}}}({{#value}}{{{value}}}{{/value}}{{^value}}null{{/value}}){{^@last}},{{/@last}}{{#@last}};{{/@last}} + {{/enumVars}} + {{/allowableValues}} + + private {{{datatype}}} value; + + {{#datatypeWithEnum}}{{{.}}}{{/datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}({{{datatype}}} value) { + this.value = value; + } + {{#jackson}} + @JsonValue + {{/jackson}} + public {{{datatype}}} getValue() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + {{#jackson}} + @JsonCreator + {{/jackson}} + public static {{#datatypeWithEnum}}{{{.}}}{{/datatypeWithEnum}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} fromValue(String text) { + for ({{#datatypeWithEnum}}{{{.}}}{{/datatypeWithEnum}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} b : {{#datatypeWithEnum}}{{{.}}}{{/datatypeWithEnum}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.values()) { + if (String.valueOf(b.value).equals(text)) { + return b; + } + } + return null; + } + {{#gson}} + public static class Adapter extends TypeAdapter<{{#datatypeWithEnum}}{{{.}}}{{/datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}> { + @Override + public void write(final JsonWriter jsonWriter, final {{#datatypeWithEnum}}{{{.}}}{{/datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}} enumeration) throws IOException { + jsonWriter.value(enumeration.getValue()); + } + + @Override + public {{#datatypeWithEnum}}{{{.}}}{{/datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}} read(final JsonReader jsonReader) throws IOException { + {{{datatype}}} value = jsonReader.{{#is ../this 'integer'}}nextInt(){{/is}}{{#isNot ../this 'integer'}}next{{{datatype}}}(){{/isNot}}; + return {{#datatypeWithEnum}}{{{.}}}{{/datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}.fromValue(String.valueOf(value)); + } + }{{/gson}} + } \ No newline at end of file diff --git a/mustacheTemplates/model_doc.mustache b/mustacheTemplates/model_doc.mustache new file mode 100644 index 0000000000..89317d7d4a --- /dev/null +++ b/mustacheTemplates/model_doc.mustache @@ -0,0 +1,3 @@ +{{#models}}{{#model}} +{{#is this 'enum'}}{{>enum_outer_doc}}{{/is}}{{#isNot this 'enum'}}{{>pojo_doc}}{{/isNot}} +{{/model}}{{/models}} diff --git a/mustacheTemplates/pojo.mustache b/mustacheTemplates/pojo.mustache new file mode 100644 index 0000000000..7a557ef7a5 --- /dev/null +++ b/mustacheTemplates/pojo.mustache @@ -0,0 +1,290 @@ +/** + * {{#description}}{{.}}{{/description}}{{^description}}{{classname}}{{/description}} + */ +{{#description}}{{#useOas2}}@ApiModel{{/useOas2}}{{^useOas2}}@Schema{{/useOas2}}(description = "{{{description}}}"){{/description}} +{{>generatedAnnotation}}{{#discriminator}}{{>typeInfoAnnotation}}{{/discriminator}}{{>xmlAnnotation}} + +public class {{classname}} {{#parent}}extends {{{parent}}} {{/parent}}{{#parcelableModel}}implements Parcelable {{#serializableModel}}, Serializable {{/serializableModel}}{{#interfaceModels}}{{#@first}}, {{/@first}}{{classname}}{{^@last}}, {{/@last}}{{#@last}} {{/@last}}{{/interfaceModels}}{{/parcelableModel}}{{^parcelableModel}}{{#serializableModel}}implements Serializable{{#interfaceModels}}, {{classname}}{{^@last}}, {{/@last}}{{#@last}} {{/@last}}{{/interfaceModels}}{{/serializableModel}}{{^serializableModel}}{{#interfaceModels}}{{#@first}}implements {{/@first}}{{classname}}{{^@last}}, {{/@last}}{{#@last}} {{/@last}}{{/interfaceModels}}{{/serializableModel}}{{/parcelableModel}}{ +{{#serializableModel}} + private static final long serialVersionUID = 1L; +{{/serializableModel}} + {{#vars}} + {{#isEnum}} + {{^isContainer}} +{{>modelInnerEnum}} + {{/isContainer}} + {{/isEnum}} + {{#items.isEnum}} + {{#items}} + {{^isContainer}} +{{>modelInnerEnum}} + {{/isContainer}} + {{/items}} + {{/items.isEnum}} + {{#jackson}} + {{#vendorExtensions.x-is-discriminator-property}} + @JsonTypeId + {{/vendorExtensions.x-is-discriminator-property}} + {{^vendorExtensions.x-is-discriminator-property}} + @JsonProperty("{{baseName}}") + {{#withXml}} + {{^isContainer}} + @JacksonXmlProperty({{#isXmlAttribute}}isAttribute = true, {{/isXmlAttribute}}{{#xmlNamespace}}namespace="{{xmlNamespace}}", {{/xmlNamespace}}localName = "{{#xmlName}}{{xmlName}}{{/xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}") + {{/isContainer}} + {{#isContainer}} + {{#isXmlWrapped}} + // items.xmlName={{items.xmlName}} + @JacksonXmlElementWrapper(useWrapping = {{isXmlWrapped}}, {{#xmlNamespace}}namespace="{{xmlNamespace}}", {{/xmlNamespace}}localName = "{{#items.xmlName}}{{items.xmlName}}{{/items.xmlName}}{{^items.xmlName}}{{items.baseName}}{{/items.xmlName}}") + {{/isXmlWrapped}} + {{/isContainer}} + {{/withXml}} + {{/vendorExtensions.x-is-discriminator-property}} + {{/jackson}} + {{#withXml}} + {{#isXmlAttribute}} + @XmlAttribute(name = "{{#xmlName}}{{xmlName}}{{/xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}") + {{/isXmlAttribute}} + {{^isXmlAttribute}} + {{^isContainer}} + @XmlElement({{#xmlNamespace}}namespace="{{xmlNamespace}}", {{/xmlNamespace}}name = "{{#xmlName}}{{xmlName}}{{/xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}") + {{/isContainer}} + {{#isContainer}} + // Is a container wrapped={{isXmlWrapped}} + {{#items}} + // items.name={{name}} items.baseName={{baseName}} items.xmlName={{xmlName}} items.xmlNamespace={{xmlNamespace}} + // items.example={{example}} items.type={{datatype}} + @XmlElement({{#xmlNamespace}}namespace="{{xmlNamespace}}", {{/xmlNamespace}}name = "{{#xmlName}}{{xmlName}}{{/xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}") + {{/items}} + {{#isXmlWrapped}} + @XmlElementWrapper({{#xmlNamespace}}namespace="{{xmlNamespace}}", {{/xmlNamespace}}name = "{{#xmlName}}{{xmlName}}{{/xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}") + {{/isXmlWrapped}} + {{/isContainer}} + {{/isXmlAttribute}} + {{/withXml}} + {{#gson}} + @SerializedName("{{baseName}}") + {{/gson}} + {{#isContainer}} + private {{{datatypeWithEnum}}} {{name}}{{#required}} = {{{defaultValue}}}{{/required}}{{^required}} = null{{/required}}; + {{/isContainer}} + {{^isContainer}} + private {{{datatypeWithEnum}}} {{name}} = {{{defaultValue}}}; + {{/isContainer}} + + {{/vars}} + {{#parcelableModel}} + public {{classname}}() { + {{#parent}} + super(); + {{/parent}} + {{#gson}} + {{#discriminator}} + this.{{discriminator.propertyName}} = this.getClass().getSimpleName(); + {{/discriminator}} + {{/gson}} + } + {{/parcelableModel}} + {{^parcelableModel}} + {{#gson}} + {{#discriminator}} + public {{classname}}() { + this.{{discriminator.propertyName}} = this.getClass().getSimpleName(); + } + {{/discriminator}} + {{/gson}} + {{/parcelableModel}} + {{#vars}} + {{^isReadOnly}} + public {{classname}} {{name}}({{{datatypeWithEnum}}} {{name}}) { + this.{{name}} = {{name}}; + return this; + } + {{#isListContainer}} + + public {{classname}} add{{nameInCamelCase}}Item({{{items.datatypeWithEnum}}} {{name}}Item) { + {{^required}} + if (this.{{name}} == null) { + this.{{name}} = {{{defaultValue}}}; + } + {{/required}} + this.{{name}}.add({{name}}Item); + return this; + } + {{/isListContainer}} + {{#isMapContainer}} + + public {{classname}} put{{nameInCamelCase}}Item(String key, {{{items.datatypeWithEnum}}} {{name}}Item) { + {{^required}} + if (this.{{name}} == null) { + this.{{name}} = {{{defaultValue}}}; + } + {{/required}} + this.{{name}}.put(key, {{name}}Item); + return this; + } + {{/isMapContainer}} + + {{/isReadOnly}} + /** + {{#description}} + * {{description}} + {{/description}} + {{^description}} + * Get {{name}} + {{/description}} + {{#minimum}} + * minimum: {{minimum}} + {{/minimum}} + {{#maximum}} + * maximum: {{maximum}} + {{/maximum}} + * @return {{name}} + **/ +{{#useBeanValidation}}{{>beanValidation}}{{/useBeanValidation}} + {{#useOas2}} + @ApiModelProperty({{#example}}example = "{{{example}}}", {{/example}}{{#required}}required = {{required}}, {{/required}}value = "{{{description}}}") + {{/useOas2}} + {{^useOas2}} + @Schema({{#example}}example = "{{{example}}}", {{/example}}{{#required}}required = {{required}}, {{/required}}description = "{{{description}}}") + {{/useOas2}} +{{#vendorExtensions.extraAnnotation}} + {{{vendorExtensions.extraAnnotation}}} +{{/vendorExtensions.extraAnnotation}} + public {{{datatypeWithEnum}}} {{getter}}() { + return {{name}}; + } + {{^isReadOnly}} + + public void {{setter}}({{{datatypeWithEnum}}} {{name}}) { + this.{{name}} = {{name}}; + } + {{/isReadOnly}} + + {{/vars}} + +{{^supportJava6}} + @Override + public boolean equals(java.lang.Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + }{{#hasVars}} + {{classname}} {{classVarName}} = ({{classname}}) o; + return {{#vars}}{{#isByteArray}}Arrays{{/isByteArray}}{{#isBinary}}Objects{{/isBinary}}{{^isByteArray}}{{^isBinary}}Objects{{/isBinary}}{{/isByteArray}}.equals(this.{{name}}, {{classVarName}}.{{name}}){{#hasMore}} && + {{/hasMore}}{{/vars}}{{#parent}} && + super.equals(o){{/parent}};{{/hasVars}}{{^hasVars}} + return {{#parent}}super.equals(o){{/parent}}{{^parent}}true{{/parent}};{{/hasVars}} + } + + @Override + public int hashCode() { + return Objects.hash({{#vars}}{{^isByteArray}}{{^isBinary}}{{name}}{{/isBinary}}{{/isByteArray}}{{#isByteArray}}Arrays.hashCode({{name}}){{/isByteArray}}{{#isBinary}}Objects.hashCode({{name}}){{/isBinary}}{{#hasMore}}, {{/hasMore}}{{/vars}}{{#parent}}{{#hasVars}}, {{/hasVars}}super.hashCode(){{/parent}}); + } + +{{/supportJava6}} +{{#supportJava6}} + @Override + public boolean equals(java.lang.Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + }{{#hasVars}} + {{classname}} {{classVarName}} = ({{classname}}) o; + return {{#vars}}ObjectUtils.equals(this.{{name}}, {{classVarName}}.{{name}}){{#hasMore}} && + {{/hasMore}}{{/vars}}{{#parent}} && + super.equals(o){{/parent}};{{/hasVars}}{{^hasVars}} + return true;{{/hasVars}} + } + + @Override + public int hashCode() { + return ObjectUtils.hashCodeMulti({{#vars}}{{name}}{{#hasMore}}, {{/hasMore}}{{/vars}}{{#parent}}{{#hasVars}}, {{/hasVars}}super.hashCode(){{/parent}}); + } + +{{/supportJava6}} + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class {{classname}} {\n"); + {{#parent}}sb.append(" ").append(toIndentedString(super.toString())).append("\n");{{/parent}} + {{#vars}}sb.append(" {{name}}: ").append(toIndentedString({{name}})).append("\n"); + {{/vars}}sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(java.lang.Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + +{{#parcelableModel}} + + public void writeToParcel(Parcel out, int flags) { +{{#model}} +{{#isArrayModel}} + out.writeList(this); +{{/isArrayModel}} +{{^isArrayModel}} +{{#parent}} + super.writeToParcel(out, flags); +{{/parent}} +{{#vars}} + out.writeValue({{name}}); +{{/vars}} +{{/isArrayModel}} +{{/model}} + } + + {{classname}}(Parcel in) { +{{#isArrayModel}} + in.readTypedList(this, {{arrayModelType}}.CREATOR); +{{/isArrayModel}} +{{^isArrayModel}} +{{#parent}} + super(in); +{{/parent}} +{{#vars}} +{{#isPrimitiveType}} + {{name}} = ({{{datatypeWithEnum}}})in.readValue(null); +{{/isPrimitiveType}} +{{^isPrimitiveType}} + {{name}} = ({{{datatypeWithEnum}}})in.readValue({{complexType}}.class.getClassLoader()); +{{/isPrimitiveType}} +{{/vars}} +{{/isArrayModel}} + } + + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<{{classname}}> CREATOR = new Parcelable.Creator<{{classname}}>() { + public {{classname}} createFromParcel(Parcel in) { +{{#model}} +{{#isArrayModel}} + {{classname}} result = new {{classname}}(); + result.addAll(in.readArrayList({{arrayModelType}}.class.getClassLoader())); + return result; +{{/isArrayModel}} +{{^isArrayModel}} + return new {{classname}}(in); +{{/isArrayModel}} +{{/model}} + } + public {{classname}}[] newArray(int size) { + return new {{classname}}[size]; + } + }; +{{/parcelableModel}} +} diff --git a/mustacheTemplates/pojo_doc.mustache b/mustacheTemplates/pojo_doc.mustache new file mode 100644 index 0000000000..a54ac2fac4 --- /dev/null +++ b/mustacheTemplates/pojo_doc.mustache @@ -0,0 +1,15 @@ +# {{classname}} + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +{{#vars}}**{{name}}** | {{#is this 'enum'}}[**{{datatypeWithEnum}}**](#{{datatypeWithEnum}}){{/is}}{{#isNot this 'enum'}}{{#is this 'primitive-type'}}**{{datatype}}**{{/is}}{{#isNot this 'primitive-type'}}[**{{datatype}}**]({{complexType}}.md){{/isNot}}{{/isNot}} | {{description}} | {{^required}} [optional]{{/required}}{{#readOnly}} [readonly]{{/readOnly}} +{{/vars}} +{{#vars}}{{#is this 'enum'}} + + +## Enum: {{datatypeWithEnum}} +Name | Value +---- | -----{{#allowableValues}}{{#enumVars}} +{{name}} | {{value}}{{/enumVars}}{{/allowableValues}} +{{/is}}{{/vars}} diff --git a/mustacheTemplates/pom.mustache b/mustacheTemplates/pom.mustache new file mode 100644 index 0000000000..c37b52281d --- /dev/null +++ b/mustacheTemplates/pom.mustache @@ -0,0 +1,329 @@ + + 4.0.0 + {{groupId}} + {{artifactId}} + jar + {{artifactId}} + {{artifactVersion}} + {{artifactUrl}} + {{artifactDescription}} + + {{scmConnection}} + {{scmDeveloperConnection}} + {{scmUrl}} + + + 2.2.0 + + + + + {{licenseName}} + {{licenseUrl}} + repo + + + + + + {{developerName}} + {{developerEmail}} + {{developerOrganization}} + {{developerOrganizationUrl}} + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.12 + + + + loggerPath + conf/log4j.properties + + + -Xms512m -Xmx1500m + methods + pertest + + + + maven-dependency-plugin + + + package + + copy-dependencies + + + ${project.build.directory}/lib + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.2 + + + + jar + test-jar + + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 1.10 + + + add_sources + generate-sources + + add-source + + + + src/main/java + + + + + add_test_sources + generate-test-sources + + add-test-source + + + + src/test/java + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.1 + + {{#java8}} + 1.8 + 1.8 + {{/java8}} + {{^java8}} + 1.7 + 1.7 + {{/java8}} + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + attach-sources + + jar-no-fork + + + + + + + + + + sign-artifacts + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + sign-artifacts + verify + + sign + + + + + + + + + + + {{#useOas2}} + + io.swagger + swagger-annotations + ${swagger-annotations-version} + + {{/useOas2}} + {{^useOas2}} + + io.swagger.core.v3 + swagger-annotations + ${swagger-annotations-version} + + {{/useOas2}} + + + + com.sun.jersey + jersey-client + ${jersey-version} + + + com.sun.jersey.contribs + jersey-multipart + ${jersey-version} + + + + + com.fasterxml.jackson.core + jackson-core + ${jackson-version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson-version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson-version} + + + com.fasterxml.jackson.jaxrs + jackson-jaxrs-json-provider + ${jackson-version} + + {{#withXml}} + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + ${jackson-version} + + + {{/withXml}} + {{#joda}} + + com.fasterxml.jackson.datatype + jackson-datatype-joda + ${jackson-version} + + {{/joda}} + {{#java8}} + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson-version} + + {{/java8}} + {{#threetenbp}} + + com.github.joschi.jackson + jackson-datatype-threetenbp + ${jackson-version} + + {{/threetenbp}} + {{^java8}} + + + com.brsanthu + migbase64 + 2.2 + + {{/java8}} + {{#supportJava6}} + + org.apache.commons + commons-lang3 + ${commons_lang3_version} + + + commons-io + commons-io + ${commons_io_version} + + {{/supportJava6}} +{{#useBeanValidation}} + + + javax.validation + validation-api + 1.1.0.Final + provided + +{{/useBeanValidation}} + {{#parcelableModel}} + + + com.google.android + android + 4.1.1.4 + provided + + {{/parcelableModel}} + + + junit + junit + ${junit-version} + test + + + + UTF-8 + {{#useOas2}} + 1.5.15 + {{/useOas2}} + {{^useOas2}} + 2.0.0 + {{/useOas2}} + 1.19.4 + {{#supportJava6}} + 2.5 + 3.6 + {{/supportJava6}} + {{^threetenbp}}2.10.1{{/threetenbp}}{{#threetenbp}}2.6.4{{/threetenbp}} + 1.0.0 + 4.12 + + diff --git a/mustacheTemplates/settings.gradle.mustache b/mustacheTemplates/settings.gradle.mustache new file mode 100644 index 0000000000..b8fd6c4c41 --- /dev/null +++ b/mustacheTemplates/settings.gradle.mustache @@ -0,0 +1 @@ +rootProject.name = "{{artifactId}}" \ No newline at end of file diff --git a/mustacheTemplates/travis.mustache b/mustacheTemplates/travis.mustache new file mode 100644 index 0000000000..70cb81a67c --- /dev/null +++ b/mustacheTemplates/travis.mustache @@ -0,0 +1,17 @@ +# +# Generated by: https://github.com/swagger-api/swagger-codegen.git +# +language: java +jdk: + - oraclejdk8 + - oraclejdk7 +before_install: + # ensure gradlew has proper permission + - chmod a+x ./gradlew +script: + # test using maven + - mvn test + # uncomment below to test using gradle + # - gradle test + # uncomment below to test using sbt + # - sbt test diff --git a/mustacheTemplates/typeInfoAnnotation.mustache b/mustacheTemplates/typeInfoAnnotation.mustache new file mode 100644 index 0000000000..f2a2e1c88f --- /dev/null +++ b/mustacheTemplates/typeInfoAnnotation.mustache @@ -0,0 +1,7 @@ +{{#jackson}} +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "{{discriminator.propertyName}}", visible = true ) +@JsonSubTypes({ + {{#children}} + @JsonSubTypes.Type(value = {{classname}}.class, name = "{{^vendorExtensions.x-discriminator-value}}{{name}}{{/vendorExtensions.x-discriminator-value}}{{#vendorExtensions.x-discriminator-value}}{{{vendorExtensions.x-discriminator-value}}}{{/vendorExtensions.x-discriminator-value}}"), + {{/children}} +}){{/jackson}} diff --git a/mustacheTemplates/xmlAnnotation.mustache b/mustacheTemplates/xmlAnnotation.mustache new file mode 100644 index 0000000000..ead6219b13 --- /dev/null +++ b/mustacheTemplates/xmlAnnotation.mustache @@ -0,0 +1,4 @@ +{{#withXml}} +@XmlRootElement({{#xmlNamespace}}namespace="{{xmlNamespace}}", {{/xmlNamespace}}name = "{{#xmlName}}{{xmlName}}{{/xmlName}}{{^xmlName}}{{classname}}{{/xmlName}}") +@XmlAccessorType(XmlAccessType.FIELD) +{{#jackson}}@JacksonXmlRootElement({{#xmlNamespace}}namespace="{{xmlNamespace}}", {{/xmlNamespace}}localName = "{{#xmlName}}{{xmlName}}{{/xmlName}}{{^xmlName}}{{classname}}{{/xmlName}}"){{/jackson}}{{/withXml}} \ No newline at end of file diff --git a/nb-configuration.xml b/nb-configuration.xml index 885f609825..4245b92b72 100644 --- a/nb-configuration.xml +++ b/nb-configuration.xml @@ -1,39 +1,43 @@ + This file contains additional configuration written by modules in the NetBeans IDE. + The configuration is intended to be shared among all the users of project and + therefore it is assumed to be part of version control checkout. + Without this configuration present, some functionality in the IDE may be limited or fail altogether. + --> - all - true - project - 4 - 4 - 4 - false - 120 - none - true - true - LF - false + Properties that influence various parts of the IDE, especially code formatting and the like. + You can copy and paste the single properties, into the pom.xml file and the IDE will pick them up. + That way multiple projects can share the same settings (useful for formatting rules for example). + Any value defined here will override the pom.xml file value but is only applicable to the current project. + --> + 4 + false + false + false + false + none 4 - 0 - 1 4 - 4 + 120 + false + false false - 80 - none - LICENSE.txt + false + none + 4 + 4 + 4 + 120 + false + project + WRAP_ALWAYS + WRAP_ALWAYS + WRAP_ALWAYS + WRAP_ALWAYS + JDK_21 + none diff --git a/pom.properties b/pom.properties new file mode 100644 index 0000000000..1e2fd89fc3 --- /dev/null +++ b/pom.properties @@ -0,0 +1,5 @@ +#Generated by Maven +#Wed Jun 09 13:49:55 CEST 2021 +groupId=com.sk89q +artifactId=commandhelper +version=3.3.5-SNAPSHOT diff --git a/pom.xml b/pom.xml index bc1344b6e5..193873153d 100644 --- a/pom.xml +++ b/pom.xml @@ -1,28 +1,27 @@ - + 4.0.0 com.sk89q commandhelper - 3.3.1-SNAPSHOT + 3.3.5-SNAPSHOT CommandHelper CommandHelper is a full blown scripting language built right into Minecraft 2010 MethodScript Team - http://wiki.sk89q.com/CommandHelper + https://methodscript.com ladycailin LadyCailin gmail: savannahcailin - http://www.laytonsmith.com#donations + https://methodscript.com project manager developer tester - -6 + +1 sk89q @@ -73,6 +72,12 @@ contributor + + Lildirt + + contributor + + jb_aero @@ -91,10 +96,22 @@ contributor + + PseudoKnight + + contributor + + + + Pieter12345/Woesh0007 + + contributor + + - sk89q's Youtrack - http://youtrack.sk89q.com/issues/CMDHELPER + Github Issues + https://github.com/EngineHub/CommandHelper/issues TeamCity @@ -111,103 +128,57 @@ scm:git:git://github.com/sk89q/commandhelper.git https://github.com/sk89q/commandhelper scm:git:git@github.com:sk89q/commandhelper.git + HEAD UTF-8 true + 16 + 16 - - sk89q-mvn2 - http://mvn2.sk89q.com/repo + enginehub-maven + https://maven.enginehub.org/repo/ - bukkit-repo - http://repo.bukkit.org/content/groups/public/ + papermc-repo + https://repo.papermc.io/repository/maven-public/ - bukkit-maven - http://repo.bukkit.org/content/repositories/releases/ - - true - + sonatype-oss-snapshots1 + https://s01.oss.sonatype.org/content/repositories/snapshots/ maven-central - http://repo1.maven.org/maven2/ + https://repo.maven.apache.org/maven2/ - - - - Plugin Metrics - http://repo.mcstats.org/content/repositories/public - - - - - Java.Net - http://download.java.net/maven/2/ - - - - com.sk89q - worldedit - 5.6.2 - - - org.spout - spoutapi - - - - org.bukkit - bukkit - 1.7.9-R0.3-SNAPSHOT - - - - - - - com.sk89q - worldguard - 5.9 - - - - - net.milkbowl - vault - 1.2.12 - jar + io.papermc.paper + paper-api + 1.21.11-R0.1-SNAPSHOT + com.googlecode.json-simple json-simple 1.1.1 @@ -215,85 +186,82 @@ + org.mindrot jBCrypt 1.0 - - - - - + org.perf4j perf4j 0.9.16 compile jar - - log4j - log4j - 1.2.17 - compile - + org.xerial sqlite-jdbc - 3.7.15-M1 + 3.45.2.0 compile + redis.clients jedis - 2.5.1 + 5.2.0-beta1 jar compile - mysql - mysql-connector-java - 5.1.30 + + com.mysql + mysql-connector-j + 8.3.0 jar compile + postgresql postgresql 9.1-901-1.jdbc4 + + + + com.microsoft.sqlserver + mssql-jdbc + + 12.6.1.jre11 + + + org.yaml snakeyaml - 1.13 + 2.2 + org.apache.commons commons-io 2.4 @@ -301,142 +269,175 @@ + com.jcraft jsch - 0.1.51 + 0.1.55 + net.sourceforge.jchardet jchardet 1.0 - + + + + org.apache.oltu.oauth2 + org.apache.oltu.oauth2.client + 1.0.2 + + + + org.brotli + dec + 0.1.2 + hamcrest-core org.hamcrest jar - 1.3 + 2.2 + test + + + hamcrest-library + org.hamcrest + jar + 2.2 test junit junit - 4.11 + 4.13.2 test org.mockito mockito-core - 1.9.5 + 5.19.0 test - + - - org.java.net.substance - substance - 6.0 - - - - org.mcstats - metrics - 1.2-SNAPSHOT - commons-codec commons-codec jar - 1.9 + 1.16.1 - - - asm - asm-all - 3.3.1 + org.ow2.asm + asm + 9.7.1 jline jline - 2.11 + 2.14.6 - javax.mail - mail - 1.5.0-b01 + com.sun.mail + javax.mail + 1.6.2 javax.activation activation 1.1.1 + + + org.eclipse.lsp4j + org.eclipse.lsp4j + 0.22.0 + + + io.swagger.core.v3 + swagger-annotations + 2.2.20 + + + com.squareup.okhttp + okhttp + 2.7.5 + + + com.squareup.okhttp + logging-interceptor + 2.7.5 + + + com.squareup.okio + okio-jvm + 3.10.0 + + + com.google.code.gson + gson + 2.10.1 + + + io.gsonfire + gson-fire + 1.9.0 + + + org.threeten + threetenbp + 1.6.8 + + + org.slf4j + slf4j-api + 1.7.36 + + + org.slf4j + slf4j-simple + 1.7.36 + + + + org.joml + joml + 1.10.5 + - - sk89q-docs-upload - ftp://sk89q-maven-deploy/commandhelper/ - - maven.sk89q.com - http://maven.sk89q.com/artifactory/libs-release-local + github + GitHub EngineHub Apache Maven Packages + https://maven.pkg.github.com/EngineHub/CommandHelper - - maven.sk89q.com-snapshot - http://maven.sk89q.com/artifactory/libs-snapshot-local - ${basedir}/src/main/java - - - org.apache.maven.wagon - wagon-ftp - 1.0-beta-6 - - - @@ -455,6 +456,22 @@ plugin.yml + + ./nativeSource + false + ${basedir}/src/main/methodscript/ + + workspace.code-workspace + + + + ./nativeSource + false + ${basedir}/src/main/ms-resources/ + + ** + + @@ -469,12 +486,14 @@ org.apache.maven.plugins maven-compiler-plugin - 2.3.2 + 3.12.1 true - 1.7 - 1.7 - -XDignore.symbol.file + 16 + + -XDignore.symbol.file + -parameters + @@ -482,11 +501,11 @@ org.apache.maven.plugins maven-jar-plugin - 2.3.1 + 3.3.0 true - true + pom.properties true true @@ -494,37 +513,18 @@ - WorldEdit.jar CommandHelper/WorldEdit.jar lib/WorldEdit.jar ../lib/WorldEdit.jar ../WorldEdit.jar ../craftbukkit-0.0.1-SNAPSHOT.jar ../bukkit.jar CommandHelper/bukkit.jar + mojang + ../bukkit.jar CommandHelper/bukkit.jar - - - maven-assembly-plugin - 2.2-beta-2 - - ${basedir}/src/main/assembly/default.xml - - - - - - org.apache.maven.plugins - maven-release-plugin - 2.2.2 - - assembly:assembly - assembly:assembly - - - org.apache.maven.plugins maven-shade-plugin - 1.4 + 3.5.2 ShadedBundle @@ -533,8 +533,8 @@ shade - false - Bundle + true + full false - com.sk89q:worldedit:jar:* - com.sk89q:worldguard:jar:* org.perf4j:perf4j:jar:* - log4j:log4j:jar:* + org.yaml:snakeyaml:jar:* com.googlecode.json-simple:json-simple:jar:* org.mindrot:jBCrypt:jar:* com.jcraft:jsch:jar:* org.apache.commons:commons-io:jar:* org.xerial:sqlite-jdbc:jar:* - org.mcstats:metrics:jar:* net.sourceforge.jchardet:jchardet:jar:* redis.clients:jedis:jar:* - mysql:mysql-connector-java:jar:* + com.mysql:mysql-connector-j:jar:* commons-codec:commons-codec:jar:* - asm:asm-all:jar:* + org.ow2.asm:asm:jar:* jline:jline:jar:* - javax.mail:mail:jar:* + com.sun.mail:javax.mail:jar:* javax.activation:activation:jar:* postgresql:postgresql:jar:* + org.apache.oltu.oauth2:org.apache.oltu.oauth2.client:jar:* + org.brotli:dec:jar:* + org.eclipse.lsp4j:org.eclipse.lsp4j:jar:* + org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc:jar:* + + com.google.code.gson:gson:jar:* + com.google.guava:guava:jar:* + + com.squareup.okhttp:okhttp:jar:* + com.squareup.okio:okio-jvm:jar:* + org.jetbrains.kotlin:kotlin-stdlib:jar:* + org.threeten:threetenbp:jar:* + io.gsonfire:gson-fire:jar:* + com.squareup.okhttp:logging-interceptor:jar:* + com.microsoft.sqlserver:mssql-jdbc:jar:* + org.slf4j:slf4j-api:jar:* + org.slf4j:slf4j-simple:jar:* + org.joml:joml:jar:* @@ -573,10 +591,10 @@ org.perf4j com.laytonsmith.libs.org.perf4j - + org.yaml.snakeyaml com.laytonsmith.libs.org.yaml.snakeyaml @@ -599,14 +617,10 @@ - - org.mcstats - com.laytonsmith.libs.org.mcstats - org.mozilla.intl.chardet com.laytonsmith.libs.org.mozilla.intl.chardet @@ -616,20 +630,16 @@ com.laytonsmith.libs.redis.clients - com.mysql.jdbc - com.laytonsmith.libs.com.mysql.jdbc - - - org.gjt.mm.mysql - com.laytonsmith.libs.org.gjt.mm.mysql + com.mysql.cj + com.laytonsmith.libs.com.mysql.cj org.apache.commons.codec com.laytonsmith.libs.org.apache.commons.codec org.objectweb.asm @@ -641,39 +651,91 @@ - - jaxax.mail - com.laytonsmith.libs.javax.mail - org.postgresql com.laytonsmith.libs.org.postgresql + + org.apache.oltu.oauth2 + com.laytonsmith.libs.org.apache.oltu.oauth2 + + + org.brotli + com.laytonsmith.libs.org.brotli + + + org.eclipse + com.laytonsmith.libs.org.eclipse + + + com.google + com.laytonsmith.libs.com.google + + + com.google.common.collect.* + + + + com.squareup.okhttp + com.laytonsmith.libs.com.squareup.okhttp + + + okio + com.laytonsmith.libs.okio + + + kotlin + com.laytonsmith.libs.kotlin + + + org.threeten + com.laytonsmith.libs.threeten + + + io.gsonfire + com.laytonsmith.libs.io.gsonfire + + + org.slf4j + com.laytonsmith.libs.org.slf4j + org.xerial:sqlite-jdbc:jar:* - native/** - org/ibex/** org/sqlite/** + + + + org/sqlite/native/FreeBSD/** + org/sqlite/native/Linux-Android/** + org/sqlite/native/Linux/ppc64/* + org/sqlite/native/Linux/arm/* + org/sqlite/native/Linux/armv6/* + org/sqlite/native/Linux/armv7/* + org/sqlite/native/Linux/x86/* + org/sqlite/native/Linux-Musl/x86/* + org/sqlite/native/Windows/x86/* + org/sqlite/native/Windows/armv7/* + org.apache.commons:commons-io:jar:* @@ -681,125 +743,203 @@ org/apache/commons/io/** + - com.sk89q:worldedit:jar:* + org.perf4j:perf4j:jar:* - - com/sk89q/bukkit/migration/** - com/sk89q/wepif/** - com/sk89q/util/** - com/sk89q/worldedit/Vector.* - - com/sk89q/worldedit/expression/** - - com/sk89q/worldedit/bukkit/BukkitCommandSender.* - com/sk89q/worldedit/LocalPlayer.* - com/sk89q/worldedit/MaxChangedBlocksException.* - com/sk89q/worldedit/WorldEditException.* - com/sk89q/worldedit/WorldEditException.* - com/sk89q/worldedit/EmptyClipboardException.* - com/sk89q/worldedit/BlockVector.* - com/sk89q/worldedit/data/DataException.* + ** - - com/sk89q/jchronic/** - com/google/** - + - com.sk89q:worldguard:jar:* - + net.sourceforge.jchardet:jchardet:jar:* - com/sk89q/worldguard/protection/flags/InvalidFlagFormat.* - com/sk89q/worldguard/protection/regions/ProtectedRegion.* - com/sk89q/worldguard/protection/regions/GlobalProtectedRegion.* - com/sk89q/worldguard/protection/regions/ProtectedCuboidRegion.* - com/sk89q/worldguard/protection/flags/Flag.* - com/sk89q/worldguard/protection/regions/ProtectedPolygonalRegion.* - com/sk89q/worldguard/protection/flags/RegionGroupFlag.* - com/sk89q/worldguard/protection/regions/ProtectedPolygonalRegion.* - com/sk89q/worldguard/protection/flags/EnumFlag.* - com/sk89q/worldguard/protection/regions/ProtectedRegion$CircularInheritanceException* + ** - - org.perf4j:perf4j:jar:* + redis.clients:jedis:jar:* ** - log4j:log4j:jar:* + com.mysql:mysql-connector-j:jar:* ** - org.mcstats:metrics:jar:* + commons-codec:commons-codec:jar:* ** + - net.sourceforge.jchardet:jchardet:jar:* + org.ow2.asm:asm:jar:* ** - redis.clients:jedis:jar:* + jline:jline:jar:* ** - mysql:mysql-connector-java:jar:* + com.sun.mail:javax.mail:jar:* ** - commons-codec:commons-codec:jar:* + javax.activation:activation:jar:* ** - + - asm:asm-all:jar:* + org.apache.oltu.oauth2:org.apache.oltu.oauth2.client:jar:* ** - jline:jline:jar:* + org.eclipse.lsp4j:org.eclipse.lsp4j:jar:* ** + + META-INF/** + - javax.mail:mail:jar:* + org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc:jar:* ** + + META-INF/** + - javax.activation:activation:jar:* + com.google.code.gson:gson:jar:* ** + + META-INF/** + - postgresql:postgresql:jar:* + com.google.guava:guava:jar:* + + ** + + + META-INF/** + + com/google/common/collect/** + + + + com.squareup.okhttp:okhttp:jar:* + + ** + + + META-INF/** + + + + com.squareup.okio:okio-jvm:jar:* + + ** + + + META-INF/** + + + + org.threeten:threetenbp:jar:* + + ** + + + META-INF/** + + + + io.gsonfire:gson-fire:jar:* ** + + META-INF/** + + + + com.squareup.okhttp:logging-interceptor:jar:* + + ** + + + META-INF/** + + + + com.microsoft.sqlserver:mssql-jdbc:jar:* + + ** + + + META-INF/** + + + + org.jetbrains.kotlin:kotlin-stdlib:jar:* + + ** + + + META-INF/** + + + + org.slf4j:slf4j-api:* + + META-INF/** + + + + org.slf4j:slf4j-simple:* + + META-INF/** + + + + org.joml:joml:* + + META-INF/** + @@ -809,7 +949,7 @@ org.codehaus.mojo exec-maven-plugin - 1.2.1 + 3.2.0 cache-annotations @@ -830,7 +970,7 @@ org.bsc.maven maven-processor-plugin - 2.2.4 + 5.0-jdk8-rc3 process @@ -847,6 +987,37 @@ com.laytonsmith.core.extensions.ExtensionAnnotationProcessor com.laytonsmith.core.MObjectAnnotationProcessor + true + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.5 + + false + + + **/*Test.java + **/Test*.java + **/*Tests.java + **/*TestCase.java + + + **/RandomTests.java + + -Dfile.encoding=UTF-8 --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED + + + + + org.apache.maven.plugins + maven-release-plugin + 3.0.1 + + false + true + true @@ -854,72 +1025,96 @@ org.apache.maven.plugins maven-javadoc-plugin - 2.4 + 3.6.3 org.apache.maven.plugins maven-deploy-plugin - 2.6 + 3.1.1 org.apache.maven.plugins maven-source-plugin - 2.1.2 + 3.3.0 org.apache.maven.plugins - maven-surefire-plugin - 2.9 - - - - **/*Test.java - **/Test*.java - **/*TestCase.java - - - **/RandomTests.java - - + maven-clean-plugin + 3.3.2 + + + org.apache.maven.plugins + maven-install-plugin + 3.1.1 + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + + org.apache.maven.plugins + maven-site-plugin + 4.0.0-M13 org.codehaus.mojo cobertura-maven-plugin - 2.4 + 2.7 - + org.apache.maven.plugins - maven-site-plugin - 3.1 - - - - org.apache.maven.plugins - maven-project-info-reports-plugin - 2.5 - - - - license - index - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.8.1 - - - org.codehaus.mojo - cobertura-maven-plugin - 2.4 - - - + maven-checkstyle-plugin + 3.3.1 + + + checkstyle + test + + ${project.compileSourceRoots} + ${project.testCompileSourceRoots} + true + ${basedir}/checkstyle.xml + config_loc=${basedir} + UTF-8 + true + true + false + + + check + + + + + + com.puppycrawl.tools + checkstyle + 8.45 + + + + + org.apache.maven.plugins + maven-enforcer-plugin + 3.4.1 + + + enforce-maven + + enforce + + + + + 3.6.3 + + + + + @@ -936,14 +1131,79 @@ true + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.1.2 + + true + + + + org.jacoco + jacoco-maven-plugin + 0.8.7 + + true + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + false + + + + fail-on-test-failures + + false + + maven-annotation-plugin - http://maven-annotation-plugin.googlecode.com/svn/trunk/mavenrepo + https://maven-annotation-plugin.googlecode.com/svn/trunk/mavenrepo + + + maven-central + https://repo.maven.apache.org/maven2/ + + + apache-snapshots + https://repository.apache.org/content/repositories/snapshots/ + + + + org.apache.maven.plugins + maven-project-info-reports-plugin + 3.1.2 + + + + license + index + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.8.1 + + + org.codehaus.mojo + cobertura-maven-plugin + 2.1 + + + diff --git a/release.properties b/release.properties new file mode 100644 index 0000000000..bc16f0181b --- /dev/null +++ b/release.properties @@ -0,0 +1,26 @@ +#release configuration +#Wed Jun 09 13:50:15 CEST 2021 +projectVersionPolicyId=default +scm.branchCommitComment=@{prefix} prepare branch @{releaseLabel} +pinExternals=false +project.rel.com.sk89q\:commandhelper=3.3.4-final +exec.activateProfiles=github +pushChanges=false +project.scm.com.sk89q\:commandhelper.tag=HEAD +project.scm.com.sk89q\:commandhelper.developerConnection=scm\:git\:git@github.com\:sk89q/commandhelper.git +scm.rollbackCommitComment=@{prefix} rollback the release of @{releaseLabel} +remoteTagging=true +scm.commentPrefix=[maven-release-plugin] +releaseStrategyId=default +project.scm.com.sk89q\:commandhelper.connection=scm\:git\:git\://github.com/sk89q/commandhelper.git +completedPhase=end-release +scm.url=scm\:git\:git@github.com\:sk89q/commandhelper.git +project.scm.com.sk89q\:commandhelper.url=https\://github.com/sk89q/commandhelper +scm.developmentCommitComment=@{prefix} prepare for next development iteration +scm.tagNameFormat=@{project.artifactId}-@{project.version} +scm.tag=commandhelper-3.3.4-final +exec.snapshotReleasePluginAllowed=false +project.dev.com.sk89q\:commandhelper=3.3.5-SNAPSHOT +preparationGoals=clean verify +scm.releaseCommitComment=@{prefix} prepare release @{releaseLabel} +exec.pomFileName=pom.xml diff --git a/scripts/bash/update-apps-api b/scripts/bash/update-apps-api new file mode 100644 index 0000000000..2a602c6019 --- /dev/null +++ b/scripts/bash/update-apps-api @@ -0,0 +1,180 @@ +#!/bin/bash + +# NOTE: This is a Bash version of the equivalent PowerShell script. If you make modifications to this file, +# you MUST make similar modifications to the PowerShell equivalent. + +set -e + +# This is the checkout which we build against for the swagger-codegen project. This can be a tag or a commit (or technically a branch) +# but is meant to be relatively stable, and regardless only intentionally updated, so that builds are generally speaking reproducable. +CheckoutId="129235049ab062492c75cc193ecfada84633cc56" + +SkipBuild=0 +SkipUpdate=0 + +while test $# -gt 0 +do + case "$1" in + --SkipBuild) + SkipBuild=1 + ;; + --SkipUpdate) + SkipUpdate=1 + ;; + esac + shift +done + +echo "SkipBuild=$SkipBuild" + +function Status { + echo -e "\e[32m$1\e[0m" +} + +function ErrorAndQuit { + echo -e "\e[31m$1\e[0m" + exit 1 +} + +function TestSoftware { + softwares=("java" "git" "mvn" "npm" "tsp") + notFound=() + + for s in "${softwares[@]}" + do + if ! [[ $(command -v $s) ]]; then + # not found + notFound+=("$s") + fi + done + + if ! [[ "${#notFound[@]}" -eq 0 ]]; then + echo "One or more required software packages are not installed, and must be installed and available on your path before continuing." + echo "Please install the following software before trying again:" + for software in "${notFound[@]}" + do + echo "$software" + done + exit + fi +} + +function TestProjectFile { + Path="$1" + if ! [[ -d "$1" ]]; then + read -p "Creating $Path, continue? [y/n] " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + mkdir -p "$Path" + else + exit + fi + else + echo "Using $Path" + fi +} + +function CloneOrCheckout { + repo="$1" + localPath="$2" + branch="$3" + + if [[ -z "$(ls -A $localPath)" ]]; then + #Clone + Status "Cloning $repo into $localPath" + git clone "$repo" "$localPath" --branch "$branch" + else + Status "Updating $localPath" + #Pull + status=$(git status --porcelain) + + if [[ -z "$status" ]]; then + # :thumbsup: + git fetch + git checkout "$branch" + else + # :thumbsdown: + ErrorAndQuit "$localPath has local changes, refusing to continue. Please stash or otherwise revert your changes before continuing." + fi + fi +} + +function CheckFileOrExit { + Path="$1" + if [[ -d "$Path" ]]; then + ErrorAndQuit "Could not find $Path, exiting" + fi +} + +function StartMain { + SwaggerGenerator="$1" + JavaRepo="$2" + NodeRepo="$3" + InputSpec="$4" + TemplateDir="$5" + + Status "Ensuring prerequisite software is installed" + TestSoftware + + Status "Creating directories if needed" + TestProjectFile "$SwaggerGenerator" + TestProjectFile "$JavaRepo" + TestProjectFile "$NodeRepo" + + SwaggerGenerator=$(realpath "$SwaggerGenerator") + JavaRepo=$(realpath "$JavaRepo") + NodeRepo=$(realpath "$NodeRepo") + MethodScriptDirectory=$(realpath $( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )/../../) + + cd "$SwaggerGenerator" + + if [[ "$SkipUpdate" -eq 0 ]]; then + Status "Getting Swagger Codegen" + # Checkout SwaggerGenerator + CloneOrCheckout "https://github.com/swagger-api/swagger-codegen" "$SwaggerGenerator" "$CheckoutId" + fi + + if [[ "$SkipBuild" -eq 0 ]]; then + Status "Building Swagger Codegen" + mvn -Dmaven.test.skip=true clean package + fi + + GeneratorJar=$(realpath modules/swagger-codegen-cli/target/swagger-codegen-cli.jar) + CheckFileOrExit "$GeneratorJar" + + Status "Generating OpenAPI spec from $InputSpec" + cd "$InputSpec" + npm install + tsp compile main.tsp --emit "@typespec/openapi3" + + InputSpec="$InputSpec/tsp-output/@typespec/openapi3/openapi.yaml" + + Status "Using $InputSpec as the OpenAPI input" + + Status "Generating Java Client" + java -jar "$GeneratorJar" generate -i "$InputSpec" -l java -o "$JavaRepo" -DhideGenerationTimestamp=true --template-dir "$MethodScriptDirectory/mustacheTemplates" + + # Copy in src/main/java + Status "Moving API files into MethodScript" + cd "$MethodScriptDirectory" + mkdir -p "$MethodScriptDirectory/src/main/java/io/swagger/client" + rm -rf "$MethodScriptDirectory/src/main/java/io/swagger/client" + mkdir "$MethodScriptDirectory/src/main/java/io/swagger/client" + cp -R "$JavaRepo/src/main/java/io/swagger/client/" "$MethodScriptDirectory/src/main/java/io/swagger/" + + Status "Generating Node Server" + java -jar "$GeneratorJar" generate -i "$InputSpec" -l nodejs-server -o "$NodeRepo" --disable-examples --template-dir "$TemplateDir" + npx swagger-typescript-api -p "$InputSpec" -o "$NodeRepo\models" --clean-output --no-client + + Status "Done!" +} + +SwaggerGeneratorDirectory="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )/../../../SwaggerGenerator" +JavaRepoDirectory="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )/../../../MethodScriptAppsJavaApi" +NodeRepoDirectory="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )/../../../apps.methodscript.com" +InputSpec="$(realpath $( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )/../../src/main/resources/apps.methodscript.com)" +TemplateDir="$NodeRepoDirectory/mustacheTemplates" + +StartMain "$SwaggerGeneratorDirectory" "$JavaRepoDirectory" "$NodeRepoDirectory" "$InputSpec" "$TemplateDir" + +exit 0 diff --git a/scripts/windows/update-apps-api.ps1 b/scripts/windows/update-apps-api.ps1 new file mode 100644 index 0000000000..e4a1f247fc --- /dev/null +++ b/scripts/windows/update-apps-api.ps1 @@ -0,0 +1,178 @@ +<# + NOTE: This is a PowerShell version of the equivalent Bash script. If you make modifications to this file, + you MUST make similar modifications to the Bash equivalent. +#> + +$ErrorActionPreference = "Stop" + +# This is the checkout which we build against for the swagger-codegen project. This can be a tag or a commit (or technically a branch) +# but is meant to be relatively stable, and regardless only intentionally updated, so that builds are generally speaking reproducable. +$CheckoutId = "129235049ab062492c75cc193ecfada84633cc56" + +[bool] $SkipBuild = $false; +[bool] $SkipUpdate = $false; +if($args.Contains("--SkipBuild")) { + $SkipBuild = $true; +} + +if($args.Contains("--SkipUpdate")) { + $SkipUpdate = $true; +} + +function Status($Message) { + Write-Host -ForegroundColor Green -Object $Message +} + +function ErrorAndQuit($Message) { + Write-Host -ForegroundColor Red -Object $Message + exit +} + +function New-Software($Name, $Exe, $Instructions) { + return @{Name = $Name; Exe = $Exe; Instructions = $Instructions} +} + +function Test-Software { + $softwares = (New-Software -Name "Java" -Exe "java.exe" -Instructions "https://adoptopenjdk.net/"), ` + (New-Software -Name "Git" -Exe "git.exe" -Instructions "https://git-scm.com/download/win"), ` + (New-Software -Name "Maven" -Exe "mvn.cmd" -Instructions "https://maven.apache.org/guides/getting-started/windows-prerequisites.html"), ` + (New-Software -Name "NPM" -Exe "npm.ps1" -Instructions "https://docs.npmjs.com/downloading-and-installing-node-js-and-npm"), ` + (New-Software -Name "TSP" -Exe "tsp.ps1" -Instructions "https://typespec.io/docs") + + + $notFound = @() + + foreach($software in $softwares) { + if($null -eq (Get-Command -Name $software.Exe -ErrorAction SilentlyContinue)) { + $notFound += $software + } + } + + if($notFound.Count -gt 0) { + Write-Host "One or more required software packages are not installed, and must be installed and available on your path before continuing." + Write-Host "Please install the following software before trying again:" + foreach($software in $notFound) { + Write-Host "$($software.Name) - $($software.Instructions)" + } + return $false + } + + return $true +} + +function Test-ProjectFile($Path) { + $Exists = Test-Path $Path -PathType Container + if(-not $exists) { + New-Item -Path $Path -ItemType Directory -Confirm + $Exists = Test-Path $Path -PathType Container + if(-not $Exists) { + exit + } + } else { + Write-Host "Using $Path" + } +} + +function CloneOrCheckout($repo, $localPath, $branch) { + $directoryInfo = Get-ChildItem $localPath | Measure-Object + if($directoryInfo.count -eq 0) { + #Clone + Status "Cloning $repo" + git clone $repo $localPath "--branch" $branch + } else { + Status "Updating $localPath" + #Pull + $status = (git status --porcelain) + # Write-Host $status.GetType() + if($null -eq $status) { + # :thumbsup: + git fetch + git checkout $branch + } else { + # :thumbsdown: + ErrorAndQuit "$localPath has local changes, refusing to continue. Please stash or otherwise revert your changes before continuing." + } + } +} + +function CheckFileOrExit($Path) { + $Exists = Test-Path $Path + if(-not $Exists) { + ErrorAndQuit "Could not find $Path, exiting" + } +} + +function Start-Main([string] $SwaggerGenerator, + [string] $JavaRepo, + [string] $NodeRepo, + [string] $InputSpec, + [string] $TemplateDir) { + Status "Ensuring prerequisite software is installed" + if((Test-Software) -eq $false) { + return + } + + Status "Creating directories if needed" + Test-ProjectFile -Path $SwaggerGenerator + Test-ProjectFile -Path $JavaRepo + Test-ProjectFile -Path $NodeRepo + + $SwaggerGenerator = (Resolve-Path $SwaggerGenerator).Path + $JavaRepo = (Resolve-Path $JavaRepo).Path + $NodeRepo = (Resolve-Path $NodeRepo).Path + $MethodScriptDirectory = (Resolve-Path "$PSScriptRoot\..\..").Path + + Set-Location $SwaggerGenerator + if(-not $SkipUpdate) { + Status "Getting Swagger Codegen" + # Checkout SwaggerGenerator + CloneOrCheckout -repo "https://github.com/swagger-api/swagger-codegen" -localPath $SwaggerGenerator -branch $CheckoutId + } + + if(-not $SkipBuild) { + Status "Building Swagger Codegen" + mvn "-Dmaven.test.skip=true" clean package + } + + $GeneratorJar = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath(".\modules\swagger-codegen-cli\target\swagger-codegen-cli.jar") + CheckFileOrExit $GeneratorJar + + Status "Generating OpenAPI spec from $InputSpec" + + Set-Location $InputSpec + npm install + tsp compile main.tsp --emit @typespec/openapi3 + + $InputSpec = "$InputSpec\tsp-output\@typespec\openapi3\openapi.yaml" + + Status "Using $InputSpec as the OpenAPI input" + + Status "Generating Java Client" + java -jar $GeneratorJar generate -i $InputSpec -l java -o $JavaRepo -DhideGenerationTimestamp=true "--template-dir" "$MethodScriptDirectory/mustacheTemplates" + + # Copy in src/main/java + Status "Moving API files into MethodScript" + Set-Location $MethodScriptDirectory + New-Item -Path "$MethodScriptDirectory\src\main\java\io\swagger\client" -ItemType Directory -Force | Out-Null + Remove-Item "$MethodScriptDirectory\src\main\java\io\swagger\client" -Force -Recurse + New-Item -Path "$MethodScriptDirectory\src\main\java\io\swagger\client" -ItemType Directory -Force | Out-Null + Copy-Item -Recurse -Path "$JavaRepo\src\main\java\io\swagger\client\*" -Destination "$MethodScriptDirectory\src\main\java\io\swagger\client" + + Status "Generating Node Server" + java -jar $GeneratorJar generate -i $InputSpec -l nodejs-server -o $NodeRepo "--disable-examples" "--template-dir" $TemplateDir + npx swagger-typescript-api -p $InputSpec -o "$NodeRepo\models" --clean-output --no-client + + Status "Done!" +} + +$SwaggerGeneratorDirectory = "$PSScriptRoot\..\..\..\SwaggerGenerator" +$JavaRepoDirectory = "$PSScriptRoot\..\..\..\MethodScriptAppsJavaApi" +$NodeRepoDirectory = "$PSScriptRoot\..\..\..\apps.methodscript.com" +$InputSpec = (Resolve-Path "$PSScriptRoot\..\..\src\main\resources\apps.methodscript.com\").Path +$TemplateDir = Join-Path -Path $NodeRepoDirectory -ChildPath 'mustacheTemplates' + +Start-Main -SwaggerGenerator $SwaggerGeneratorDirectory ` + -JavaRepo $JavaRepoDirectory ` + -NodeRepo $NodeRepoDirectory ` + -InputSpec $InputSpec ` + -TemplateDir $TemplateDir diff --git a/src/installer/Windows/MethodScriptInstaller.exe b/src/installer/Windows/MethodScriptInstaller.exe new file mode 100644 index 0000000000..1dfec8ce31 Binary files /dev/null and b/src/installer/Windows/MethodScriptInstaller.exe differ diff --git a/src/installer/Windows/README.md b/src/installer/Windows/README.md new file mode 100644 index 0000000000..508a1b7a19 --- /dev/null +++ b/src/installer/Windows/README.md @@ -0,0 +1,8 @@ +This installer is written so that in general it shouldn't need updating. +However, if it does need updating, you need the NSIS compiler. This can +be found [here](https://sourceforge.net/projects/nsis/). You will also need the +INetC plugin, found [here](http://nsis.sourceforge.net/Inetc_plug-in). Copy the +dlls into C:\Program Files (x86)\NSIS\Plugins. + +Install and launch the compiler. Open the installer.nsi file in the compiler, and +compile it. This will create the installer.exe file. \ No newline at end of file diff --git a/src/installer/Windows/installer.nsi b/src/installer/Windows/installer.nsi new file mode 100644 index 0000000000..c89df78aa1 --- /dev/null +++ b/src/installer/Windows/installer.nsi @@ -0,0 +1,272 @@ + +; MethodScript Installer +; Checks for Java, and if not present, downloads and installs AdoptOpenJDK +; then downloads the latest MethodScript jar, running the install command. +; Optionally, sample files from the 'examples' command can be installed. + +Unicode true + +!include "MUI2.nsh" +!include "LogicLib.nsh" +!include "nsDialogs.nsh" +!include "x64.nsh" +!include "FileFunc.nsh" + +Name "MethodScript Cmdline Installer" +Caption "MethodScript Cmdline" +OutFile "MethodScriptInstaller.exe" +ShowInstDetails show + +Function .onInit + ${If} ${RunningX64} + SetRegView 64 + ${EndIf} + SetOutPath $TEMP + File /oname=spltmp.bmp "..\..\main\resources\siteDeploy\resources\images\CommandHelper_IconHighRes_Sprite.bmp" + advsplash::show 750 1500 1500 0xFF00FF $TEMP\spltmp + Pop $0 + Delete $TEMP\spltmp.bmp +FunctionEnd + +Function un.onInit + ${If} ${RunningX64} + SetRegView 64 + ${EndIf} +FunctionEnd + +!define MUI_ICON "..\..\main\resources\siteDeploy\resources\images\favicon.ico" +!define MUI_HEADERIMAGE +!define MUI_HEADERIMAGE_BITMAP "..\..\main\resources\siteDeploy\resources\images\CommandHelper_Icon.bmp" +!define MUI_HEADERIMAGE_RIGHT + + +!define MUI_WELCOMEPAGE_TITLE "MethodScript Cmdline Installer" +!define MUI_WELCOMEPAGE_TEXT "This installer downloads and configures your system with everything you need to \ +run MethodScript from the command line. If Java is already installed, that version will be used, but if you don't \ +have Java installed, AdoptOpenJDK will be installed for you. If you wish to use another version of Java other than \ +those offered, please exit the installer, install Java manually, then restart the installer." +!insertmacro MUI_PAGE_WELCOME +!insertmacro MUI_PAGE_LICENSE "..\..\..\LICENSE.txt" +Page Custom CheckJava LeaveJava + +!insertmacro MUI_PAGE_INSTFILES + + +!insertmacro MUI_LANGUAGE "English" +Var JavaInstallationPath +Var Dialog +Var InstallJava +Var JavaVersion +Var JDK +Var JRE +Var UseJRE +Var Use64Bit +Var SkipJava + +Function CheckJava + + StrCpy $InstallJava "true" + !insertmacro MUI_HEADER_TEXT "Java Installation" "Select the Java version to install. \ + To install another version, exit the installer, manually install Java, then run it again." + nsDialogs::Create 1018 + Pop $Dialog + + ${If} $Dialog == error + Abort + ${EndIf} + + ${NSD_CreateGroupBox} 10% 10u 80% 62u "JDK/JRE" + Pop $0 + + ${NSD_CreateLabel} 20% 25u 50% 10u "If you aren't sure which one to select, use JRE" + + ${NSD_CreateRadioButton} 20% 35u 20% 10u "JRE" + Pop $JRE + + ${NSD_CreateRadioButton} 20% 45u 20% 10u "JDK" + Pop $JDK + + ${NSD_CreateGroupBox} 5% 75u 90% 34u "Java Version" + Pop $0 + + ${NSD_CreateDropList} 10% 87u 80% 80u "Version" + Pop $JavaVersion + ${NSD_CB_AddString} $JavaVersion "16" + + ${NSD_CreateCheckBox} 10% 95u 80% 80u "Skip Java Installation (MethodScript won't run without Java)" + Pop $SkipJava + + nsDialogs::Show +FunctionEnd + +Function LeaveJava + ${NSD_GetState} $JRE $R0 + ${NSD_GetState} $JDK $R1 + ${NSD_GetState} $SkipJava $R2 + + ${If} $R2 == "1" + StrCpy $InstallJava "false" + StrCpy $R0 "16" + ${EndIf} + + ${If} $R2 == "0" + ${If} $R0 == 1 + StrCpy $UseJRE "true" + ${ElseIf} $R1 == 1 + StrCpy $UseJRE "false" + ${Else} + MessageBox MB_ICONEXCLAMATION "Please select either the JDK or JRE" + Abort + ${EndIf} + + ${NSD_GetText} $JavaVersion $R0 + + ${If} $R0 == "" + MessageBox MB_ICONEXCLAMATION "Please select the Java version you wish to install" + Abort + ${ElseIf} $R0 == "16" + StrCpy $JavaVersion "16" + ${Else} + StrCpy $JavaVersion $R0 + ${EndIf} + + ${If} ${RunningX64} + StrCpy $Use64Bit "true" + ${Else} + StrCpy $Use64Bit "false" + ${EndIf} + ${EndIf} +FunctionEnd + +Section DoOverview + ${If} $InstallJava == "true" + DetailPrint "Installing Java" + DetailPrint "Using JRE: $UseJRE" + DetailPrint "Java Version to install: $JavaVersion" + DetailPrint "Install 64 bit Java: $Use64Bit" + ${Else} + DetailPrint "Skipping Java Installation" + ${EndIf} +SectionEnd + +Var JavaDownload +Section DoJavaInstall + ${If} $InstallJava == "false" + goto DoJavaInstallEnd + ${EndIf} + ${If} $InstallJava == "true" + ${If} $JavaVersion == "16" + ${If} $UseJRE == "true" + ${If} $Use64Bit == "true" + StrCpy $JavaDownload "https://github.com/AdoptOpenJDK/openjdk16-binaries/releases/download/jdk-16.0.1%2B9/OpenJDK16U-jre_x64_windows_hotspot_16.0.1_9.msi" + ${Else} + StrCpy $JavaDownload "https://github.com/AdoptOpenJDK/openjdk16-binaries/releases/download/jdk-16.0.1%2B9/OpenJDK16U-jre_x86-32_windows_hotspot_16.0.1_9.msi" + ${EndIf} + ${Else} + ${If} $Use64Bit == "true" + StrCpy $JavaDownload "https://github.com/AdoptOpenJDK/openjdk16-binaries/releases/download/jdk-16.0.1%2B9/OpenJDK16U-jdk_x64_windows_hotspot_16.0.1_9.msi" + ${Else} + StrCpy $JavaDownload "https://github.com/AdoptOpenJDK/openjdk16-binaries/releases/download/jdk-16.0.1%2B9/OpenJDK16U-jdk_x86-32_windows_hotspot_16.0.1_9.msi" + ${EndIf} + ${EndIf} + ${Else} + ;; Forgotten use case! + MessageBox MB_ICONSTOP "Error, missing use case!" + ${EndIf} + ${EndIf} + GetInstaller: + DetailPrint "Downloading Java Installer from $JavaDownload to $TEMP\JavaInstaller.msi" + inetc::get $JavaDownload $TEMP\JavaInstaller.msi + Pop $R0 ;Get the return value + ${If} $R0 == "success" + MessageBox MB_ICONSTOP "Download failed: $R0" + Abort + ${EndIf} + DetailPrint "Installing Java" + ExecWait "msiexec /i $\"$TEMP\JavaInstaller.msi$\"" $0 + ${If} $0 != 0 + MessageBox MB_ABORTRETRYIGNORE "Java installation failed or was cancelled" IDABORT AbortInstaller IDRETRY GetInstaller + ${EndIf} + + Delete $TEMP\JavaInstaller.msi + Call _FindJava + ${If} $JavaInstallationPath == "" + MessageBox MB_ABORTRETRYIGNORE "After installation, Java still cannot be found" IDABORT AbortInstaller IDRETRY GetInstaller + ${EndIf} + goto DoJavaInstallEnd + AbortInstaller: + Abort + DoJavaInstallEnd: +SectionEnd + +Section DoDownloadMethodScript + ; First, create the AppData folder, which is where the jar will go + ;;;; TODO: Write out the icon in the installer, and update the DoConfigureUninstaller location + CreateDirectory $LOCALAPPDATA\MethodScript + DetailPrint "Downloading latest MethodScript jar to $LOCALAPPDATA\MethodScript\MethodScript.jar" + inetc::get "https://methodscript.com/MethodScript.jar" $LOCALAPPDATA\MethodScript\MethodScript.jar + ExecWait "java --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.prefs/java.util.prefs=ALL-UNNAMED -Xrs -jar $LOCALAPPDATA\MethodScript\MethodScript.jar install-cmdline" + ExecWait "java -jar $LOCALAPPDATA\MethodScript\MethodScript.jar eval $\"exit()$\"" + SetOutPath $LOCALAPPDATA\MethodScript + File /oname=icon.ico "..\..\main\resources\siteDeploy\resources\images\commandhelper_icon.ico" + ; This might not be working, but anyways, the icon is used in the uninstaller, so putting the icon in the folder is necessary. + WriteINIStr "$LOCALAPPDATA\MethodScript\desktop.ini" ".ShellClassInfo" "IconResource" "$LOCALAPPDATA\MethodScript\icon.ico,0" + WriteINIStr "$LOCALAPPDATA\MethodScript\desktop.ini" "ViewState" "FolderType" "Documents" + SetFileAttributes "$LOCALAPPDATA\MethodScript\desktop.ini" HIDDEN|SYSTEM +SectionEnd + +Section DoConfigureUninstaller + WriteUninstaller "$LOCALAPPDATA\MethodScript\uninstall.exe" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\MethodScript" "DisplayName" "MethodScript" + WriteRegExpandStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\MethodScript" \ + "UninstallString" "$\"$LOCALAPPDATA\MethodScript\uninstall.exe$\"" + WriteRegExpandStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\MethodScript" \ + "InstallLocation" "$\"$LOCALAPPDATA\MethodScript$\"" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\MethodScript" \ + "DisplayIcon" "$\"$LOCALAPPDATA\MethodScript\icon.ico$\"" + ; TODO Once a silent uninstall is ready + ;WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\MethodScript" \ + ; "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S" + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\MethodScript" \ + "NoRepair" "1" + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\MethodScript" \ + "NoModify" "1" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\MethodScript" "Publisher" "MethodScript Contributors" + ${GetSize} "$LOCALAPPDATA\MethodScript" "/S=OK" $0 $1 $2 + IntFmt $0 "0x%08X" $0 + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\MethodScript" "EstimatedSize" "$0" + nsExec::ExecToStack "java -jar $LOCALAPPDATA\MethodScript\MethodScript.jar eval $\"engine_build_date()$\"" + Pop $0 ; Return + Pop $1 ; Output + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\MethodScript" "DisplayVersion" "$1" + + DetailPrint "Cmdline MethodScript is now installed! You may need to reboot your computer to use it from the shell." + DetailPrint "Try running `mscript` or `mscript -- help`." +SectionEnd + +Section un.DoMethodScriptUninstall + ExecWait "java -jar $LOCALAPPDATA\MethodScript\MethodScript.jar uninstall-cmdline" +SectionEnd + +Section un.DoPreferencesUninstall + MessageBox MB_YESNO "Would you like to remove your preferences and other files located at $LOCALAPPDATA\MethodScript?" IDNO DoPreferencesUninstallEnd + RMDir /R /REBOOTOK "$LOCALAPPDATA\MethodScript" + DoPreferencesUninstallEnd: +SectionEnd + +Section un.DoJavaUninstall + MessageBox MB_ICONINFORMATION "If you wish to uninstall Java as well, please find the uninstaller in Add/Remove Programs." +SectionEnd + +Section un.DoUninstallerUninstall + DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\MethodScript" +SectionEnd + +Function _FindJava + ExecWait "java -version" $0 + ${if} $0 == "0" + StrCpy $JavaInstallationPath "java" + ${else} + StrCpy $JavaInstallationPath "" + ${endif} +FunctionEnd diff --git a/src/main/assembly/default.xml b/src/main/assembly/default.xml deleted file mode 100644 index e2d906e30f..0000000000 --- a/src/main/assembly/default.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - false - - tar.gz - tar.bz2 - zip - - - - ${project.build.directory}/${artifactId}-${project.version}.jar - CommandHelper.jar - / - false - - - README.html - / - true - - - - - - CHANGELOG.txt - LICENSE.txt - - - - \ No newline at end of file diff --git a/src/main/java/com/laytonsmith/PureUtilities/ArgumentParser.java b/src/main/java/com/laytonsmith/PureUtilities/ArgumentParser.java index 3605765da9..7037d4e31d 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/ArgumentParser.java +++ b/src/main/java/com/laytonsmith/PureUtilities/ArgumentParser.java @@ -1,1095 +1,1545 @@ package com.laytonsmith.PureUtilities; +import com.laytonsmith.PureUtilities.Common.StringUtils; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Objects; + +// TODO: Add enum type to arg types, as well as providing hooks for autocomplete and custom argument validators /** - * An ArgumentParser allows for programmatic registration of arguments, - * which will be automatically parsed and validated. Additionally, - * automatically generated help text can be retrieved and displayed, perhaps - * if a --help argument is present. - * + * An ArgumentParser allows for programmatic registration of arguments, which + * will be automatically parsed and validated. Additionally, automatically + * generated help text can be retrieved and displayed, perhaps if a --help + * argument is present. + * */ -public class ArgumentParser { - - /** - * A description of the command itself. - */ - String description = ""; - List argumentModel = new ArrayList(); - - /** - * Returns the default argument, if it exists. - * - * @return - */ - private Argument getArgument() { - for (Argument a : argumentModel) { - if (a.shortArg == null && a.longArg == null) { - return a; - } - } - return null; - } - - private Argument getArgument(Character c) { - for (Argument a : argumentModel) { - if (a.shortArg == null) { - continue; - } - if (a.shortArg.equals(c)) { - return a; - } - } - return null; - } - - private Argument getArgument(String s) { - for (Argument a : argumentModel) { - if (a.longArg == null) { - continue; - } - if (a.longArg.equals(s)) { - return a; - } - } - return null; - } - - private class Argument { - - Character shortArg; - String longArg; - Type argType; - String defaultVal; - List defaultList; - String description; - String usageName; - boolean required; - String singleVal; - List arrayVal; - - private Argument(Argument arg) { - if (arg == null) { - return; - } - this.shortArg = arg.shortArg; - this.longArg = arg.longArg; - this.argType = arg.argType; - this.defaultVal = arg.defaultVal; - this.description = arg.description; - this.usageName = arg.usageName; - this.required = arg.required; - } - - private Argument(Character shortArg, String longArg, Type argType, String defaultVal, String description, String usageName, boolean required) { - this.shortArg = shortArg; - this.longArg = longArg; - this.argType = argType; - this.description = description; - - this.defaultVal = defaultVal; - if (isArray() && defaultVal != null) { - defaultList = ArgumentParser.this.lex(defaultVal); - } - this.usageName = usageName; - this.required = required; - } - - public final boolean isFlag() { - return argType == Type.BOOLEAN; - } - - public final boolean isArray() { - return argType == Type.ARRAY_OF_NUMBERS || argType == Type.ARRAY_OF_STRINGS; - } - - public final boolean isSingle() { - return argType == Type.NUMBER || argType == Type.STRING; - } - - public final boolean isNumeric() { - return argType == Type.NUMBER || argType == Type.ARRAY_OF_NUMBERS; - } - - private void setValue(String val) { - if (isArray()) { - arrayVal = ArgumentParser.this.lex(val); - } else { - singleVal = val; - } - } - - private void setValue(List val) { - arrayVal = new ArrayList<>(val); - } - - public boolean modelEquals(Argument obj) { - if (this.shortArg != null) { - return this.shortArg.equals(obj.shortArg); - } else if (this.longArg != null) { - return this.longArg.equals(obj.longArg); - } else { - return obj.shortArg == null && obj.longArg == null; - } - } - - @Override - public String toString() { - StringBuilder b = new StringBuilder(); - if (longArg != null && shortArg != null) { - b.append("--").append(longArg).append("/").append("-").append(shortArg); - } else if (longArg != null) { - b.append("--").append(longArg); - } else if (shortArg != null) { - b.append("-").append(shortArg); - } - b.append(": "); - if (isSingle()) { - b.append(singleVal); - } else if (isArray()) { - boolean first = true; - b.append("["); - for (String s : arrayVal) { - if (!first) { - b.append(", "); - } - first = false; - b.append("\"").append(s.replaceAll("\"", "\\\"")).append("\""); - } - b.append("]"); - } - b.append("\n"); - return b.toString(); - } - - private String generateDescription(boolean shortCode) { - StringBuilder b = new StringBuilder(); - b.append("\t"); - if (shortArg == null && longArg == null) { - //Default argument - b.append("<").append(usageName).append(">: ").append(description).append("\n"); - } else { - //If short code is false, we need to check to see if there is a short code, if so, - //this is an alias. - if (shortCode) { - b.append("-").append(shortArg); - } else { - b.append("--").append(longArg); - } - b.append(": "); - - if (!shortCode && shortArg != null) { - //Alias - b.append("Alias to -").append(shortArg); - } else { - if (argType != Type.BOOLEAN) { - if (required) { - b.append("Required. "); - } else { - b.append("Optional. "); - } - } - if (argType == Type.NUMBER) { - b.append("A numeric value. "); - } - if (argType == Type.ARRAY_OF_NUMBERS) { - b.append("A list of numbers. "); - } - if (argType == Type.ARRAY_OF_STRINGS) { - b.append("A list. "); - } - b.append(description.replaceAll("\n", "\n\t\t")); - } - b.append("\n"); - } - return b.toString(); - } - } - - private ArgumentParser() { - } - - public static class ValidationException extends Exception { - - private ValidationException(String string) { - super(string); - } - } - - public static class ResultUseException extends RuntimeException { - - ResultUseException(String string) { - super(string); - } - } - - public class ArgumentParserResults { - - List arguments = new ArrayList(); - - private void updateArgument(Argument a) { - if (a == null) { - return; - } - List toRemove = new ArrayList(); - for (Argument arg : arguments) { - if (arg.modelEquals(a)) { - toRemove.add(arg); - } - } - for (Argument arg : toRemove) { - arguments.remove(arg); - } - arguments.add(a); - } - - /** - * Returns true if the flag represented by this short code is set. - * - * @param flag - * @return - */ - public boolean isFlagSet(Character flag) { - return getArg(flag) != null; - } - - /** - * Returns true is the flag represented by this long code is set. - * - * @param flag - * @return - */ - public boolean isFlagSet(String flag) { - return getArg(flag) != null; - } - - /** - * Gets the unassociated arguments passed in as a String. For instance, - * if the arguments were - * These are arguments, then "These are arguments" will be - * returned. However, assuming -c is registered as a single string type, - * and the arguments are - * -c These are arguments, then only "are arguments" is - * returned. This will return an empty string if no arguments were set. - * - * @return - */ - public String getStringArgument() { - try { - Argument a = getArg(); - if (a.arrayVal == null) { - return ""; - } - StringBuilder b = new StringBuilder(); - boolean first = true; - for (String val : a.arrayVal) { - if (!first) { - b.append(" "); - } - first = false; - b.append(val); - } - return b.toString(); - } - catch (ResultUseException e) { - return ""; - } - } - - /** - * Returns the string associated with the switch represented by this - * short code. If the switch wasn't set, null is returned. - * - * @param flag - * @return - * @throws ArgumentParser.ResultUseException - */ - public String getStringArgument(Character flag) throws ResultUseException { - return getStringArgument(getArg(flag)); - } - - /** - * Returns the string associated with the switch represented by this - * long code. If the switch wasn't set, null is returned. - * - * @param flag - * @return - * @throws ArgumentParser.ResultUseException - */ - public String getStringArgument(String flag) throws ResultUseException { - return getStringArgument(getArg(flag)); - } - - private String getStringArgument(Argument arg) { - if (arg == null) { - return null; - } - if (arg.argType != Type.STRING) { - throw new ClassCastException("Argument type not set to " + Type.STRING.name() + ". Cannot return a " + "string" + "."); - } - return arg.singleVal; - } - - /** - * Returns the value associated with the switch represented by this - * short code, pre-parsed as a double. If the switch wasn't set, null is - * returned. - * - * @param flag - * @return - * @throws ResultUseException, NumberFormatException - */ - public Double getNumberArgument(Character flag) throws ResultUseException { - return getNumberArgument(getArg(flag)); - } - - /** - * Returns the value associated with the switch represented by this long - * code, pre-parsed as a double. If the switch wasn't set, null is - * returned. - * - * @param flag - * @return - * @throws ResultUseException, NumberFormatException - */ - public Double getNumberArgument(String flag) throws ResultUseException { - return getNumberArgument(getArg(flag)); - } - - private Double getNumberArgument(Argument arg) { - if (arg == null) { - return null; - } - if (arg.argType != Type.NUMBER) { - throw new ClassCastException("Argument type not set to " + Type.NUMBER.name() + ". Cannot return a " + "number" + "."); - } - return Double.parseDouble(arg.singleVal); - } - - /** - * Gets the unassociated arguments passed in as a List of Strings. For - * instance, if the arguments were - * These are arguments, then ["These", "are", "arguments"] - * will be returned. However, assuming -c is registered as a single - * string type, and the arguments are - * -c These are arguments, then only ["are", "arguments"] - * is returned. This will return an empty array if no arguments were - * set. - * - * @return - */ - public List getStringListArgument() { - try { - Argument a = getArg(); - if (a.arrayVal == null) { - return new ArrayList(); - } - return new ArrayList(a.arrayVal); - } - catch (ResultUseException e) { - return new ArrayList(); - } - } - - /** - * Returns the list of values associated with the switch represented by - * this short code. If the switch wasn't set, null is returned. - * - * @param flag - * @return - * @throws ArgumentParser.ResultUseException - */ - public List getStringListArgument(Character flag) throws ResultUseException { - return getStringListArgument(getArg(flag)); - } - - /** - * Returns the list of values associated with the switch represented by - * this long code. If the switch wasn't set, null is returned. - * - * @param flag - * @return - * @throws ArgumentParser.ResultUseException - */ - public List getStringListArgument(String flag) throws ResultUseException { - return getStringListArgument(getArg(flag)); - } - - private List getStringListArgument(Argument arg) { - if (arg == null) { - return null; - } - if (arg.argType != Type.ARRAY_OF_STRINGS) { - throw new ClassCastException("Argument type not set to " + Type.ARRAY_OF_STRINGS.name() + ". Cannot return a " + "string list" + "."); - } - return new ArrayList(arg.arrayVal); - } - - /** - * Returns the list of values associated with the switch represented by - * this short code, pre-parsed into doubles. If the switch wasn't set, - * null is returned. - * - * @param flag - * @return - * @throws ArgumentParser.ResultUseException - */ - public List getNumberListArgument(Character flag) throws ResultUseException { - return getNumberListArgument(getArg(flag)); - } - - /** - * Returns the list of values associated with the switch represented by - * this long code, pre-parsed into doubles. If the switch wasn't set, - * null is returned. - * - * @param flag - * @return - * @throws ArgumentParser.ResultUseException - */ - public List getNumberListArgument(String flag) throws ResultUseException { - return getNumberListArgument(getArg(flag)); - } - - private List getNumberListArgument(Argument arg) { - if (arg == null) { - return null; - } - if (arg.argType != Type.ARRAY_OF_NUMBERS) { - throw new ClassCastException("Argument type not set to " + Type.ARRAY_OF_NUMBERS.name() + ". Cannot return a " + "number list" + "."); - } - - List list = new ArrayList(); - for (String s : arg.arrayVal) { - list.add(Double.parseDouble(s)); - } - return list; - } - - private Argument getArg() { - for (Argument a : arguments) { - if (a.shortArg == null && a.longArg == null) { - return a; - } - } - return new Argument(ArgumentParser.this.getArgument()); - } - - private Argument getArg(Character flag) throws ResultUseException { - for (Argument a : arguments) { - if (a.shortArg == null) { - continue; - } - if (a.shortArg.equals(flag)) { - return a; - } - } - return null; - } - - private Argument getArg(String flag) throws ResultUseException { - for (Argument a : arguments) { - if (a.longArg == null) { - continue; - } - if (a.longArg.equals(flag)) { - return a; - } - } - return null; - } - - @Override - public String toString() { - StringBuilder b = new StringBuilder(); - for (Argument arg : arguments) { - if (arg.isFlag()) { - b.append("Flag "); - if (arg.longArg != null && arg.shortArg != null) { - b.append("--").append(arg.longArg).append("/").append("-").append(arg.shortArg); - } else if (arg.longArg != null) { - b.append("--").append(arg.longArg); - } else if (arg.shortArg != null) { - b.append("-").append(arg.shortArg); - } - b.append(" is set.\n"); - } else { - b.append(arg.toString()); - } - } - return b.toString(); - } - } - - public static ArgumentParser GetParser() { - return new ArgumentParser(); - } - - public static enum Type { - - STRING, - NUMBER, - ARRAY_OF_STRINGS, - ARRAY_OF_NUMBERS, - BOOLEAN - } - - private ArgumentParser addArgument0(Character shortArg, String longArg, Type argType, String defaultVal, String description, String usageName, boolean required) { - //TODO: Make sure this switch doesn't already exist - argumentModel.add(new Argument(shortArg, longArg, argType, defaultVal, description, usageName, required)); - return this; - } - - /** - * Adds an argument to this argument parser. This is the most complex method - * of adding an argument, all other methods are wrappers around this. - * - * The short code and long code for a switch both represent the underlying - * switch, that is, they both "addresses" of a single underlying switch. - * When accessing the argument later, you may use either the short code or - * the long code to retrieve the value of the switch, but it is important to - * understand that they are both pointing to the same item. - * - * @param shortArg The short code for this switch. - * @param longArg The long code for this switch. - * @param argType The expected type of this switch. - * @param defaultVal The default value of this switch. If defaultVal is not - * null, the switch will always exist when calling get*Argument from the - * results. If argType is BOOLEAN, setting this will cause the switch to - * @param description The description of this argument, which is used when - * building the help text created by getBuiltDescription. - * @return - */ - public ArgumentParser addArgument(Character shortArg, String longArg, Type argType, String defaultVal, String description, String usageName, boolean required) { - if (argType == Type.BOOLEAN) { - throw new IllegalArgumentException("Cannot use addArgument to add a flag. Use addFlag instead."); - } - if (shortArg == null && longArg == null) { - if (argType != Type.STRING && argType != Type.ARRAY_OF_STRINGS) { - throw new IllegalArgumentException("Cannot set the type of the default switch to anything but " + Type.STRING.name() + " or " - + Type.ARRAY_OF_STRINGS.name()); - } - } - return addArgument0(shortArg, longArg, argType, defaultVal, description, usageName, required); - } - - /** - * Sets the default switch's arg type, default value, and description. The - * default switch is the switch that is associated with "loose" arguments, - * for instance, - * these are args would all be loose arguments, because they - * aren't associated with any explicit switches. Note that there is no Type - * specified here, that's because the arguments can be grabbed as either an - * array of strings or a string. - * - * @param argType - * @param defaultVal - * @param description - * @return - */ - public ArgumentParser addArgument(String defaultVal, String description, String usageName, boolean required) { - return addArgument(null, null, Type.ARRAY_OF_STRINGS, defaultVal, description, usageName, required); - } - - /** - * Sets the default switch with no default value. - * - * @param argType - * @param description - * @return - */ - public ArgumentParser addArgument(String description, String usageName, boolean required) { - return addArgument(null, null, Type.ARRAY_OF_STRINGS, null, description, usageName, required); - } - - /** - * Adds a new argument with no default value. - * - * @param shortArg - * @param longArg - * @param argType - * @param description - * @return - */ - public ArgumentParser addArgument(Character shortArg, String longArg, Type argType, String description, String usageName, boolean required) { - return addArgument(shortArg, longArg, argType, null, description, usageName, required); - } - - /** - * Adds a new argument with no long code. - * - * @param shortArg - * @param argType - * @param defaultVal - * @param description - * @return - */ - public ArgumentParser addArgument(Character shortArg, Type argType, String defaultVal, String description, String usageName, boolean required) { - return addArgument(shortArg, null, argType, defaultVal, description, usageName, required); - } - - /** - * Adds a new argument with no short code. - * - * @param longArg - * @param argType - * @param defaultVal - * @param description - * @return - */ - public ArgumentParser addArgument(String longArg, Type argType, String defaultVal, String description, String usageName, boolean required) { - return addArgument(null, longArg, argType, defaultVal, description, usageName, required); - } - - /** - * Adds a new argument with no long code, and no default value. - * - * @param shortArg - * @param argType - * @param description - * @return - */ - public ArgumentParser addArgument(Character shortArg, Type argType, String description, String usageName, boolean required) { - return addArgument(shortArg, null, argType, null, description, usageName, required); - } - - /** - * Adds a new argument with no short code, and no default value. - * - * @param longArg - * @param argType - * @param description - * @return - */ - public ArgumentParser addArgument(String longArg, Type argType, String description, String usageName, boolean required) { - return addArgument(null, longArg, argType, null, description, usageName, required); - } - - /** - * Adds a new flag. - * - * @param shortArg - * @param longArg - * @param description - * @return - */ - public ArgumentParser addFlag(Character shortArg, String longArg, String description) { - return addArgument0(shortArg, longArg, Type.BOOLEAN, null, description, null, false); - } - - /** - * Adds a new flag with no short code. - * - * @param longArg - * @param description - * @return - */ - public ArgumentParser addFlag(String longArg, String description) { - return addArgument0(null, longArg, Type.BOOLEAN, null, description, null, false); - } - - /** - * Adds a new flag with no long code. - * - * @param shortArg - * @param description - * @return - */ - public ArgumentParser addFlag(Character shortArg, String description) { - return addArgument0(shortArg, null, Type.BOOLEAN, null, description, null, false); - } - - /** - * Adds a description to the ArgumentParser object, which is used when - * building the description returned by getBuiltDescription. - * - * @param description - * @return - */ - public ArgumentParser addDescription(String description) { - this.description = description; - return this; - } - - /** - * Builds a description of this ArgumentParser, which may be useful as - * "Usage" text to provide to a user if a call to match throws a - * ValidationException, for instance. - * - * @return - */ - public String getBuiltDescription() { - StringBuilder b = new StringBuilder(); - //Now, we need to go through and get all the switch names in alphabetical - //order. - b.append("\t").append(this.description).append("\n\n"); - List shortCodes = new ArrayList(); - List longCodes = new ArrayList(); - List shortCodesDone = new ArrayList(); - List longCodesDone = new ArrayList(); - List aliases = new ArrayList(); - for (Argument arg : argumentModel) { - if (arg.shortArg != null) { - shortCodes.add(arg.shortArg); - } - if (arg.longArg != null) { - longCodes.add(arg.longArg); - } - if (arg.shortArg != null && arg.longArg != null) { - aliases.add(arg.longArg); - } - } - Collections.sort(shortCodes); - Collections.sort(longCodes); - //Go through the flags first - boolean hasShortCodeFlags = false; - StringBuilder flags = new StringBuilder(); - List shortFlags = new ArrayList(); - List longFlags = new ArrayList(); - List shortArguments = new ArrayList(); - List longArguments = new ArrayList(); - for (Character c : shortCodes) { - Argument a = getArgument(c); - if (a.isFlag()) { - shortCodesDone.add(c); - flags.append(a.generateDescription(true)); - hasShortCodeFlags = true; - shortFlags.add(c); - } else { - shortArguments.add(c); - } - } - - for (String s : longCodes) { - Argument a = getArgument(s); - if (a.isFlag()) { - longCodesDone.add(s); - flags.append(a.generateDescription(false)); - if (!aliases.contains(s)) { - longFlags.add(s); - } - } else if (!aliases.contains(s)) { - longArguments.add(s); - } - } - - b.append("Usage:\n\t"); - //Get the short flags first, then the long flags, then the short arguments, then the long arguments - List parts = new ArrayList(); - if (!shortFlags.isEmpty()) { - StringBuilder usage = new StringBuilder(); - usage.append("[-"); - for (Character c : shortFlags) { - usage.append(c); - } - usage.append("]"); - parts.add(usage.toString()); - } - - for (String s : longFlags) { - parts.add("[--" + s + "]"); - } - - List usageList = new ArrayList(); - for (Character c : shortArguments) { - usageList.add(getArgument(c)); - } - for (String s : longArguments) { - usageList.add(getArgument(s)); - } - for (Argument a : usageList) { - StringBuilder usage = new StringBuilder(); - if (!a.required) { - usage.append("["); - } - if (a.shortArg != null) { - usage.append("-").append(a.shortArg); - } else { - usage.append("--").append(a.longArg); - } - usage.append(" <"); - if (a.isNumeric()) { - usage.append("#"); - } - usage.append(a.usageName); - if (a.isArray()) { - usage.append(", ..."); - } - usage.append(">"); - - if(a.defaultVal != null && !"".equals(a.defaultVal)){ +public final class ArgumentParser { + + /** + * Eases the operation of building a single argument. A new instance of this should be constructed, and the IDE + * will guide you through selecting the rest of the elements. Using this builder, it is not possible to create + * an inconsistent Argument. + */ + public static class ArgumentBuilder { + + enum Mode { SHORT, LONG, BOTH, DEFAULT } + Mode mode; + + char shortArg; + String longArg; + + String description; + String usageName; + boolean required; + + Type argType = Type.ARRAY_OF_STRINGS; + String defaultValue; + + /** + * Sets the description for this argument. For consistency, it is arbitrarily decided that these descriptions + * should end in a period. + * @param description The description + * @return The next step in the build process + */ + public ArgumentBuilderRequired2 setDescription(String description) { + Objects.requireNonNull(description, "description may not be null"); + ArgumentBuilder.this.description = description; + return new ArgumentBuilderRequired2(); + } + + public class ArgumentBuilderRequired2 { + ArgumentBuilderRequired2() {} + + /** + * Sets the usage name for this argument. Typically, this is just the name + * of the long argument, or a very short phrase describing the value, + * but is only used for informational purposes, so + * is ok to be repeated across arguments. + *

+ * During help generation, this argument is surrounded by <> to indicate that it + * is an argument, so there is no need to wrap the value in symbols. + *

+ * The documentation will look something like this, assuming the argument is named "path" + * and the usageName is "path to file": + *

+			 *	--path <path to file>
+			 * 
+ * + * If the type of the argument is an array, {@code , ...} is appended, and if it is numeric, {@code #} + * is prepended. + *

+ * Flags do not have a parameter, so no usage name is needed. + * + * @param usageName The display name in the help docs. + * @return The next step in the build process. + */ + public ArgumentBuilderRequired3 setUsageName(String usageName) { + Objects.requireNonNull(usageName, "usageName may not be null"); + ArgumentBuilder.this.usageName = usageName; + return new ArgumentBuilderRequired3(); + } + + /** + * Treat this argument as a flag. A flag is a value, which is true if present, and false if not present. + * It takes no arguments, and cannot be the default argument. + * @return The next step in the build process. + */ + public FlagBuilderMode asFlag() { + return new FlagBuilderMode(); + } + } + + public class ArgumentBuilderRequired3 { + ArgumentBuilderRequired3() {} + + /** + * Configures this as a required argument. If the argument is missing, then a {@link ValidationException} + * will be thrown during parsing. + * @return The next step in the build process. + */ + public RequiredArgumentBuilderMode setRequired() { + ArgumentBuilder.this.required = true; + return new RequiredArgumentBuilderMode(); + } + + /** + * Shortcut to setting this argument as required and as the default argument. + * @return The final step in the build process. + */ + public RequiredArgumentBuilderOptional setRequiredAndDefault() { + return setRequired().asDefault(); + } + + /** + * Configures this as an optional argument. The build + * process will allow you to set a default value if + * desired. + * @return The next step in the build process. + */ + public OptionalArgumentBuilderMode setOptional() { + ArgumentBuilder.this.required = false; + return new OptionalArgumentBuilderMode(); + } + + /** + * Shortcut to setting this argument as optional and as the default argument. + * @return The final step in the build process. + */ + public OptionalArgumentBuilderOptional setOptionalAndDefault() { + return setOptional().asDefault(); + } + + } + + public class FlagBuilderMode { + /** + * Creates an argument that is a flag. That is, this is a boolean true if present, + * boolean false if not. There are no arguments attached to this value. + * @param shortArg The short code for the argument + * @return The next step in the build process. + */ + public ArgumentBuilderFlag setName(char shortArg) { + new OptionalArgumentBuilderMode().setName(shortArg); + argType = Type.BOOLEAN; + return new ArgumentBuilderFlag(); + } + + /** + * Creates an argument that is a flag. That is, this is a boolean true if present, + * boolean false if not. There are no arguments attached to this value. + * @param longArg The long code for the argument + * @return The next step in the build process. + */ + public ArgumentBuilderFlag setName(String longArg) { + new OptionalArgumentBuilderMode().setName(longArg); + argType = Type.BOOLEAN; + return new ArgumentBuilderFlag(); + } + + /** + * Creates an argument that is a flag. That is, this is a boolean true if present, + * boolean false if not. There are no arguments attached to this value. + * @param shortArg The short code for the argument + * @param longArg The long code for the argument + * @return The next step in the build process. + */ + public ArgumentBuilderFlag setName(char shortArg, String longArg) { + new OptionalArgumentBuilderMode().setName(shortArg, longArg); + argType = Type.BOOLEAN; + return new ArgumentBuilderFlag(); + } + } + + public class OptionalArgumentBuilderMode { + /** + * Sets the name of this argument, using a short code. The value passed in must + * be a printable character (as defined by {@link Character#isAlphabetic(int)}) + * and cannot be \0. + * + * In this mode, the argument may only be addressed via the short arg. + * + * This does not set a flag, this sets an argument with input. + * @param shortArg The short arg + * @return The next step in the build process + */ + public OptionalArgumentBuilderOptional setName(char shortArg) { + if(shortArg == '\0') { + throw new NullPointerException("shortArg may not be the null character"); + } + + if(!Character.isAlphabetic(shortArg)) { + throw new IllegalArgumentException("shortArg must be a alphabetical character"); + } + + mode = Mode.SHORT; + ArgumentBuilder.this.shortArg = shortArg; + return new OptionalArgumentBuilderOptional(); + } + + /** + * Sets the name of this argument, using a long code. The value passed in may not + * be null. + * + * In this mode, the argument may only be addressed via the long arg. + * + * This does not set a flag, this sets an argument with input. + * + * @param longArg The long arg + * @return The next step in the build process + */ + public OptionalArgumentBuilderOptional setName(String longArg) { + Objects.requireNonNull(longArg, "longArg must not be null"); + + mode = Mode.LONG; + ArgumentBuilder.this.longArg = longArg; + return new OptionalArgumentBuilderOptional(); + } + + /** + * Sets the name of this argument, using a short code and a long code. + * The short arg passed in must + * be a printable character (as defined by {@link Character#isAlphabetic(int)}) + * and cannot be \0. + * + * The long arg passed in may not be null. + * + * @param shortArg The single character address for this argument + * @param longArg The long name of this argument + * @return The next step in the build process. + */ + public OptionalArgumentBuilderOptional setName(char shortArg, String longArg) { + setName(shortArg); + setName(longArg); + + // reset mode + mode = Mode.BOTH; + return new OptionalArgumentBuilderOptional(); + } + + /** + * Considers this argument to be a default argument, that is, this contains + * the parameters passed in without parameter names. There may only be one of + * these arguments in the list. If a second one is attempted to be added, an + * exception is thrown. + * @return The next step in the build process. + */ + public OptionalArgumentBuilderOptional asDefault() { + mode = Mode.DEFAULT; + return new OptionalArgumentBuilderOptional(); + } + } + + public class RequiredArgumentBuilderMode { + /** + * Sets the name of this argument, using a short code. The value passed in must + * be a printable character (as defined by {@link Character#isAlphabetic(int)}) + * and cannot be \0. + * + * In this mode, the argument may only be addressed via the short arg. + * + * This does not set a flag, this sets an argument with input. + * @param shortArg The short arg + * @return The next step in the build process + */ + public RequiredArgumentBuilderOptional setName(char shortArg) { + if(shortArg == '\0') { + throw new NullPointerException("shortArg may not be the null character"); + } + + if(!Character.isAlphabetic(shortArg)) { + throw new IllegalArgumentException("shortArg must be a alphabetical character"); + } + + mode = Mode.SHORT; + ArgumentBuilder.this.shortArg = shortArg; + return new RequiredArgumentBuilderOptional(); + } + + /** + * Sets the name of this argument, using a long code. The value passed in may not + * be null. + * + * In this mode, the argument may only be addressed via the long arg. + * + * This does not set a flag, this sets an argument with input. + * + * @param longArg The long arg + * @return The next step in the build process + */ + public RequiredArgumentBuilderOptional setName(String longArg) { + Objects.requireNonNull(longArg, "longArg must not be null"); + + mode = Mode.LONG; + ArgumentBuilder.this.longArg = longArg; + return new RequiredArgumentBuilderOptional(); + } + + /** + * Sets the name of this argument, using a short code and a long code. + * The short arg passed in must + * be a printable character (as defined by {@link Character#isAlphabetic(int)}) + * and cannot be \0. + * + * The long arg passed in may not be null. + * + * @param shortArg The single character address for this argument + * @param longArg The long name of this argument + * @return The next step in the build process. + */ + public RequiredArgumentBuilderOptional setName(char shortArg, String longArg) { + setName(shortArg); + setName(longArg); + + // reset mode + mode = Mode.BOTH; + return new RequiredArgumentBuilderOptional(); + } + + /** + * Considers this argument to be a default argument, that is, this contains + * the parameters passed in without parameter names. There may only be one of + * these arguments in the list. If a second one is attempted to be added, an + * exception is thrown. + * @return The next step in the build process. + */ + public RequiredArgumentBuilderOptional asDefault() { + mode = Mode.DEFAULT; + return new RequiredArgumentBuilderOptional(); + } + } + + /** + * A subset of types that are only valid for non-flag types. + */ + public static enum BuilderTypeNonFlag { + /** + * A single string value + */ + STRING(Type.STRING), + /** + * An array of strings + */ + ARRAY_OF_STRINGS(Type.ARRAY_OF_STRINGS), + /** + * A numeric value + */ + NUMBER(Type.NUMBER), + /** + * An array of numeric values + */ + ARRAY_OF_NUMBERS(Type.ARRAY_OF_NUMBERS); + + Type type; + private BuilderTypeNonFlag(Type type) { + this.type = type; + } + + Type getType() { + return this.type; + } + } + + /** + * Represents an object that is ready to be built. While user code cannot + * build the Argument directly, this is used internally. + */ + public abstract class ArgumentBuilderFinal { + private ArgumentBuilderFinal() {} + + /** + * After building, this Argument should be in a consistent state. + * @return + */ + abstract Argument build(); + } + + public final class ArgumentBuilderFlag extends ArgumentBuilderFinal { + private ArgumentBuilderFlag() {} + + @Override + Argument build() { + return new OptionalArgumentBuilderOptional().build(); + } + } + + public final class OptionalArgumentBuilderOptional extends ArgumentBuilderFinal { + OptionalArgumentBuilderOptional() {} + + /** + * Sets the argument type. By default, ARRAY_OF_STRINGS is assumed. + * @param argType The type of this argument + * @return {@code this}, which can be called repeatedly to set the + * optional arguments, or used as is. + */ + public OptionalArgumentBuilderOptional setArgType(BuilderTypeNonFlag argType) { + Objects.requireNonNull(argType, "argType cannot be null"); + ArgumentBuilder.this.argType = argType.getType(); + return this; + } + + /** + * For arguments that are not required, the default value may be set. + * If this argument is required, calling this method is an error. + * @param defaultVal + * @return {@code this}, which can be called repeatedly to set the + * optional arguments, or used as is. + */ + public OptionalArgumentBuilderOptional setDefaultVal(String defaultVal) { + if(required) { + throw new IllegalArgumentException("Required arguments cannot have a default value provided"); + } + ArgumentBuilder.this.defaultValue = defaultVal; + return this; + } + + @Override + Argument build() { + return new Argument(shortArg == '\0' ? null : shortArg, longArg, argType, defaultValue, description, + usageName, required); + } + + } + + public final class RequiredArgumentBuilderOptional extends ArgumentBuilderFinal { + RequiredArgumentBuilderOptional() {} + + /** + * Sets the argument type. By default, ARRAY_OF_STRINGS is assumed. + * @param argType The type of this argument + * @return {@code this}, which can be called repeatedly to set the + * optional arguments, or used as is. + */ + public RequiredArgumentBuilderOptional setArgType(BuilderTypeNonFlag argType) { + Objects.requireNonNull(argType, "argType cannot be null"); + ArgumentBuilder.this.argType = argType.getType(); + return this; + } + + @Override + Argument build() { + return new OptionalArgumentBuilderOptional().build(); + } + + } + + } + + /** + * A description of the command itself. + */ + String description = ""; + String extendedDescription = ""; + /** + * The model for the arguments + */ + List argumentModel = new ArrayList<>(); + /** + * Whether to throw an error if unrecognized arguments were provided + */ + boolean errorOnUnknown = true; + + /** + * Returns the default argument, if it exists. + * + * @return + */ + private Argument getArgument() { + for(Argument a : argumentModel) { + if(a.shortArg == null && a.longArg == null) { + return a; + } + } + return null; + } + + private Argument getArgument(Character c) { + for(Argument a : argumentModel) { + if(a.shortArg == null) { + continue; + } + if(a.shortArg.equals(c)) { + return a; + } + } + return null; + } + + private Argument getArgument(String s) { + for(Argument a : argumentModel) { + if(a.longArg == null) { + continue; + } + if(a.longArg.equals(s)) { + return a; + } + } + return null; + } + + private static final class Argument { + + Character shortArg; + String longArg; + Type argType; + String defaultVal; + List defaultList; + String description; + String usageName; + boolean required; + String singleVal; + List arrayVal; + + private Argument(Argument arg) { + if(arg == null) { + return; + } + this.shortArg = arg.shortArg; + this.longArg = arg.longArg; + this.argType = arg.argType; + this.defaultVal = arg.defaultVal; + this.description = arg.description; + this.usageName = arg.usageName; + this.required = arg.required; + } + + private Argument(Character shortArg, String longArg, Type argType, String defaultVal, String description, String usageName, boolean required) { + this.shortArg = shortArg; + this.longArg = longArg; + this.argType = argType; + this.description = description; + + this.defaultVal = defaultVal; + if(isArray() && defaultVal != null) { + defaultList = ArgumentParser.lex(defaultVal); + } + this.usageName = usageName; + this.required = required; + } + + public final boolean isFlag() { + return argType == Type.BOOLEAN; + } + + public final boolean isArray() { + return argType == Type.ARRAY_OF_NUMBERS || argType == Type.ARRAY_OF_STRINGS; + } + + public final boolean isSingle() { + return argType == Type.NUMBER || argType == Type.STRING; + } + + public final boolean isNumeric() { + return argType == Type.NUMBER || argType == Type.ARRAY_OF_NUMBERS; + } + + private void setValue(String val) { + if(isArray()) { + arrayVal = ArgumentParser.lex(val); + } else { + singleVal = val; + } + } + + private void setValue(List val) { + arrayVal = new ArrayList<>(val); + } + + public boolean modelEquals(Argument obj) { + if(this.shortArg != null) { + return this.shortArg.equals(obj.shortArg); + } else if(this.longArg != null) { + return this.longArg.equals(obj.longArg); + } else { + return obj.shortArg == null && obj.longArg == null; + } + } + + @Override + public String toString() { + StringBuilder b = new StringBuilder(); + if(longArg != null && shortArg != null) { + b.append("--").append(longArg).append("/").append("-").append(shortArg); + } else if(longArg != null) { + b.append("--").append(longArg); + } else if(shortArg != null) { + b.append("-").append(shortArg); + } + b.append(": "); + if(isSingle()) { + b.append(singleVal); + } else if(isArray()) { + boolean first = true; + b.append("["); + for(String s : arrayVal) { + if(!first) { + b.append(", "); + } + first = false; + b.append("\"").append(s.replace("\"", "\\\"")).append("\""); + } + b.append("]"); + } + b.append("\n"); + return b.toString(); + } + + private String generateDescription(boolean shortCode) { + StringBuilder b = new StringBuilder(); + b.append("\t"); + if(shortArg == null && longArg == null) { + //Default argument + b.append("<").append(usageName).append(">: ").append(description).append("\n"); + } else { + //If short code is false, we need to check to see if there is a short code, if so, + //this is an alias. + if(shortCode) { + b.append(TermColors.GREEN).append("-").append(shortArg).append(TermColors.RESET); + } else { + b.append(TermColors.GREEN).append("--").append(longArg).append(TermColors.RESET); + } + b.append(": "); + + if(!shortCode && shortArg != null) { + //Alias + b.append("Alias to -").append(shortArg); + } else { + if(argType != Type.BOOLEAN) { + if(required) { + b.append("Required. "); + } else { + b.append("Optional. "); + } + } + if(argType == Type.NUMBER) { + b.append("A numeric value. "); + } + if(argType == Type.ARRAY_OF_NUMBERS) { + b.append("A list of numbers. "); + } + if(argType == Type.ARRAY_OF_STRINGS) { + b.append("A list. "); + } + b.append(description.replace("\n", "\n\t\t")); + } + b.append("\n"); + } + return b.toString(); + } + } + + private ArgumentParser() { + } + + public static final class ValidationException extends Exception { + + private ValidationException(String string) { + super(string); + } + } + + public static class ResultUseException extends RuntimeException { + + ResultUseException(String string) { + super(string); + } + } + + public final class ArgumentParserResults { + + List arguments = new ArrayList<>(); + List unclassified = new ArrayList<>(); + List rawArgs; + + private ArgumentParserResults(List rawArgs) { + this.rawArgs = new ArrayList<>(rawArgs); + } + + private void updateArgument(Argument a) { + if(a == null) { + return; + } + List toRemove = new ArrayList<>(); + for(Argument arg : arguments) { + if(arg.modelEquals(a)) { + toRemove.add(arg); + } + } + for(Argument arg : toRemove) { + arguments.remove(arg); + } + arguments.add(a); + } + + private void updateUnclassifiedArgument(String a) { + unclassified.add(a); + } + + /** + * Returns true if the flag represented by this short code is set. + * + * @param flag + * @return + */ + public boolean isFlagSet(Character flag) { + return getArg(flag) != null; + } + + /** + * Returns true is the flag represented by this long code is set. + * + * @param flag + * @return + */ + public boolean isFlagSet(String flag) { + return getArg(flag) != null; + } + + /** + * Gets the unassociated arguments passed in as a String. For instance, + * if the arguments were These are arguments, then "These + * are arguments" will be returned. However, assuming -c is registered + * as a single string type, and the arguments are + * -c These are arguments, then only "are arguments" is + * returned. This will return an empty string if no arguments were set. + * + * @return + */ + public String getStringArgument() { + try { + Argument a = getArg(); + if(a.arrayVal == null) { + return ""; + } + StringBuilder b = new StringBuilder(); + boolean first = true; + for(String val : a.arrayVal) { + if(!first) { + b.append(" "); + } + first = false; + b.append(val); + } + return b.toString(); + } catch (ResultUseException e) { + return ""; + } + } + + /** + * Returns the string associated with the switch represented by this + * short code. If the switch wasn't set, null is returned. + * + * @param flag + * @return + * @throws ArgumentParser.ResultUseException + */ + public String getStringArgument(Character flag) throws ResultUseException { + return getStringArgument(getArg(flag)); + } + + /** + * Returns the string associated with the switch represented by this + * long code. If the switch wasn't set, null is returned. + * + * @param flag + * @return + * @throws ArgumentParser.ResultUseException + */ + public String getStringArgument(String flag) throws ResultUseException { + return getStringArgument(getArg(flag)); + } + + private String getStringArgument(Argument arg) { + if(arg == null) { + return null; + } + if(arg.argType != Type.STRING) { + throw new ClassCastException("Argument type not set to " + Type.STRING.name() + ". Cannot return a " + "string" + "."); + } + return arg.singleVal; + } + + /** + * Returns the value associated with the switch represented by this + * short code, pre-parsed as a double. If the switch wasn't set, null is + * returned. + * + * @param flag + * @return + * @throws ResultUseException, NumberFormatException + */ + public Double getNumberArgument(Character flag) throws ResultUseException { + return getNumberArgument(getArg(flag)); + } + + /** + * Returns the value associated with the switch represented by this long + * code, pre-parsed as a double. If the switch wasn't set, null is + * returned. + * + * @param flag + * @return + * @throws ResultUseException, NumberFormatException + */ + public Double getNumberArgument(String flag) throws ResultUseException { + return getNumberArgument(getArg(flag)); + } + + private Double getNumberArgument(Argument arg) { + if(arg == null) { + return null; + } + if(arg.argType != Type.NUMBER) { + throw new ClassCastException("Argument type not set to " + Type.NUMBER.name() + ". Cannot return a " + "number" + "."); + } + return Double.parseDouble(arg.singleVal); + } + + /** + * Gets the unassociated arguments passed in as a List of Strings. For + * instance, if the arguments were These are arguments, + * then ["These", "are", "arguments"] will be returned. However, + * assuming -c is registered as a single string type, and the arguments + * are -c These are arguments, then only ["are", + * "arguments"] is returned. This will return an empty array if no + * arguments were set. + * + * @return + */ + public List getStringListArgument() { + try { + Argument a = getArg(); + if(a.arrayVal == null) { + return new ArrayList<>(); + } + return new ArrayList<>(a.arrayVal); + } catch (ResultUseException e) { + return new ArrayList<>(); + } + } + + /** + * Returns the list of values associated with the switch represented by + * this short code. If the switch wasn't set, null is returned. + * + * @param flag + * @return + * @throws ArgumentParser.ResultUseException + */ + public List getStringListArgument(Character flag) throws ResultUseException { + return getStringListArgument(getArg(flag)); + } + + /** + * Returns the list of values associated with the switch represented by this short code. If the switch + * wasn't set, the default return value is returned instead. + * @param flag + * @param defaultReturn + * @return + * @throws com.laytonsmith.PureUtilities.ArgumentParser.ResultUseException + */ + public List getStringListArgument(Character flag, List defaultReturn) + throws ResultUseException { + List d = getStringListArgument(flag); + if(d == null) { + return defaultReturn; + } + return d; + } + + /** + * Returns the list of values associated with the switch represented by + * this long code. If the switch wasn't set, null is returned. + * + * @param flag + * @return + * @throws ArgumentParser.ResultUseException + */ + public List getStringListArgument(String flag) throws ResultUseException { + return getStringListArgument(getArg(flag)); + } + + /** + * Returns the list of values associated with the switch represented by this long code. If the switch + * wasn't set, the default return value is returned instead. + * @param flag + * @param defaultReturn + * @return + * @throws com.laytonsmith.PureUtilities.ArgumentParser.ResultUseException + */ + public List getStringListArgument(String flag, List defaultReturn) throws ResultUseException { + List d = getStringListArgument(flag); + if(d == null) { + return defaultReturn; + } + return d; + } + + private List getStringListArgument(Argument arg) { + if(arg == null) { + return null; + } + if(arg.argType != Type.ARRAY_OF_STRINGS) { + throw new ClassCastException("Argument type not set to " + Type.ARRAY_OF_STRINGS.name() + ". Cannot return a " + "string list" + "."); + } + return new ArrayList<>(arg.arrayVal); + } + + /** + * Returns the list of values associated with the switch represented by + * this short code, pre-parsed into doubles. If the switch wasn't set, + * null is returned. + * + * @param flag + * @return + * @throws ArgumentParser.ResultUseException + */ + public List getNumberListArgument(Character flag) throws ResultUseException { + return getNumberListArgument(getArg(flag)); + } + + /** + * Returns the list of values associated with the switch represented by + * this long code, pre-parsed into doubles. If the switch wasn't set, + * null is returned. + * + * @param flag + * @return + * @throws ArgumentParser.ResultUseException + */ + public List getNumberListArgument(String flag) throws ResultUseException { + return getNumberListArgument(getArg(flag)); + } + + private List getNumberListArgument(Argument arg) { + if(arg == null) { + return null; + } + if(arg.argType != Type.ARRAY_OF_NUMBERS) { + throw new ClassCastException("Argument type not set to " + Type.ARRAY_OF_NUMBERS.name() + ". Cannot return a " + "number list" + "."); + } + + List list = new ArrayList<>(); + for(String s : arg.arrayVal) { + list.add(Double.parseDouble(s)); + } + return list; + } + + private Argument getArg() { + for(Argument a : arguments) { + if(a.shortArg == null && a.longArg == null) { + return a; + } + } + return new Argument(ArgumentParser.this.getArgument()); + } + + private Argument getArg(Character flag) throws ResultUseException { + for(Argument a : arguments) { + if(a.shortArg == null) { + continue; + } + if(a.shortArg.equals(flag)) { + return a; + } + } + return null; + } + + private Argument getArg(String flag) throws ResultUseException { + for(Argument a : arguments) { + if(a.longArg == null) { + continue; + } + if(a.longArg.equals(flag)) { + return a; + } + } + return null; + } + + @Override + public String toString() { + StringBuilder b = new StringBuilder(); + for(Argument arg : arguments) { + if(arg.isFlag()) { + b.append("Flag "); + if(arg.longArg != null && arg.shortArg != null) { + b.append("--").append(arg.longArg).append("/").append("-").append(arg.shortArg); + } else if(arg.longArg != null) { + b.append("--").append(arg.longArg); + } else if(arg.shortArg != null) { + b.append("-").append(arg.shortArg); + } + b.append(" is set.\n"); + } else { + b.append(arg.toString()); + } + } + return b.toString(); + } + + /** + * Returns a list of the raw, unprocessed arguments. This includes all arguments as is, with no processing. + * @return + */ + public List getRawArguments() { + return new ArrayList<>(rawArgs); + } + } + + public static ArgumentParser GetParser() { + return new ArgumentParser(); + } + + static enum Type { + STRING, + NUMBER, + ARRAY_OF_STRINGS, + ARRAY_OF_NUMBERS, + BOOLEAN + } + + /** + * Adds the configured argument to the ArgumentParser. + *

+ * To build an Argument, create a new instance of {@link ArgumentBuilder}. + * @param arg The argument to add to the list + * @return {@code this}, for continued chaining + */ + public ArgumentParser addArgument(ArgumentBuilder.ArgumentBuilderFinal arg) { + Argument arg0 = arg.build(); + // Check to make sure this isn't a duplicate value + for(Argument a : argumentModel) { + // default arg + if(a.shortArg == null && a.longArg == null) { + // This is the default arg, ensure that arg is not the default + if(arg0.shortArg == null && arg0.longArg == null) { + throw new IllegalArgumentException("Only 1 default argument may be provided."); + } + } + if(a.shortArg != null && arg0.shortArg != null && a.shortArg.equals(arg0.shortArg)) { + throw new IllegalArgumentException("A new argument with short arg '" + arg0.shortArg + "' was provided," + + " but a previous argument with the same short arg was already provided."); + } + if(a.longArg != null && arg0.longArg != null && a.longArg.equals(arg0.longArg)) { + throw new IllegalArgumentException("A new argument with long arg \"" + arg0.longArg + "\" was provided," + + " but a previous argument with the same long arg was already provided."); + } + } + argumentModel.add(arg0); + return this; + } + + /** + * Adds a description to the ArgumentParser object, which is used when + * building the description returned by getBuiltDescription. For consistency, + * it is arbitrarily decided that these descriptions should end with a period. + * + * @param description + * @return + */ + public ArgumentParser addDescription(String description) { + this.description = description; + return this; + } + + /** + * Provides an extended description. This is not returned with {@link #getDescription()}, but is included + * as part of the built description. + * @param extendedDescription + * @return + */ + public ArgumentParser addExtendedDescription(String extendedDescription) { + this.extendedDescription = extendedDescription; + return this; + } + + /** + * If set to true (which is the default), then unknown options will cause an error. If false, + * they will not cause an error, and can even still be accessed in the results. However, this is + * only recommended for situations where arguments are processed in a later step as well, if all + * the possibly valid arguments are known up front, then this should generally remain true, so + * as to fail faster and give the user a better overall experience. + * + * Also note that it is possible to escape dashes, so the argument parser doesn't accept the argument + * as a known flag, but instead a literal, which can then be further processed later by another + * argument parser, for instance. It may be better to instead instruct users to pass the arguments + * through escaped, so that arguments can still be validated. (This works for both short and + * long arguments, i.e. {@code \-s} or {@code \--long}.) + * + * @param errorOnUnknown + * @return {@code this} for easier chaining. + */ + public ArgumentParser setErrorOnUnknownArgs(boolean errorOnUnknown) { + this.errorOnUnknown = errorOnUnknown; + return this; + } + + /** + * Builds a description of this ArgumentParser, which may be useful as + * "Usage" text to provide to a user if a call to match throws a + * ValidationException, for instance. + * + * @return + */ + public String getBuiltDescription() { + StringBuilder b = new StringBuilder(); + //Now, we need to go through and get all the switch names in alphabetical + //order. + b.append("\t").append(this.description).append("\n\n"); + if(!this.extendedDescription.equals("")) { + b.append("\t").append(this.extendedDescription).append("\n\n"); + } + List shortCodes = new ArrayList<>(); + List longCodes = new ArrayList<>(); + List shortCodesDone = new ArrayList<>(); + List longCodesDone = new ArrayList<>(); + List aliases = new ArrayList<>(); + for(Argument arg : argumentModel) { + if(arg.shortArg != null) { + shortCodes.add(arg.shortArg); + } + if(arg.longArg != null) { + longCodes.add(arg.longArg); + } + if(arg.shortArg != null && arg.longArg != null) { + aliases.add(arg.longArg); + } + } + Collections.sort(shortCodes); + Collections.sort(longCodes); + //Go through the flags first + boolean hasShortCodeFlags = false; + StringBuilder flags = new StringBuilder(); + List shortFlags = new ArrayList<>(); + List longFlags = new ArrayList<>(); + List shortArguments = new ArrayList<>(); + List longArguments = new ArrayList<>(); + for(Character c : shortCodes) { + Argument a = getArgument(c); + if(a.isFlag()) { + shortCodesDone.add(c); + flags.append(a.generateDescription(true)); + hasShortCodeFlags = true; + shortFlags.add(c); + } else { + shortArguments.add(c); + } + } + + for(String s : longCodes) { + Argument a = getArgument(s); + if(a.isFlag()) { + longCodesDone.add(s); + flags.append(a.generateDescription(false)); + if(!aliases.contains(s)) { + longFlags.add(s); + } + } else if(!aliases.contains(s)) { + longArguments.add(s); + } + } + + b.append(TermColors.BOLD).append("Usage:\n\t").append(TermColors.RESET); + //Get the short flags first, then the long flags, then the short arguments, then the long arguments + List parts = new ArrayList<>(); + if(!shortFlags.isEmpty()) { + StringBuilder usage = new StringBuilder(); + usage.append("[").append("-"); + for(Character c : shortFlags) { + usage.append(c); + } + usage.append("]"); + parts.add(usage.toString()); + } + + for(String s : longFlags) { + parts.add("[--" + s + "]"); + } + + List usageList = new ArrayList<>(); + for(Character c : shortArguments) { + usageList.add(getArgument(c)); + } + for(String s : longArguments) { + usageList.add(getArgument(s)); + } + for(Argument a : usageList) { + StringBuilder usage = new StringBuilder(); + if(!a.required) { + usage.append("["); + } + if(a.shortArg != null) { + usage.append("-").append(a.shortArg); + } else { + usage.append("--").append(a.longArg); + } + usage.append(" <"); + if(a.isNumeric()) { + usage.append("#"); + } + usage.append(a.usageName); + if(a.isArray()) { + usage.append(", ..."); + } + usage.append(">"); + + if(a.defaultVal != null && !"".equals(a.defaultVal)) { usage.append(" (default "); - if(a.argType == Type.STRING){ + if(a.argType == Type.STRING) { usage.append("\""); } usage.append(a.defaultVal); - if(a.argType == Type.STRING){ + if(a.argType == Type.STRING) { usage.append("\""); } usage.append(")"); } - - if (!a.required) { - usage.append("]"); - } - parts.add(usage.toString()); - } - - //Now, if the default switch exists, put it here too - if (getArgument() != null) { - parts.add("<" + getArgument().usageName + ", ...>"); - } - - { - StringBuilder usage = new StringBuilder(); - boolean first = true; - for (String part : parts) { - if (!first) { - b.append(" "); - } - first = false; - b.append(part); - } - if(parts.isEmpty()){ + + if(!a.required) { + usage.append("]"); + } + parts.add(usage.toString()); + } + + //Now, if the default switch exists, put it here too + if(getArgument() != null) { + String s = "<"; + if(getArgument().isNumeric()) { + s += "#"; + } + s += getArgument().usageName; + if(getArgument().isArray()) { + s += ", ..."; + } + s += ">"; + parts.add(s); + } + + { + StringBuilder usage = new StringBuilder(); + boolean first = true; + for(String part : parts) { + if(!first) { + b.append(" "); + } + first = false; + b.append(part); + } + if(parts.isEmpty()) { usage.append("No arguments."); } - b.append(usage.toString()); - } - b.append("\n\nOptions:\n\n"); - Argument def = getArgument(); - if (def != null && def.description != null) { - b.append(def.generateDescription(false)); - } - - if (flags.length() != 0) { - b.append("Flags"); - if (hasShortCodeFlags) { - b.append(" (Short flags may be combined)"); - } - b.append(":\n"); - b.append(flags.toString()); - b.append("\n"); - } - - if(shortCodes.isEmpty() && longCodes.isEmpty() && def == null && flags.length() == 0){ + b.append(usage.toString()); + } + + b.append("\n\n"); + Argument def = getArgument(); + if(def != null && def.description != null) { + b.append(def.generateDescription(false)); + } + + if(flags.length() != 0) { + b.append(TermColors.BOLD).append("Flags").append(TermColors.RESET); + if(hasShortCodeFlags) { + b.append(" (Short flags may be combined)"); + } + b.append(":\n"); + b.append(flags.toString()); + b.append("\n"); + } + + if(shortCodes.isEmpty() && longCodes.isEmpty() && def == null && flags.length() == 0) { b.append("\tNo flags or options.\n"); } else { - if(shortCodes.isEmpty() && longCodes.isEmpty() && def == null){ + if(shortCodes.isEmpty() && longCodes.isEmpty() && def == null) { b.append("\tNo options.\n"); - } else if(flags.length() == 0){ + } else if(flags.length() == 0) { b.append("\tNo flags.\n"); + } else { + b.append(TermColors.BOLD).append("Options:\n").append(TermColors.RESET); } } - for (Character c : shortCodes) { - if (!shortCodesDone.contains(c)) { - b.append(getArgument(c).generateDescription(true)); - } - } - for (String s : longCodes) { - if (!longCodesDone.contains(s)) { - b.append(getArgument(s).generateDescription(false)); - } - } - return b.toString(); - } - + for(Character c : shortCodes) { + if(!shortCodesDone.contains(c)) { + b.append(getArgument(c).generateDescription(true)); + } + } + for(String s : longCodes) { + if(!longCodesDone.contains(s)) { + b.append(getArgument(s).generateDescription(false)); + } + } + return b.toString(); + } + /** - * Returns just the description that was registered with {@see #addDescription(String)}. - * @return The description, or null, if one has not been set yet. + * Returns just the description that was registered with {@link #addDescription(java.lang.String)} + * + * @return The description, or empty string, if one has not been set yet. * @see #getBuiltDescription() */ - public String getDescription(){ + public String getDescription() { return description; } - /** - * This method takes a raw string, which represents the arguments as a - * single string. It supports quoted arguments (both single and double), so - * "this example" would make the string "this example" one argument instead - * of two. Quotes may be escaped with a backslash, like so: "\"quote\"". - * Also, all arguments that start with a dash are considered flags, if you - * need a literal dash, escape it with a backslash too, \-. Instead of - * quoting arguments, you could also add a \ in front of a space to make it - * a literal space, - * like\ this. Within a string, to add a literal backslash in - * front of an otherwise escapable character, use two backslashes - * "like this\\" - * - * @param args - * @return - */ - public ArgumentParserResults match(String args) throws ValidationException { - return parse(lex(args)); - } + /** + * This method takes a raw string, which represents the arguments as a + * single string. It supports quoted arguments (both single and double), so + * "this example" would make the string "this example" one argument instead + * of two. Quotes may be escaped with a backslash, like so: "\"quote\"". + * Also, all arguments that start with a dash are considered flags, if you + * need a literal dash, escape it with a backslash too, \-. Instead of + * quoting arguments, you could also add a \ in front of a space to make it + * a literal space, like\ this. Within a string, to add a + * literal backslash in front of an otherwise escapable character, use two + * backslashes "like this\\" + * + * @param args + * @return + * @throws com.laytonsmith.PureUtilities.ArgumentParser.ValidationException + */ + public ArgumentParserResults match(String args) throws ValidationException { + return parse(lex(args)); + } /** - * Returns a simple List of the arguments, parsed into a proper argument list. - * This will work essentially identically to how general shell arguments are parsed. + * This method assumes that the arguments have already been parsed out, so, + * for instance, if an argument inside of args[x] contains spaces, it will + * still be considered one argument. So, ["args with spaces", "args"] may + * have looked like this originally: "args with spaces" args + * but through some means or another, you have already parsed the arguments + * out. + * * @param args - * @return + * @return + * @throws com.laytonsmith.PureUtilities.ArgumentParser.ValidationException */ - static List lex(String args) { - //First, we have to tokenize the strings. Since we can have quoted arguments, we can't simply split on spaces. - List arguments = new ArrayList(); - StringBuilder buf = new StringBuilder(); - boolean state_in_single_quote = false; - boolean state_in_double_quote = false; - for (int i = 0; i < args.length(); i++) { - Character c0 = args.charAt(i); - Character c1 = i + 1 < args.length() ? args.charAt(i + 1) : null; - - if (c0 == '\\') { - if (c1 == '\'' && state_in_single_quote - || c1 == '"' && state_in_double_quote - || c1 == ' ' && !state_in_double_quote && !state_in_single_quote - || c1 == '\\' && ( state_in_double_quote || state_in_single_quote )) { - //We are escaping the next character. Add it to the buffer instead, and - //skip ahead two - buf.append(c1); - i++; - continue; - } - - } - - if (c0 == ' ') { - if (!state_in_double_quote && !state_in_single_quote) { - //argument split - if (buf.length() != 0) { - arguments.add(buf.toString()); - buf = new StringBuilder(); - } - continue; - } - } - if (c0 == '\'' && !state_in_double_quote) { - if (state_in_single_quote) { - state_in_single_quote = false; - arguments.add(buf.toString()); - buf = new StringBuilder(); - } else { - if (buf.length() != 0) { - arguments.add(buf.toString()); - buf = new StringBuilder(); - } - state_in_single_quote = true; - } - continue; - } - if (c0 == '"' && !state_in_single_quote) { - if (state_in_double_quote) { - state_in_double_quote = false; - arguments.add(buf.toString()); - buf = new StringBuilder(); - } else { - if (buf.length() != 0) { - arguments.add(buf.toString()); - buf = new StringBuilder(); - } - state_in_double_quote = true; - } - continue; - } - buf.append(c0); - } - if (buf.length() != 0) { - arguments.add(buf.toString()); - } - return arguments; - } - - private ArgumentParserResults parse(List args) throws ValidationException { - ArgumentParserResults results = new ArgumentParserResults(); - //Fill in results with all the defaults - for (Argument arg : argumentModel) { - if (arg.defaultVal != null) { - //For flags, we simply don't add them if they default to false. - if (!arg.isFlag() || ( arg.isFlag() && arg.defaultVal != null )) { - Argument newArg = new Argument(arg); - newArg.setValue(arg.defaultVal); - results.updateArgument(newArg); - } - } - } - //These are arguments that are not flags. - List looseArgs = new ArrayList<>(); - Argument lastArg = null; - for (String arg : args) { - if (arg.matches("^[\\\\]+-.*$")) { - //This is an argument that starts with a literal dash, but we need - //to pull out this first backslash - looseArgs.add(arg.substring(1)); - continue; - } - - //Our regexes have a star, because -(-) is a valid argument that is an empty string. - //"" != null. - if (arg.matches("--[a-zA-Z0-9\\-]*")) { - //Finish up the last argument - results.updateArgument(validateArgument(lastArg, looseArgs)); - //This is a long arg, and so it is the only one. - arg = arg.substring(2); - lastArg = getArgument(arg); - continue; - } - - if (arg.matches("-[a-zA-Z0-9]*")) { - //Finish up the last argument - results.updateArgument(validateArgument(lastArg, looseArgs)); - //This is a short arg, but it could be multiple letters (therefore multiple flags) - //At most, one of these can be a non-flag argument. - boolean hasNonFlagArg = false; - char lastNonFlag = ' '; - for (int i = 1; i < arg.length(); i++) { - Character c = arg.charAt(i); - Argument vArg = getArgument(c); - if(vArg == null){ - throw new ValidationException("Unrecognized flag: " + c); + public ArgumentParserResults match(String[] args) throws ValidationException { + return parse(Arrays.asList(args)); + } + + /** + * Returns a simple List of the arguments, parsed into a proper argument + * list. This will work essentially identically to how general shell + * arguments are parsed. + * + * @param args + * @return + */ + static List lex(String args) { + //First, we have to tokenize the strings. Since we can have quoted arguments, we can't simply split on spaces. + List arguments = new ArrayList<>(); + StringBuilder buf = new StringBuilder(); + boolean stateInSingleQuote = false; + boolean stateInDoubleQuote = false; + for(int i = 0; i < args.length(); i++) { + Character c0 = args.charAt(i); + Character c1 = i + 1 < args.length() ? args.charAt(i + 1) : null; + + if(c0 == '\\') { + if(c1 == '\'' && stateInSingleQuote + || c1 == '"' && stateInDoubleQuote + || c1 == ' ' && !stateInDoubleQuote && !stateInSingleQuote + || c1 == '\\' && (stateInDoubleQuote || stateInSingleQuote)) { + //We are escaping the next character. Add it to the buffer instead, and + //skip ahead two + buf.append(c1); + i++; + continue; + } + + } + + if(c0 == ' ') { + if(!stateInDoubleQuote && !stateInSingleQuote) { + //argument split + if(buf.length() != 0) { + arguments.add(buf.toString()); + buf = new StringBuilder(); + } + continue; + } + } + if(c0 == '\'' && !stateInDoubleQuote) { + if(stateInSingleQuote) { + stateInSingleQuote = false; + arguments.add(buf.toString()); + buf = new StringBuilder(); + } else { + if(buf.length() != 0) { + arguments.add(buf.toString()); + buf = new StringBuilder(); + } + stateInSingleQuote = true; + } + continue; + } + if(c0 == '"' && !stateInSingleQuote) { + if(stateInDoubleQuote) { + stateInDoubleQuote = false; + arguments.add(buf.toString()); + buf = new StringBuilder(); + } else { + if(buf.length() != 0) { + arguments.add(buf.toString()); + buf = new StringBuilder(); + } + stateInDoubleQuote = true; + } + continue; + } + buf.append(c0); + } + if(buf.length() != 0) { + arguments.add(buf.toString()); + } + return arguments; + } + + private ArgumentParserResults parse(List args) throws ValidationException { + ArgumentParserResults results = new ArgumentParserResults(args); + //Fill in results with all the defaults + for(Argument arg : argumentModel) { + if(arg.defaultVal != null) { + //For flags, we simply don't add them if they default to false. + if(!arg.isFlag() || (arg.isFlag() && arg.defaultVal != null)) { + Argument newArg = new Argument(arg); + newArg.setValue(arg.defaultVal); + results.updateArgument(newArg); + } + } + } + //These are arguments that are not flags. + List looseArgs = new ArrayList<>(); + Argument lastArg = null; + for(String arg : args) { + if(arg.matches("^[\\\\]+-.*$")) { + //This is an argument that starts with a literal dash, but we need + //to pull out this first backslash + looseArgs.add(arg.substring(1)); + continue; + } + + //Our regexes have a star, because -(-) is a valid argument that is an empty string. + //"" != null. + if(arg.matches("--[a-zA-Z0-9\\-]*")) { + //Finish up the last argument + results.updateArgument(validateArgument(lastArg, looseArgs)); + //This is a long arg, and so it is the only one. + arg = arg.substring(2); + if(errorOnUnknown && getArgument(arg) == null) { + throw new ValidationException("Unrecognized argument: " + arg); + } + lastArg = getArgument(arg); + continue; + } + + if(arg.matches("-[a-zA-Z0-9]*")) { + //Finish up the last argument + results.updateArgument(validateArgument(lastArg, looseArgs)); + //This is a short arg, but it could be multiple letters (therefore multiple flags) + //At most, one of these can be a non-flag argument. + boolean hasNonFlagArg = false; + char lastNonFlag = ' '; + for(int i = 1; i < arg.length(); i++) { + Character c = arg.charAt(i); + Argument vArg = getArgument(c); + if(vArg == null) { + if(errorOnUnknown) { + throw new ValidationException("Unrecognized flag: " + c); + } else { + results.updateUnclassifiedArgument("-" + c.toString()); + continue; + } + } + if(!vArg.isFlag() && hasNonFlagArg) { + //We have already come across a non-flag argument, and since this one isn't + //a flag, we need to throw an exception. + throw new ValidationException("Cannot combine multiple non-flag arguments using the short form. Found '" + c + + "' but had already found '" + lastNonFlag + "'. You must split these into multiple arguments, even if they" + + " do not have any parameters, for instance, -" + c + " -" + lastNonFlag); + } + //Is this a non flag? + if(!vArg.isFlag()) { + hasNonFlagArg = true; + lastNonFlag = c; + lastArg = vArg; + //This is all we need to do, it will be dealt with by the next iteration + } else { + //Since it's just a flag, we don't need to worry about it regarding loose arguments, so we + //are just going to go ahead and add it to the results. + results.updateArgument(vArg); } - if (!vArg.isFlag() && hasNonFlagArg) { - //We have already come across a non-flag argument, and since this one isn't - //a flag, we need to throw an exception. - throw new ValidationException("Cannot combine multiple non-flag arguments using the short form. Found '" + c - + "' but had already found '" + lastNonFlag + "'. You must split these into multiple arguments, even if they" - + " do not have any parameters, for instance, -" + c + " -" + lastNonFlag); - } - //Is this a non flag? - if (!vArg.isFlag()) { - hasNonFlagArg = true; - lastNonFlag = c; - lastArg = vArg; - //This is all we need to do, it will be dealt with by the next iteration - } else { - //Since it's just a flag, we don't need to worry about it regarding loose arguments, so we - //are just going to go ahead and add it to the results. - results.updateArgument(vArg); - } - } - continue; - } - + } + continue; + } + //It's just a loose arg, so we'll add it to the list and deal with it at the end looseArgs.add(arg); - } + } - //Finish up the last argument - results.updateArgument(validateArgument(lastArg, looseArgs)); - if(looseArgs.size() > 0){ + //Finish up the last argument + results.updateArgument(validateArgument(lastArg, looseArgs)); + if(looseArgs.size() > 0) { //There are loose arguments left, so add them to the loose argument list. results.updateArgument(validateArgument(null, looseArgs)); } - //TODO: Check to see if all the required values are here + //Check to see if all the required values are here + List missing = new ArrayList<>(); + model: for(Argument model : argumentModel) { + if(model.required) { + for(Argument r : results.arguments) { + if(r.modelEquals(model)) { + continue model; + } + } + if(model.shortArg != null && model.longArg == null) { + missing.add(model.shortArg.toString()); + } else if(model.shortArg == null && model.longArg != null) { + missing.add(model.longArg); + } else if(model.shortArg != null && model.longArg != null) { + missing.add(model.shortArg + "/" + model.longArg); + } else { + missing.add(""); + } + } + } + if(!missing.isEmpty()) { + throw new ValidationException("Missing required argument(s): " + StringUtils.Join(missing, ", ")); + } - return results; - } + return results; + } - private Argument validateArgument(Argument arg, List looseArgs) throws ValidationException { - if (arg == null) { - if(!looseArgs.isEmpty()){ + private Argument validateArgument(Argument arg, List looseArgs) throws ValidationException { + if(arg == null) { + if(!looseArgs.isEmpty()) { //All the loose arguments are accounted for. Either we're done with the arguments, //or we just hit a -(-)specifier, so we can stop parsing these, and go ahead and //add them to the results. @@ -1098,57 +1548,40 @@ private Argument validateArgument(Argument arg, List looseArgs) throws V looseArgs.clear(); return a; } - return null; - } - Argument finishedArgument = new Argument(arg); - if (arg.isSingle()) { - //Just the first loose argument is associated with this argument, - //the rest (if any) belong to the default loose argument list. - //Of course, looseArgs could be empty, in which case we won't add anything to the list. - if (looseArgs.size() > 0) { - String looseArg = looseArgs.get(0); - looseArgs.remove(0); - finishedArgument.setValue(looseArg); - if (arg.isNumeric()) { - try { - Double.parseDouble(looseArg); - } - catch (NumberFormatException e) { - throw new ValidationException("Expecting a numeric value, but \"" + looseArg + "\" was encountered."); - } - } - } else { - finishedArgument.setValue(""); - } - } else if (arg.isArray()) { - finishedArgument.setValue(looseArgs); - if (arg.isNumeric()) { - for (String val : looseArgs) { - try { - Double.parseDouble(val); - } - catch (NumberFormatException e) { - throw new ValidationException("Expecting a numeric value, but \"" + val + "\" was encountered."); - } - } - } - looseArgs.clear(); - } - return finishedArgument; - } - - /** - * This method assumes that the arguments have already been parsed out, so, - * for instance, if an argument inside of args[x] contains spaces, it will - * still be considered one argument. So, ["args with spaces", "args"] may - * have looked like this originally: - * "args with spaces" args but through some means or another, - * you have already parsed the arguments out. - * - * @param args - * @return - */ - public ArgumentParserResults match(String[] args) throws ValidationException { - return parse(Arrays.asList(args)); - } + return null; + } + Argument finishedArgument = new Argument(arg); + if(arg.isSingle()) { + //Just the first loose argument is associated with this argument, + //the rest (if any) belong to the default loose argument list. + //Of course, looseArgs could be empty, in which case we won't add anything to the list. + if(looseArgs.size() > 0) { + String looseArg = looseArgs.get(0); + looseArgs.remove(0); + finishedArgument.setValue(looseArg); + if(arg.isNumeric()) { + try { + Double.parseDouble(looseArg); + } catch (NumberFormatException e) { + throw new ValidationException("Expecting a numeric value, but \"" + looseArg + "\" was encountered."); + } + } + } else { + finishedArgument.setValue(""); + } + } else if(arg.isArray()) { + finishedArgument.setValue(looseArgs); + if(arg.isNumeric()) { + for(String val : looseArgs) { + try { + Double.parseDouble(val); + } catch (NumberFormatException e) { + throw new ValidationException("Expecting a numeric value, but \"" + val + "\" was encountered."); + } + } + } + looseArgs.clear(); + } + return finishedArgument; + } } diff --git a/src/main/java/com/laytonsmith/PureUtilities/ArgumentSuite.java b/src/main/java/com/laytonsmith/PureUtilities/ArgumentSuite.java index 9e5c75fb50..f06cb0f069 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/ArgumentSuite.java +++ b/src/main/java/com/laytonsmith/PureUtilities/ArgumentSuite.java @@ -6,156 +6,186 @@ import com.laytonsmith.PureUtilities.Common.ArrayUtils; import com.laytonsmith.PureUtilities.Common.StringUtils; import java.util.ArrayList; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.TreeMap; /** * An ArgumentSuite is an ArgumentParser that supports "modes". - * - * A mode is a required parameter that causes a fully separate argument parser to - * be used to parse the remaining arguments. This allows for finer control over - * mutually exclusive parameters in both the documentation and the validation, as - * well as wider support for traditional use cases. - * + * + * A mode is a required parameter that causes a fully separate argument parser to be used to parse the remaining + * arguments. This allows for finer control over mutually exclusive parameters in both the documentation and the + * validation, as well as wider support for traditional use cases. + * */ public class ArgumentSuite { - - private Map suite; - private Map aliases; + + private final Map suite; + private final Map undocumentedSuites; + private final Map aliases; private String description; - - public ArgumentSuite(){ - suite = new LinkedHashMap(); - aliases = new LinkedHashMap(); + + public ArgumentSuite() { + suite = new TreeMap<>((o1, o2) -> { + return o1.compareTo(o2); + }); + undocumentedSuites = new HashMap<>(); + aliases = new LinkedHashMap<>(); } - + /** - * Adds a new mode. A mode name may contain dashes, which would look like normal - * argument flags, but would actually be a mode. This is useful especially for a - * --help command, which shows the ArgumentSuite's help. - * @param modeName The name of this mode. This may not contain spaces. + * Adds a new mode. A mode name may contain dashes, which would look like normal argument flags, but would actually + * be a mode. This is useful especially for a --help command, which shows the ArgumentSuite's help. + * + * @param modeName The name of this mode. This may not contain spaces. * @param mode The sub-ArgumentParser that will be used when in this mode. + * @param undocumented Undocumented tools do not show up in the built description, though otherwise work and + * can be retrieved as usual through the getMode methods. + * @return * @throws IllegalArgumentException if the name of the mode contains spaces */ - public ArgumentSuite addMode(String modeName, ArgumentParser mode){ + public ArgumentSuite addMode(String modeName, ArgumentParser mode, boolean undocumented) { validateModeName(modeName); suite.put(modeName, mode); + undocumentedSuites.put(modeName, undocumented); return this; } - + /** - * Adds a mode alias. This is the recommended behavior instead of adding the - * same mode with a different name, because the built description is aware - * of the difference between an alias and the real mode. All the same rules - * apply to aliases that apply to mode names. The realModeName doesn't - * strictly need to exist yet. + * Adds a mode alias. This is the recommended behavior instead of adding the same mode with a different name, + * because the built description is aware of the difference between an alias and the real mode. All the same rules + * apply to aliases that apply to mode names. The realModeName doesn't strictly need to exist yet. + * * @param alias * @param realModeName - * @return + * @return */ - public ArgumentSuite addModeAlias(String alias, String realModeName){ + public ArgumentSuite addModeAlias(String alias, String realModeName) { validateModeName(alias); aliases.put(alias, realModeName); return this; } - - private void validateModeName(String modeName){ - if(modeName.contains(" ")){ + + private void validateModeName(String modeName) { + if(modeName.contains(" ")) { throw new IllegalArgumentException("The mode name may not contain a space."); } } - + /** - * Adds a description, which is used in {@see #getBuiltDescription} + * Adds a description, which is used in {@link #getBuiltDescription()}. * @param description - * @return + * @return */ - public ArgumentSuite addDescription(String description){ + public ArgumentSuite addDescription(String description) { this.description = description; return this; } - + /** * Returns a mode that was previously registered. + * * @param name The name of the mode to get * @return The mode registered under the provided name * @throws IllegalArgumentException if the mode is not registered */ - public ArgumentParser getMode(String name){ - if(suite.containsKey(name)){ + public ArgumentParser getMode(String name) { + if(suite.containsKey(name)) { return suite.get(name); } else { throw new IllegalArgumentException("No mode by the name \"" + name + "\" has been registered."); } } - + /** * Selects the appropriate mode, and calls match on that ArgumentParser. + * * @param args The pre-parsed arguments * @param defaultMode The default mode, which will be used only if no arguments were passed in. - * @return - * @throws ResultUseException if the mode cannot be found, or if the sub-ArgumentParser - * throws an exception. + * @return + * @throws ResultUseException if the mode cannot be found, or if the sub-ArgumentParser throws an exception. + * @throws com.laytonsmith.PureUtilities.ArgumentParser.ValidationException + * @throws com.laytonsmith.PureUtilities.ArgumentSuite.ModeNotFoundException If the mode itself was not found. */ - public ArgumentSuiteResults match(String [] args, String defaultMode) throws ResultUseException, ValidationException { - String [] nonModeArgs = new String[0]; + public ArgumentSuiteResults match(String[] args, String defaultMode) throws ResultUseException, + ValidationException, ModeNotFoundException { + String[] nonModeArgs = ArrayUtils.EMPTY_STRING_ARRAY; String mode; - if(args.length > 1){ + if(args.length > 1) { mode = args[0]; nonModeArgs = ArrayUtils.cast(ArrayUtils.slice(args, 1, args.length - 1), String[].class); - } else if(args.length == 1){ + } else if(args.length == 1) { mode = args[0]; } else { //0 argsm, use the defaultMode. mode = defaultMode; } - if(aliases.containsKey(mode)){ + if(aliases.containsKey(mode)) { mode = aliases.get(mode); } - if(suite.containsKey(mode)){ + if(suite.containsKey(mode)) { return new ArgumentSuiteResults(mode, suite.get(mode), suite.get(mode).match(nonModeArgs)); } else { - throw new ResultUseException("Mode " + mode + " was not found."); + String msg = ""; + for(String key : suite.keySet()) { + if(StringUtils.LevenshteinDistance(key, mode) <= 2) { + if("".equals(msg)) { + msg = "\nDid you mean:\n"; + } + msg += "\t" + key + "\n"; + } + } + throw new ResultUseException("Mode " + mode + " was not found." + msg); } } - + /** * Selects the appropriate mode, and calls match on that ArgumentParser. + * * @param args The unparsed arguments * @param defaultMode The default mode, which will be used only if no arguments were passed in. - * @return - * @throws ResultUseException if the mode cannot be found, or if the sub-ArgumentParser - * throws an exception. + * @return + * @throws ResultUseException if the mode cannot be found, or if the sub-ArgumentParser throws an exception. + * @throws com.laytonsmith.PureUtilities.ArgumentParser.ValidationException + * @throws com.laytonsmith.PureUtilities.ArgumentSuite.ModeNotFoundException If the mode could not be found */ - public ArgumentSuiteResults match(String args, String defaultMode) throws ResultUseException, ValidationException { + public ArgumentSuiteResults match(String args, String defaultMode) throws ResultUseException, ValidationException, + ModeNotFoundException { //We're going to use ArgumentParser's parse method to get a string list, then //pass that to the other match return match(ArgumentParser.lex(args).toArray(new String[]{}), defaultMode); } - + /** - * Returns a built description of this ArgumentSuite, which would be appropriate - * to display if no arguments are passed in (or the mode name is help, -help, --help, etc) - * @return + * Returns a built description of this ArgumentSuite, which would be appropriate to display if no arguments are + * passed in (or the mode name is help, -help, --help, etc) + * + * @return */ - public String getBuiltDescription(){ + public String getBuiltDescription() { StringBuilder b = new StringBuilder(); - if(description != null){ + if(description != null) { b.append(description).append("\n\n"); } b.append("Modes: (a mode must be the first argument) \n"); - for(String mode : suite.keySet()){ - b.append("\t").append(TermColors.BOLD).append(mode); - if(aliases.containsValue(mode)){ + for(String mode : suite.keySet()) { + if(undocumentedSuites.get(mode) == true) { + // Skip undocumented values. + continue; + } + + b.append("\t").append(TermColors.BOLD).append(TermColors.BRIGHT_GREEN).append(mode); + if(aliases.containsValue(mode)) { List keys = new ArrayList<>(); - for(String alias : aliases.keySet()){ - if(aliases.get(alias).equals(mode)){ + for(String alias : aliases.keySet()) { + if(aliases.get(alias).equals(mode)) { keys.add(alias); } } b.append(TermColors.RESET).append(" (Alias"); - if(keys.size() != 1){ + if(keys.size() != 1) { b.append("es"); } b.append(": ").append(StringUtils.Join(keys, ", ")).append(")"); @@ -164,137 +194,100 @@ public String getBuiltDescription(){ } return b.toString(); } - + /** - * A convenience method to get the real mode name registered for this - * alias, or null if no such alias exists. If the alias is actually a mode, - * it is simply returned. Useful for perhaps a help mode, to resolve the actual - * mode named. If {@code alias} is null, null is returned. + * A convenience method to get the real mode name registered for this alias, or null if no such alias exists. If the + * alias is actually a mode, it is simply returned. Useful for perhaps a help mode, to resolve the actual mode + * named. If {@code alias} is null, null is returned. + * * @param alias - * @return + * @return */ - public String getModeFromAlias(String alias){ - if(alias == null){ + public String getModeFromAlias(String alias) { + if(alias == null) { return null; } - if(suite.containsKey(alias)){ + if(suite.containsKey(alias)) { return alias; - } else if(aliases.containsKey(alias)){ + } else if(aliases.containsKey(alias)) { return aliases.get(alias); } else { return null; } } - + /** - * A convenience method to get the underlying ArgumentParser - * based on the mode name given. Aliases will not suffice, but you - * may call getModeFromAlias to resolve the mode name first. Null - * is returned if no mode exists with that name. Useful for perhaps - * a help mode, to generically display a mode's help. If {@see mode} - * is null, null is returned. + * A convenience method to get the underlying ArgumentParser based on the mode name given. Aliases will not suffice, + * but you may call getModeFromAlias to resolve the mode name first. Null is returned if no mode exists with that + * name. Useful for perhaps a help mode, to generically display a mode's help. If {@code mode} is null, null is returned. + * * @param mode - * @return + * @return */ - public ArgumentParser getModeFromName(String mode){ - if(mode == null){ + public ArgumentParser getModeFromNameOrNull(String mode) { + if(mode == null) { return null; } - if(suite.containsKey(mode)){ + if(suite.containsKey(mode)) { return suite.get(mode); } else { return null; } } - - public static class ArgumentSuiteResults{ - private ArgumentParser mode; - private ArgumentParserResults results; - private String modeName; - private ArgumentSuiteResults(String modeName, ArgumentParser mode, ArgumentParserResults results){ + + /** + * This exception is only thrown if the mode could not be found. If that is the case, the arguments were not + * attempted to be parsed. If the exception is not thrown, then that means that the mode WAS found, but the + * arguments are wrong. It can be appropriate to then manually look up the mode, and display the help just + * for that mode, instead of all modes. + */ + public static final class ModeNotFoundException extends Exception { + + public ModeNotFoundException(String message) { + super(message); + } + + } + + public static final class ArgumentSuiteResults { + + private final ArgumentParser mode; + private final ArgumentParserResults results; + private final String modeName; + + private ArgumentSuiteResults(String modeName, ArgumentParser mode, ArgumentParserResults results) { this.modeName = modeName; this.mode = mode; this.results = results; } - + /** * Returns the name of the mode that was selected. (Not the alias) - * @return + * + * @return */ - public String getModeName(){ + public String getModeName() { return modeName; } - + /** - * The ArgumentParser for the given mode. This will be a reference - * to the mode passed in, so you can do == on it. - * @return + * The ArgumentParser for the given mode. This will be a reference to the mode passed in, so you can do == on + * it. + * + * @return */ - public ArgumentParser getMode(){ + public ArgumentParser getMode() { return mode; } - + /** * The ArgumentParserResults for the ArgumentParser mode - * @return + * + * @return */ - public ArgumentParserResults getResults(){ + public ArgumentParserResults getResults() { return results; } } - - public static void main(String [] args){ - ArgumentSuite suite = new ArgumentSuite(); - ArgumentParser mode1 = ArgumentParser.GetParser(); - ArgumentParser mode2 = ArgumentParser.GetParser(); - mode1.addArgument("arg1", ArgumentParser.Type.STRING, "Argument 1", "arg1", false); - mode1.addArgument("arg2", ArgumentParser.Type.STRING, "Argument 2", "arg2", false); - mode1.addDescription("A description of Mode 1"); - mode2.addArgument("arg1", ArgumentParser.Type.STRING, "Argument 1", "arg1", false); - mode2.addArgument("arg2", ArgumentParser.Type.STRING, "Argument 2", "arg2", false); - mode2.addDescription("A description of Mode 2"); - suite.addModeAlias("mode-2", "mode2"); - suite.addModeAlias("mode--2", "mode2"); - ArgumentParser help = ArgumentParser.GetParser(); - help.addDescription("Displays the help for a mode, then exits.") - .addArgument("The mode name", "mode name", true); - suite.addDescription("A description of the suite") - .addMode("help", help) - .addMode("mode1", mode1) - .addMode("mode2", mode2); - try { - ArgumentSuiteResults results = suite.match("wat", "help"); - ArgumentParser mode = results.getMode(); - ArgumentParserResults modeResults = results.getResults(); - if(mode == help){ - String modeHelp = modeResults.getStringArgument(); - ArgumentParser selectedMode = suite.getModeFromName(suite.getModeFromAlias(modeHelp)); - if(selectedMode != null){ - System.out.println(selectedMode.getBuiltDescription()); - System.exit(0); - } else { - showHelp(suite); - } - } else if(mode == mode1 || mode == mode2){ - String argument; - if((argument = modeResults.getStringArgument("arg1")) != null){ - System.out.println("You selected " + argument + " for arg1"); - } - if((argument = modeResults.getStringArgument("arg2")) != null){ - System.out.println("You selected " + argument + " for arg2"); - } - System.exit(0); - } - } catch (ResultUseException ex) { - showHelp(suite); - } catch (ValidationException ex) { - showHelp(suite); - } - } - - private static void showHelp(ArgumentSuite suite){ - System.out.println(suite.getBuiltDescription()); - System.exit(1); - } - + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/Annotations/CacheAnnotations.java b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/Annotations/CacheAnnotations.java index f1259c4b15..f64956d331 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/Annotations/CacheAnnotations.java +++ b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/Annotations/CacheAnnotations.java @@ -1,32 +1,105 @@ - package com.laytonsmith.PureUtilities.ClassLoading.Annotations; +import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery; import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscoveryCache; import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscoveryURLCache; +import com.laytonsmith.PureUtilities.Common.Annotations.AnnotationChecks; +import com.laytonsmith.PureUtilities.Common.StreamUtils; +import com.laytonsmith.PureUtilities.Common.StringUtils; +import com.laytonsmith.abstraction.Implementation; +import com.laytonsmith.annotations.api; +import com.laytonsmith.annotations.typeof; +import com.laytonsmith.core.SimpleDocumentation; +import com.laytonsmith.core.functions.DummyFunction; import java.io.File; import java.io.FileOutputStream; +import java.lang.reflect.Method; import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import com.laytonsmith.PureUtilities.Common.Annotations.InterfaceRunnerFor; +import com.laytonsmith.core.natives.interfaces.MixedInterfaceRunner; /** * */ public class CacheAnnotations { - + public static void main(String[] args) throws Exception { File outputDir = new File(args[0]); File scanDir = new File(args[1]); - if(outputDir.toString().startsWith("-classpath") || outputDir.toString().startsWith("-Xdebug")){ + if(outputDir.toString().startsWith("-classpath") || outputDir.toString().startsWith("-Xdebug")) { //This happens when running locally. I dunno what that is, but we //can skip this step. - System.out.println("Skipping annotation caching, running locally."); + StreamUtils.GetSystemOut().println("Skipping annotation caching, running locally."); return; } - System.out.println("-- Caching annotations --"); - System.out.println("Scanning for classes in " + scanDir.getAbsolutePath()); - System.out.println("Outputting file to directory " + outputDir.getAbsolutePath()); + StreamUtils.GetSystemOut().println("-- Caching annotations --"); + StreamUtils.GetSystemOut().println("Scanning for classes in " + scanDir.getAbsolutePath()); + StreamUtils.GetSystemOut().println("Outputting file to directory " + outputDir.getAbsolutePath()); long start = System.currentTimeMillis(); - new ClassDiscoveryURLCache(new URL("file:" + scanDir.getCanonicalPath())) - .writeDescriptor(new FileOutputStream(new File(outputDir, ClassDiscoveryCache.OUTPUT_FILENAME))); - System.out.println("Done writing " + ClassDiscoveryCache.OUTPUT_FILENAME + ", which took " + (System.currentTimeMillis() - start) + " ms."); + URL cacheFile = new URL("file:" + scanDir.getCanonicalPath()); + ClassDiscoveryURLCache cache = new ClassDiscoveryURLCache(cacheFile); + cache.writeDescriptor(new FileOutputStream(new File(outputDir, ClassDiscoveryCache.OUTPUT_FILENAME))); + StreamUtils.GetSystemOut().println("Done writing " + ClassDiscoveryCache.OUTPUT_FILENAME + ", which took " + (System.currentTimeMillis() - start) + " ms."); + ClassDiscovery.getDefaultInstance().addPreCache(cacheFile, cache); + ClassDiscovery.getDefaultInstance().addDiscoveryLocation(cacheFile); + StreamUtils.GetSystemOut().println("-- Checking for custom compile errors --"); + AnnotationChecks.checkForceImplementation(); + AnnotationChecks.checkForTypeInTypeofClasses(); + AnnotationChecks.verifyExhaustiveVisitors(); + AnnotationChecks.verifyNonInheritImplements(); + + Implementation.setServerType(Implementation.Type.SHELL); + List uhohs = new ArrayList<>(); + Set apiClasses = new HashSet<>(); + apiClasses.addAll(ClassDiscovery.getDefaultInstance().loadClassesWithAnnotation(api.class)); + apiClasses.addAll(ClassDiscovery.getDefaultInstance().loadClassesWithAnnotation(typeof.class)); + for(Class c : apiClasses) { + boolean isGetNameExempt = false; + if(c.isInterface()) { + for(Class r : ClassDiscovery.getDefaultInstance().loadClassesWithAnnotation(InterfaceRunnerFor.class)) { + InterfaceRunnerFor f = (InterfaceRunnerFor) r.getAnnotation(InterfaceRunnerFor.class); + if(f.value() == c) { + isGetNameExempt = ClassDiscovery.GetClassAnnotation(c, typeof.class) != null; + c = r; + break; + } + } + } + // Verify that all classes that are @api classes have the valid functions required for proper documentation + // generation, as well as ultimately extend at minimum SimpleDocumentation. + if(DummyFunction.class.isAssignableFrom(c)) { + // Skip this one. These are excused from the normal reporting requirements. + continue; + } + if(!SimpleDocumentation.class.isAssignableFrom(c) && !MixedInterfaceRunner.class.isAssignableFrom(c)) { + uhohs.add(c.getName() + " must implement SimpleDocumentation"); + continue; + } + for(Method m : SimpleDocumentation.class.getDeclaredMethods()) { + try { + c.getDeclaredMethod(m.getName(), m.getParameterTypes()); + } catch (NoSuchMethodException ex) { + // typeof is exempt from having getName in each individual class, because the + // typeof value is that information. + if(!m.getName().equals("getName")) { + if(ClassDiscovery.GetClassAnnotation(c, typeof.class) != null && !isGetNameExempt) { + uhohs.add(c.getName() + " must implement " + m.getName() + "()."); + } + } + } catch (SecurityException ex) { + throw new Error(ex); + } + } + } + if(!uhohs.isEmpty()) { + Collections.sort(uhohs); + throw new Exception("There " + StringUtils.PluralHelper(uhohs.size(), "compile error") + ":\n" + StringUtils.Join(uhohs, "\n")); + } + StreamUtils.GetSystemOut().println("-- Finished with custom compiler checks --"); } } diff --git a/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassDiscovery.java b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassDiscovery.java index aaacb28c05..3a0ceb4806 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassDiscovery.java +++ b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassDiscovery.java @@ -1,12 +1,18 @@ package com.laytonsmith.PureUtilities.ClassLoading; +import com.laytonsmith.PureUtilities.ClassLoading.ClassMirror.AbstractMethodMirror; import com.laytonsmith.PureUtilities.ClassLoading.ClassMirror.ClassMirror; +import com.laytonsmith.PureUtilities.ClassLoading.ClassMirror.ClassMirrorVisitor; import com.laytonsmith.PureUtilities.ClassLoading.ClassMirror.ClassReferenceMirror; +import com.laytonsmith.PureUtilities.ClassLoading.ClassMirror.ConstructorMirror; import com.laytonsmith.PureUtilities.ClassLoading.ClassMirror.FieldMirror; import com.laytonsmith.PureUtilities.ClassLoading.ClassMirror.MethodMirror; import com.laytonsmith.PureUtilities.Common.ClassUtils; import com.laytonsmith.PureUtilities.Common.FileUtil; +import com.laytonsmith.PureUtilities.Common.StackTraceUtils; +import com.laytonsmith.PureUtilities.Common.StreamUtils; import com.laytonsmith.PureUtilities.Common.StringUtils; +import com.laytonsmith.PureUtilities.Pair; import com.laytonsmith.PureUtilities.ProgressIterator; import com.laytonsmith.PureUtilities.ZipIterator; import java.io.File; @@ -14,6 +20,9 @@ import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.lang.annotation.Annotation; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; @@ -32,45 +41,44 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; +import org.objectweb.asm.ClassReader; /** - * This class contains methods for dynamically determining things about Classes, - * without loading them into PermGen. Search criteria is provided, (most notably - * annotations, however also subclasses) and Class/Field/Method mirrors are - * returned, eliminating the PermGen requirements, even though all known classes - * are scanned against. It is then up to the calling method to actually - * determine if the classes need to be loaded, thereby deferring all logic to - * actually take up more PermGen space to the calling code, instead of this - * class. + * This class contains methods for dynamically determining things about Classes, without loading them into PermGen. + * Search criteria is provided, (most notably annotations, however also subclasses) and Class/Field/Method mirrors are + * returned, eliminating the PermGen requirements, even though all known classes are scanned against. It is then up to + * the calling method to actually determine if the classes need to be loaded, thereby deferring all logic to actually + * take up more PermGen space to the calling code, instead of this class. */ public class ClassDiscovery { + private static final boolean IS_DEBUG = java.lang.management.ManagementFactory.getRuntimeMXBean() + .getInputArguments().toString().contains("jdwp"); + /** * The default instance. */ private static ClassDiscovery defaultInstance = null; /** - * Returns the default, shared instance. This is usually how you want to - * gain a reference to this class, as caching can often times be shared - * among multiple tasks, though if you need a private instance, you can use - * the constructor to create a new one. Using this instance automatically checks - * for the jarInfo.ser file in this jar, and if present, adds it to the cache. + * Returns the default, shared instance. This is usually how you want to gain a reference to this class, as caching + * can often times be shared among multiple tasks, though if you need a private instance, you can use the + * constructor to create a new one. Using this instance automatically checks for the jarInfo.ser file in this jar, + * and if present, adds it to the cache. * * @return */ public static synchronized ClassDiscovery getDefaultInstance() { - if (defaultInstance == null) { + if(defaultInstance == null) { defaultInstance = new ClassDiscovery(); - //defaultInstance.setDebugMode(true); +// defaultInstance.setDebugMode(true); } return defaultInstance; } /** - * Can be used to set the default ClassDiscovery instance returned by - * getDefaultInstance. Setting it to null is acceptable, and then a new, - * default ClassDiscovery instance will be generated. + * Can be used to set the default ClassDiscovery instance returned by getDefaultInstance. Setting it to null is + * acceptable, and then a new, default ClassDiscovery instance will be generated. * * @param cd */ @@ -79,100 +87,104 @@ public static void setDefaultInstance(ClassDiscovery cd) { } /** - * Creates a new instance of the ClassDiscovery class. Normally, you should - * probably just use the default instance, as caching across the board is a - * good thing, however, it may be the case that you need a standalone - * instance, in which case, you can create a new one. + * Creates a new instance of the ClassDiscovery class. Normally, you should probably just use the default instance, + * as caching across the board is a good thing, however, it may be the case that you need a standalone instance, in + * which case, you can create a new one. */ public ClassDiscovery() { // } - + /** - * Stores the mapping of class name to ClassMirror object. At any given - * time, after doDiscovery is called, this will be up to date with all known - * classes. + * Stores the mapping of class name to ClassMirror object. At any given time, after doDiscovery is called, this will + * be up to date with all known classes. */ - private final Map>> classCache = new HashMap>>(); + private final Map>> classCache = new HashMap<>(); /** - * This cache maps jvm name to the associated ClassMirror object, to speed - * up lookups. + * This cache maps jvm name to the associated ClassMirror object, to speed up lookups. */ - private final Map> jvmNameToMirror = new HashMap>(); + private final Map> jvmNameToMirror = new HashMap<>(); /** * Maps the fuzzy class name to actual Class object. */ - private final Map> fuzzyClassCache = new HashMap>(); + private final Map> fuzzyClassCache = new HashMap<>(); /** * Maps the forName cache. */ - private final Map> forNameCache = new HashMap>(); + private final Map> forNameCache = new HashMap<>(); /** * List of all URLs from which to pull classes. */ - private final Set urlCache = new HashSet(); + private final Set urlCache = new HashSet<>(); /** - * When a URL is added to urlCache, it is also initially added here. If - * there are any URLs in this set, they must be resolved first. + * When a URL is added to urlCache, it is also initially added here. If there are any URLs in this set, they must be + * resolved first. */ - private final Set dirtyURLs = new HashSet(); + private final Set dirtyURLs = new HashSet<>(); /** - * Cache for class subtypes. Whenever a new URL is added to the URL cache, - * this is cleared. + * Cache for class subtypes. Whenever a new URL is added to the URL cache, this is cleared. */ - private final Map, Set>> classSubtypeCache = new HashMap, Set>>(); + private final Map, Set>> classSubtypeCache = new HashMap<>(); /** - * Cache for class annotations. Whenever a new URL is added to the URL - * cache, this is cleared. + * Cache for class annotations. Whenever a new URL is added to the URL cache, this is cleared. */ - private final Map, Set>> classAnnotationCache = new HashMap, Set>>(); + private final Map, Set>> classAnnotationCache = new HashMap<>(); /** - * Cache for field annotations. Whenever a new URL is added to the URL - * cache, this is cleared. + * Cache for field annotations. Whenever a new URL is added to the URL cache, this is cleared. */ - private final Map, Set> fieldAnnotationCache = new HashMap, Set>(); + private final Map, Set> fieldAnnotationCache = new HashMap<>(); /** - * Cache for method annotations. Whenever a new URL is added to the URL - * cache, this is cleared. + * Cache for method annotations. Whenever a new URL is added to the URL cache, this is cleared. */ - private final Map, Set> methodAnnotationCache = new HashMap, Set>(); + private final Map, Set> methodAnnotationCache = new HashMap<>(); + /** + * Cache for constructor annotations. Whenever a new URL is added to the URL cache, this is cleared. + */ + private final Map, Set>> constructorAnnotationCache = new HashMap<>(); + private final Map, Class>, Set>> + classesWithAnnotationThatExtendCache = new HashMap<>(); + + /** + * Cache for mapping real classes to class mirrors. This is not cleared when a new URL is added, since the mapping + * would always be the same anyways. + */ + private final Map, ClassMirror> classToMirrorCache = new HashMap<>(); /** * By default null, but this can be set per instance. */ private ProgressIterator progressIterator = null; /** - * External cache. If added before discovery happens for a URL, this will - * cause the discovery process to be skipped entirely for a given URL. + * External cache. If added before discovery happens for a URL, this will cause the discovery process to be skipped + * entirely for a given URL. */ - private final Map preCaches = new HashMap(); + private final Map preCaches = new HashMap<>(); /** * If true, debug information will be printed out. */ private boolean debug; - + /** * May be null, but if set, is the cache retriever. */ private ClassDiscoveryCache classDiscoveryCache; - + /** - * Turns debug mode on. If true, data about what is happening is printed out, - * as well as timing information. - * @param on + * Turns debug mode on. If true, data about what is happening is printed out, as well as timing information. + * + * @param on */ - public void setDebugMode(boolean on){ + public void setDebugMode(boolean on) { debug = on; } /** - * Removes the cache for this URL. After calling this, it is ensured that - * the discovery methods won't be pulling from a cache. This is used during - * initial cache creation. + * Removes the cache for this URL. After calling this, it is ensured that the discovery methods won't be pulling + * from a cache. This is used during initial cache creation. * * @param url */ public void removePreCache(URL url) { - if (url == null) { + if(url == null) { throw new NullPointerException("url cannot be null"); } preCaches.remove(url); @@ -185,27 +197,26 @@ public void removePreCache(URL url) { * @param cache */ public void addPreCache(URL url, ClassDiscoveryURLCache cache) { - if (url == null) { + if(url == null) { throw new NullPointerException("url cannot be null"); } - if(debug){ - System.out.println("Adding precache for " + url); + if(debug) { + StreamUtils.GetSystemOut().println("Adding precache for " + url); } preCaches.put(url, cache); } - + /** - * Sets the class discovery cache. This is optional, but if set - * is used to potentially speed up caching. - * @param cache + * Sets the class discovery cache. This is optional, but if set is used to potentially speed up caching. + * + * @param cache */ - public void setClassDiscoveryCache(ClassDiscoveryCache cache){ + public void setClassDiscoveryCache(ClassDiscoveryCache cache) { this.classDiscoveryCache = cache; } /** - * Sets the progress iterator for when this class starts up. This is an - * optional operation. + * Sets the progress iterator for when this class starts up. This is an optional operation. * * @param progressIterator */ @@ -214,13 +225,12 @@ public void setProgressIterator(ProgressIterator progressIterator) { } /** - * Looks through all the URLs and pulls out all known classes, and caches - * them in the classCache object. + * Looks through all the URLs and pulls out all known classes, and caches them in the classCache object. */ private synchronized void doDiscovery() { - if (!dirtyURLs.isEmpty()) { + if(!dirtyURLs.isEmpty()) { Iterator it = dirtyURLs.iterator(); - while (it.hasNext()) { + while(it.hasNext()) { discover(it.next()); it.remove(); } @@ -228,55 +238,55 @@ private synchronized void doDiscovery() { } /** - * Does the class discovery for this particular URL. This should only be - * called by doDiscovery. Other internal methods should call doDiscovery, - * which handles looking through the dirtyURLs. + * Does the class discovery for this particular URL. This should only be called by doDiscovery. Other internal + * methods should call doDiscovery, which handles looking through the dirtyURLs. */ private synchronized void discover(URL rootLocation) { long start = System.currentTimeMillis(); - if(debug){ - System.out.println("Beginning discovery of " + rootLocation); + if(debug) { + StreamUtils.GetSystemOut().println("Beginning discovery of " + rootLocation); } try { //If the ClassDiscoveryCache is set, just use this. - if(classDiscoveryCache != null){ + if(classDiscoveryCache != null) { ClassDiscoveryURLCache cduc = classDiscoveryCache.getURLCache(rootLocation); preCaches.put(rootLocation, cduc); } - + String url; try { - url = URLDecoder.decode(rootLocation.toString(), "UTF8"); + url = URLDecoder.decode(rootLocation.toString(), "UTF-8"); } catch (UnsupportedEncodingException ex) { - url = URLDecoder.decode(rootLocation.toString()); + // apparently this should never happen, but we have to catch it anyway + url = null; } - - if (url == null) { + + if(url == null) { url = GetClassContainer(ClassDiscovery.class).toString(); } final File rootLocationFile; - if (!classCache.containsKey(rootLocation)) { - classCache.put(rootLocation, Collections.synchronizedSet(new HashSet>())); + if(!classCache.containsKey(rootLocation)) { + classCache.put(rootLocation, Collections.synchronizedSet(new HashSet<>())); } else { classCache.get(rootLocation).clear(); } final Set> mirrors = classCache.get(rootLocation); - if (preCaches.containsKey(rootLocation)) { - if(debug){ - System.out.println("Precache already contains this URL, so using it"); + if(preCaches.containsKey(rootLocation)) { + if(debug) { + StreamUtils.GetSystemOut().println("Precache already contains this URL, so using it"); } //No need, already got a cache for this url mirrors.addAll(preCaches.get(rootLocation).getClasses()); return; } - if(debug){ - System.out.println("Precache does not contain data for this URL, so scanning now."); + if(debug) { + StreamUtils.GetSystemOut().println("Precache does not contain data for this URL, so scanning now."); } url = url.replaceFirst("^jar:", ""); - if (url.endsWith("!/")) { + if(url.endsWith("!/")) { url = StringUtils.replaceLast(url, "!/", ""); } - if (url.startsWith("file:") && !url.endsWith(".jar")) { + if(url.startsWith("file:") && !url.endsWith(".jar")) { final AtomicInteger id = new AtomicInteger(0); // ExecutorService service = Executors.newFixedThreadPool(10, new ThreadFactory() { // @Override @@ -284,28 +294,30 @@ private synchronized void discover(URL rootLocation) { // return new Thread(r, "ClassDiscovery-Async-" + id.incrementAndGet()); // } // }); - + //Remove file: from the front String root = url.substring(5); rootLocationFile = new File(root); - List fileList = new ArrayList(); + List fileList = new ArrayList<>(); descend(new File(root), fileList); //Now, we have all the class files in the package. But, it's the absolute path //to all of them. We have to first remove the "front" part - for (File f : fileList) { + for(File f : fileList) { String file = f.toString(); - if (!file.matches(".*\\$(?:\\d)*\\.class") && file.endsWith(".class")) { + if(!file.matches(".*\\$(?:\\d)*\\.class") && file.endsWith(".class")) { InputStream stream = null; try { stream = FileUtil.readAsStream(new File(rootLocationFile, f.getAbsolutePath().replaceFirst(Pattern.quote(new File(root).getAbsolutePath() + File.separator), ""))); - ClassMirror cm = new ClassMirror(stream, new URL(url)); - mirrors.add(cm); + ClassReader reader = new ClassReader(stream); + ClassMirrorVisitor mirrorVisitor = new ClassMirrorVisitor(); + reader.accept(mirrorVisitor, ClassReader.SKIP_FRAMES); + mirrors.add(mirrorVisitor.getMirror(new URL(url))); } catch (IOException ex) { Logger.getLogger(ClassDiscovery.class.getName()).log(Level.SEVERE, null, ex); } finally { - if (stream != null) { + if(stream != null) { try { stream.close(); } catch (IOException ex) { @@ -322,7 +334,7 @@ private synchronized void discover(URL rootLocation) { // } catch (InterruptedException ex) { // Logger.getLogger(ClassDiscovery.class.getName()).log(Level.SEVERE, null, ex); // } - } else if (url.startsWith("file:") && url.endsWith(".jar")) { + } else if(url.startsWith("file:") && url.endsWith(".jar")) { //We are running from a jar url = url.replaceFirst("file:", ""); rootLocationFile = new File(url); @@ -331,10 +343,12 @@ private synchronized void discover(URL rootLocation) { zi.iterate(new ZipIterator.ZipIteratorCallback() { @Override public void handle(String filename, InputStream in) { - if (!filename.matches(".*\\$(?:\\d)*\\.class") && filename.endsWith(".class")) { + if(!filename.matches(".*\\$(?:\\d)*\\.class") && filename.endsWith(".class")) { try { - ClassMirror cm = new ClassMirror(in, rootLocationFile.toURI().toURL()); - mirrors.add(cm); + ClassReader reader = new ClassReader(in); + ClassMirrorVisitor mirrorVisitor = new ClassMirrorVisitor(); + reader.accept(mirrorVisitor, ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG); + mirrors.add(mirrorVisitor.getMirror(rootLocationFile.toURI().toURL())); } catch (IOException ex) { Logger.getLogger(ClassDiscovery.class.getName()).log(Level.SEVERE, null, ex); } @@ -348,18 +362,19 @@ public void handle(String filename, InputStream in) { } else { throw new RuntimeException("Unknown url type: " + rootLocation); } + } catch (RuntimeException e) { + e.printStackTrace(System.err); } finally { - if(debug){ - System.out.println("Scans finished for " + rootLocation + ", taking " + (System.currentTimeMillis() - start) + " ms."); + if(debug) { + StreamUtils.GetSystemOut().println("Scans finished for " + rootLocation + ", taking " + (System.currentTimeMillis() - start) + " ms."); } } } private ClassLoader defaultClassLoader = null; /** - * Sets the default class loader for the various load methods that are - * called without a ClassLoader. This is optional, and if not set, the class - * loader of this class is used. + * Sets the default class loader for the various load methods that are called without a ClassLoader. This is + * optional, and if not set, the class loader of this class is used. * * @param cl */ @@ -368,12 +383,13 @@ public void setDefaultClassLoader(ClassLoader cl) { } /** - * Gets the classloader set with {@link #setDefaultClassLoader(java.lang.ClassLoader)}, or the - * builtin default if none was specified ever. Regardless, never returns null. - * @return + * Gets the classloader set with {@link #setDefaultClassLoader(java.lang.ClassLoader)}, or the builtin default if + * none was specified ever. Regardless, never returns null. + * + * @return */ public ClassLoader getDefaultClassLoader() { - if (defaultClassLoader == null) { + if(defaultClassLoader == null) { return ClassDiscovery.class.getClassLoader(); } else { return defaultClassLoader; @@ -381,35 +397,50 @@ public ClassLoader getDefaultClassLoader() { } /** - * Adds a new discovery URL. This makes the URL eligible to be included when - * finding classes/methods/fields with the various other methods. If the URL - * already has been added, this has no effect. + * Adds the jar that the calling class is in to the discovery location. This is equivalent to running + * {@code addDiscoveryLocation(ClassDiscovery.GetClassContainer(this.getClass()));} + */ + public void addThisJar() { + this.addDiscoveryLocation(GetClassContainer(StackTraceUtils.getCallingClass())); + } + + /** + * Adds a new discovery URL. This makes the URL eligible to be included when finding classes/methods/fields with the + * various other methods. If the URL already has been added, this has no effect. * * @param url */ public synchronized void addDiscoveryLocation(URL url) { - if (url == null) { + if(url == null) { throw new NullPointerException("url cannot be null"); } - if (urlCache.contains(url)) { + + // Encode "+" character in URL, which is not encoded in URL creation like other special characters. + try { + url = new URL(url.toString().replace("+", "%2B")); + } catch (MalformedURLException e) { + throw new Error(e); + } + + if(urlCache.contains(url) && classCache.containsKey(url)) { //Already here, so just return. return; } urlCache.add(url); dirtyURLs.add(url); - classCache.put(url, new HashSet>()); + classCache.put(url, new HashSet<>()); } - + /** - * Searches one deep, finding all jar files, and adds them, using - * addDiscoveryLocation. If folder doesn't exist, is null, - * doesn't contain any jars, or otherwise can't be read, nothing happens. - * @param folder + * Searches one deep, finding all jar files, and adds them, using addDiscoveryLocation. If folder doesn't exist, is + * null, doesn't contain any jars, or otherwise can't be read, nothing happens. + * + * @param folder */ - public void addAllJarsInFolder(File folder){ - if(folder != null && folder.exists() && folder.isDirectory()){ - for(File f : folder.listFiles()){ - if(f.getName().endsWith(".jar")){ + public void addAllJarsInFolder(File folder) { + if(folder != null && folder.exists() && folder.isDirectory()) { + for(File f : folder.listFiles()) { + if(f.getName().endsWith(".jar")) { try { addDiscoveryLocation(f.toURI().toURL()); } catch (MalformedURLException ex) { @@ -419,18 +450,18 @@ public void addAllJarsInFolder(File folder){ } } } - + /** * Remove a discovery URL. Will invalidate caches. - * + * * @param url */ public synchronized void removeDiscoveryLocation(URL url) { - if (url == null) { + if(url == null) { throw new NullPointerException("url cannot be null"); } - - if (!urlCache.contains(url)) { + + if(!urlCache.contains(url)) { //Not here, so just return. return; } @@ -443,12 +474,10 @@ public synchronized void removeDiscoveryLocation(URL url) { } /** - * Clears the internal caches. This is called automatically when a new - * discovery location is added with addDiscoveryLocation, but this should be - * called if the caches could have become invalidated since the last load, - * as well as if the reference to any of the class loaders that loaded any - * classes during the course of using this instance need to be garbage - * collected. + * Clears the internal caches. This is called automatically when a new discovery location is added with + * addDiscoveryLocation, but this should be called if the caches could have become invalidated since the last load, + * as well as if the reference to any of the class loaders that loaded any classes during the course of using this + * instance need to be garbage collected. */ public void invalidateCaches() { classCache.clear(); @@ -458,133 +487,146 @@ public void invalidateCaches() { classAnnotationCache.clear(); fieldAnnotationCache.clear(); methodAnnotationCache.clear(); + constructorAnnotationCache.clear(); + classesWithAnnotationThatExtendCache.clear(); dirtyURLs.addAll(urlCache); } /** - * Returns a list of all known classes. The ClassMirror for each class is - * returned, and further examination can be done on each class, or loadClass - * can be called on the ClassMirror to get the actual Class object. No - * ClassLoaders are involved directly in this operation. + * Returns a list of all known classes. The ClassMirror for each class is returned, and further examination can be + * done on each class, or loadClass can be called on the ClassMirror to get the actual Class object. No ClassLoaders + * are involved directly in this operation. * * @return A list of ClassMirror objects for all known classes */ public Set> getKnownClasses() { doDiscovery(); - Set> ret = new HashSet>(); - for (URL url : urlCache) { + Set> ret = new HashSet<>(); + for(URL url : urlCache) { ret.addAll(getKnownClasses(url)); } return ret; } /** - * Gets all known classes, only within this URL. If this url isn't in the - * list of discovery locations, it is automatically added, via - * {@link #addDiscoveryLocation(java.net.URL)}. + * Gets all known classes, only within this URL. If this url isn't in the list of discovery locations, it is + * automatically added, via {@link #addDiscoveryLocation(java.net.URL)}. * * @param url * @return */ public List> getKnownClasses(URL url) { - if (url == null) { + if(url == null) { throw new NullPointerException("url cannot be null"); } - if (!classCache.containsKey(url)) { + if(!classCache.containsKey(url)) { addDiscoveryLocation(url); } doDiscovery(); - return new ArrayList>(classCache.get(url)); + return new ArrayList<>(classCache.get(url)); } /** - * Returns a list of known classes that extend the given superclass, or - * implement the given interface. + * Returns a list of known classes that extend the given superclass, or implement the given interface. * * @param * @param superType * @return */ + @SuppressWarnings("unchecked") public Set> getClassesThatExtend(Class superType) { - if (superType == java.lang.Object.class) { + if(superType == java.lang.Object.class) { //To avoid complication down the road, if this is the case, //just return all known classes here. - return (Set) getKnownClasses(); + // Ugh, this double cast though. This is definitely safe, since + // everything extends Object in java, but to get the compiler to + // shut up, we have to suppress warnings and double cast it. + return (Set>) (Set) getKnownClasses(); } - if (classSubtypeCache.containsKey(superType)) { - return new HashSet>((Set) classSubtypeCache.get(superType)); + if(classSubtypeCache.containsKey(superType)) { + return new HashSet<>((Set) classSubtypeCache.get(superType)); } doDiscovery(); - Set> mirrors = new HashSet>(); + Set> mirrors = new HashSet<>(); Set> knownClasses = (Set) getKnownClasses(); outer: - for (ClassMirror m : knownClasses) { - if(doesClassExtend(m, superType)){ + for(ClassMirror m : knownClasses) { + if(doesClassExtend(m, superType)) { mirrors.add(m); } } classSubtypeCache.put(superType, mirrors); return (Set) mirrors; } - + /** - * Returns true if subClass extends, implements, or is superClass. - * This searches the entire known class ecosystem, including the known ClassMirrors - * for this information. + * Returns true if subClass extends, implements, or is superClass. This searches the entire known class ecosystem, + * including the known ClassMirrors for this information. + * * @param subClass * @param superClass - * @return + * @return */ - public boolean doesClassExtend(ClassMirror subClass, Class superClass){ - if (subClass.directlyExtendsFrom(superClass)) { + @SuppressWarnings("unchecked") + public boolean doesClassExtend(ClassMirror subClass, Class superClass) { + if(subClass.directlyExtendsFrom(superClass)) { //Trivial case, so just add this now, then continue. return true; } //Well, crap, more complicated. Ok, so, the list of supers //can probably be walked up even further, so we need to find //the supers of these (and make sure it's not in the ClassMirror - //cache, to avoid loading classes unneccessarily) and then load + //cache, to avoid loading classes unnecessarily) and then load //the actual Class object for them. Essentially, this falls back //to loading the class when it //can't be found in the mirrors pool. - Set supers = new HashSet(); - //Get the superclass. If it's java.lang.Object, we're done. - ClassReferenceMirror su = subClass.getSuperClass(); - while (!su.getJVMName().equals("Ljava/lang/Object;")) { - supers.add(su); - ClassMirror find = getClassMirrorFromJVMName(su.getJVMName()); - if (find == null) { - try { - //Ok, have to Class.forName this one - Class clazz = ClassUtils.forCanonicalName(su.toString(), false, defaultClassLoader); - //We can just use isAssignableFrom now - if (superClass.isAssignableFrom(clazz)) { - return true; - } else { - //We need to add change the reference to su - su = new ClassReferenceMirror("L" + clazz.getSuperclass().getName().replace(".", "/") + ";"); + Set> supers = new HashSet<>(); + // Interfaces don't have a superclass. If they extend something, that's different. + if(!subClass.isInterface()) { + //Get the superclass. If it's java.lang.Object, we're done. + ClassReferenceMirror su = subClass.getSuperClass(); + while(!"Ljava/lang/Object;".equals(su.getJVMName())) { + supers.add(su); + ClassMirror find = getClassMirrorFromJVMName(su.getJVMName()); + if(find == null) { + try { + //Ok, have to Class.forName this one + Class clazz = ClassUtils.forCanonicalName(su.toString(), false, defaultClassLoader); + //We can just use isAssignableFrom now + if(superClass.isAssignableFrom(clazz)) { + return true; + } else { + //We need to add change the reference to su + su = new ClassReferenceMirror<>("L" + clazz.getSuperclass().getName().replace('.', '/') + ";"); + } + } catch (ClassNotFoundException ex) { + //Hmm, ok? I guess something bad happened, so let's break + //the loop and give up on this class. + return false; } - } catch (ClassNotFoundException ex) { - //Hmm, ok? I guess something bad happened, so let's break - //the loop and give up on this class. - return false; + } else { + su = find.getSuperClass(); + } + } + for(ClassReferenceMirror r : supers) { + // Look through the supers. If any of them equal the search class, return true + if(r.getJVMName().equals(ClassUtils.getJVMName(superClass))) { + return true; } - } else { - su = find.getSuperClass(); } } //Same thing now, but for interfaces - Deque interfaces = new ArrayDeque(); - Set handled = new HashSet(); + Deque> interfaces = new ArrayDeque<>(); + Set> handled = new HashSet<>(); interfaces.addAll(subClass.getInterfaces()); //Also have to add all the supers' interfaces too - for (ClassReferenceMirror r : supers) { - ClassMirror find = getClassMirrorFromJVMName(r.getJVMName()); - if (find == null) { + for(ClassReferenceMirror r : supers) { + ClassMirror find = getClassMirrorFromJVMName(r.getJVMName()); + if(find == null) { try { - Class clazz = Class.forName(r.toString()); - for (Class c : clazz.getInterfaces()) { - interfaces.add(new ClassReferenceMirror("L" + c.getName().replace(".", "/") + ";")); + Class clazz = Class.forName(r.toString()); + for(Class c : clazz.getInterfaces()) { + interfaces.add(new ClassReferenceMirror<>("L" + c.getName().replace('.', '/') + ";")); } } catch (ClassNotFoundException ex) { return false; @@ -593,25 +635,25 @@ public boolean doesClassExtend(ClassMirror subClass, Class superClass){ interfaces.addAll(find.getInterfaces()); } } - while (!interfaces.isEmpty()) { - ClassReferenceMirror in = interfaces.pop(); - if(ClassUtils.getJVMName(superClass).equals(in.getJVMName())){ + while(!interfaces.isEmpty()) { + ClassReferenceMirror in = interfaces.pop(); + if(ClassUtils.getJVMName(superClass).equals(in.getJVMName())) { //Early short circuit. We know it's in the the list already. return true; } - if (handled.contains(in)) { + if(handled.contains(in)) { continue; } handled.add(in); supers.add(in); - ClassMirror find = getClassMirrorFromJVMName(in.getJVMName()); - if (find != null) { + ClassMirror find = getClassMirrorFromJVMName(in.getJVMName()); + if(find != null) { interfaces.addAll(find.getInterfaces()); } else { try { //Again, have to check Class.forName - Class clazz = ClassUtils.forCanonicalName(in.toString(), false, getDefaultClassLoader()); - if (superClass.isAssignableFrom(clazz)) { + Class clazz = ClassUtils.forCanonicalName(in.toString(), false, getDefaultClassLoader()); + if(superClass.isAssignableFrom(clazz)) { return true; } } catch (ClassNotFoundException ex) { @@ -624,10 +666,9 @@ public boolean doesClassExtend(ClassMirror subClass, Class superClass){ } /** - * Unlike {@link #getClassesThatExtend(java.lang.Class)}, this actually - * loads the matching classes into PermGen, and returns a Set of these - * classes. This is useful if you are for sure going to use these classes - * immediately, and don't want to have to lazy load them individually. + * Unlike {@link #getClassesThatExtend(java.lang.Class)}, this actually loads the matching classes into PermGen, and + * returns a Set of these classes. This is useful if you are for sure going to use these classes immediately, and + * don't want to have to lazy load them individually. * * @param * @param superType @@ -638,10 +679,9 @@ public Set> loadClassesThatExtend(Class superType) { } /** - * Unlike {@link #getClassesThatExtend(java.lang.Class)}, this actually - * loads the matching classes into PermGen, and returns a Set of these - * classes. This is useful if you are for sure going to use these classes - * immediately, and don't want to have to lazy load them individually. + * Unlike {@link #getClassesThatExtend(java.lang.Class)}, this actually loads the matching classes into PermGen, and + * returns a Set of these classes. This is useful if you are for sure going to use these classes immediately, and + * don't want to have to lazy load them individually. * * @param * @param superType @@ -650,20 +690,20 @@ public Set> loadClassesThatExtend(Class superType) { * @return */ public Set> loadClassesThatExtend(Class superType, ClassLoader loader, boolean initialize) { - Set> set = new HashSet>(); - for (ClassMirror cm : getClassesThatExtend(superType)) { + Set> set = new HashSet<>(); + for(ClassMirror cm : getClassesThatExtend(superType)) { set.add(cm.loadClass(loader, initialize)); } return set; } - private ClassMirror getClassMirrorFromJVMName(String className) { - if (jvmNameToMirror.containsKey(className)) { + private ClassMirror getClassMirrorFromJVMName(String className) { + if(jvmNameToMirror.containsKey(className)) { return jvmNameToMirror.get(className); } - for (ClassMirror c : getKnownClasses()) { - if (("L" + c.getJVMClassName() + ";").equals(className)) { - jvmNameToMirror.put("L" + c.getJVMClassName() + ";", c); + for(ClassMirror c : getKnownClasses()) { + if(c.getJVMClassName().equals(className)) { + jvmNameToMirror.put(c.getJVMClassName(), c); return c; } } @@ -673,64 +713,109 @@ private ClassMirror getClassMirrorFromJVMName(String className) { } /** - * Returns a list of classes that have been annotated with the specified - * annotation. This will work with annotations that have been declared with - * the {@link RetentionPolicy#CLASS} property. + * Returns a list of classes that have been annotated with the specified annotation. This will work with annotations + * that have been declared with the {@link RetentionPolicy#CLASS} property. * * @param annotation * @return */ public Set> getClassesWithAnnotation(Class annotation) { - if (classAnnotationCache.containsKey(annotation)) { - return new HashSet>(classAnnotationCache.get(annotation)); + if(classAnnotationCache.containsKey(annotation)) { + return new HashSet<>(classAnnotationCache.get(annotation)); } doDiscovery(); - Set> mirrors = new HashSet>(); - for (ClassMirror m : getKnownClasses()) { - if (m.hasAnnotation(annotation)) { + Set> mirrors = new HashSet<>(); + for(ClassMirror m : getKnownClasses()) { + if(m.hasAnnotation(annotation)) { mirrors.add(m); } } classAnnotationCache.put(annotation, mirrors); return mirrors; } - + /** * Combines finding classes with a specified annotation, and classes that extend a certain type. + * * @param The type that will be returned, based on superClass * @param annotation The annotation that the classes should be tagged with * @param superClass The super class that the classes should extend * @return A set of class mirrors that match the criteria */ - public Set> getClassesWithAnnotationThatExtend(Class annotation, Class superClass){ - Set> mirrors = new HashSet<>(); - for(ClassMirror c : getClassesWithAnnotation(annotation)){ - if(doesClassExtend(c, superClass)){ - mirrors.add((ClassMirror)c); + @SuppressWarnings("unchecked") + public Set> getClassesWithAnnotationThatExtend(Class annotation, Class superClass) { + Pair, Class> id = new Pair<>(annotation, superClass); + if(classesWithAnnotationThatExtendCache.containsKey(id)) { + // This (insane) double cast is necessary, because the cache will certainly contain the value of the + // correct type, + // but there's no way for us to encode T into the generic type of the definition, so we just do this, + // lie to the compiler, and go about our merry way. We do the same below. + // I'm totally open to a better approach though. + return (Set>) (Object) classesWithAnnotationThatExtendCache.get(id); + } + Set> mirrors = new HashSet<>(); + for(ClassMirror c : getClassesWithAnnotation(annotation)) { + if(doesClassExtend(c, superClass)) { + mirrors.add((ClassMirror) c); } } + if(superClass.getAnnotation(annotation) != null) { + // The mechanism above won't automatically add this class, so we need to add it + // ourselves here. + mirrors.add(new ClassMirror<>(superClass)); + } + classesWithAnnotationThatExtendCache.put(id, (Set>) (Object) mirrors); return mirrors; } - + + /** + * Returns the ClassMirror for the given Class, if it exists in the ecosystem. This is useful for obtaining + * reflective information that ClassMirror provides, but Class doesn't. Note, however, this will only be able + * to find classes that were loaded into the ecosystem in the first place, which may preclude most classes in + * the actual ecosystem, including the core Java classes. + * @param + * @param clazz + * @return + * @throws NoClassDefFoundError + */ + public ClassMirror getMirrorFromClass(Class clazz) throws NoClassDefFoundError { + ClassMirror cm = classToMirrorCache.get(clazz); + if(cm == null) { + for(ClassMirror m : getKnownClasses()) { + if(m.getClassName().equals(clazz.getName().replace('$', '.'))) { + cm = m; + classToMirrorCache.put(clazz, cm); + break; + } + } + if(cm == null) { + throw new NoClassDefFoundError("ClassDiscovery was unable to locate the class " + + clazz + ". This can only load classes that are available to the ClassDiscovery system."); + } + } + return (ClassMirror) cm; + + } + /** - * Unlike {@link #getClassesWithAnnotationThatExtend(java.lang.Class, java.lang.Class)}, this actually - * loads the matching classes into PermGen, and returns a Set of these classes. - * This is useful if you are for sure going to use these classes immediately, and don't want to have - * to lazy load them individually. + * Unlike {@link #getClassesWithAnnotationThatExtend(java.lang.Class, java.lang.Class)}, this actually loads the + * matching classes into PermGen, and returns a Set of these classes. This is useful if you are for sure going to + * use these classes immediately, and don't want to have to lazy load them individually. + * * @param The type that will be returned, based on superClass * @param annotation The annotation that the classes should be tagged with * @param superClass The super class that the classes should extend * @return A set of classes that match the criteria */ - public Set> loadClassesWithAnnotationThatExtend(Class annotation, Class superClass){ + public Set> loadClassesWithAnnotationThatExtend(Class annotation, Class superClass) { return loadClassesWithAnnotationThatExtend(annotation, superClass, getDefaultClassLoader(), true); } - + /** - * Unlike {@link #getClassesWithAnnotationThatExtend(java.lang.Class, java.lang.Class)}, this actually - * loads the matching classes into PermGen, and returns a Set of these classes. - * This is useful if you are for sure going to use these classes immediately, and don't want to have - * to lazy load them individually. + * Unlike {@link #getClassesWithAnnotationThatExtend(java.lang.Class, java.lang.Class)}, this actually loads the + * matching classes into PermGen, and returns a Set of these classes. This is useful if you are for sure going to + * use these classes immediately, and don't want to have to lazy load them individually. + * * @param The type that will be returned, based on superClass * @param annotation The annotation that the classes should be tagged with * @param superClass The super class that the classes should extend @@ -738,9 +823,9 @@ public Set> loadClassesWithAnnotationThatExtend(Class Set> loadClassesWithAnnotationThatExtend(Class annotation, Class superClass, ClassLoader loader, boolean initialize){ - Set> set = new HashSet>(); - for (ClassMirror cm : getClassesWithAnnotationThatExtend(annotation, superClass)) { + public Set> loadClassesWithAnnotationThatExtend(Class annotation, Class superClass, ClassLoader loader, boolean initialize) { + Set> set = new HashSet<>(); + for(ClassMirror cm : getClassesWithAnnotationThatExtend(annotation, superClass)) { try { set.add(cm.loadClass(loader, initialize)); } catch (NoClassDefFoundError e) { @@ -752,59 +837,63 @@ public Set> loadClassesWithAnnotationThatExtend(Class loadClassesWithAnnotation(Class annotation) { + public Set> loadClassesWithAnnotation(Class annotation) { return loadClassesWithAnnotation(annotation, getDefaultClassLoader(), true); } /** - * Unlike {@link #getClassesWithAnnotation(java.lang.Class)}, this actually - * loads the matching classes into PermGen, and returns a Set of these - * classes. This is useful if you are for sure going to use these classes - * immediately, and don't want to have to lazy load them individually. + * Unlike {@link #getClassesWithAnnotation(java.lang.Class)}, this actually loads the matching classes into PermGen, + * and returns a Set of these classes. This is useful if you are for sure going to use these classes immediately, + * and don't want to have to lazy load them individually. * * @param annotation * @param loader * @param initialize * @return */ - public Set loadClassesWithAnnotation(Class annotation, ClassLoader loader, boolean initialize) { - Set set = new HashSet(); - for (ClassMirror cm : getClassesWithAnnotation(annotation)) { + public Set> loadClassesWithAnnotation(Class annotation, ClassLoader loader, boolean initialize) { + Set> set = new HashSet<>(); + for(ClassMirror cm : getClassesWithAnnotation(annotation)) { try { set.add(cm.loadClass(loader, initialize)); } catch (NoClassDefFoundError e) { - //Ignore this for now? - //throw new Error("While trying to process " + cm.toString() + ", an error occurred.", e); + if(IS_DEBUG) { + // This is tough. Normally, we really want to ignore this error, but during development, it can be + // a critical error to see to diagnose a very hard to find error. So we compromise here, and only + // print error details out while in debug mode. + System.err.println("While trying to process " + cm.toString() + ", an error occurred. It is" + + " probably safe to ignore this error, but if you're debugging to figure out why an" + + " expected class is not showing up, then this is probably why."); + e.printStackTrace(System.err); + } } } return set; } /** - * Returns a list of fields that have been annotated with the specified - * annotation. This will work with annotations that have been declared with - * the {@link RetentionPolicy#CLASS} property. + * Returns a list of fields that have been annotated with the specified annotation. This will work with annotations + * that have been declared with the {@link RetentionPolicy#CLASS} property. * * @param annotation * @return */ public Set getFieldsWithAnnotation(Class annotation) { - if (fieldAnnotationCache.containsKey(annotation)) { - return new HashSet(fieldAnnotationCache.get(annotation)); + if(fieldAnnotationCache.containsKey(annotation)) { + return new HashSet<>(fieldAnnotationCache.get(annotation)); } doDiscovery(); - Set mirrors = new HashSet(); - for (ClassMirror m : getKnownClasses()) { - for (FieldMirror f : m.getFields()) { - if (f.hasAnnotation(annotation)) { + Set mirrors = new HashSet<>(); + for(ClassMirror m : getKnownClasses()) { + for(FieldMirror f : m.getFields()) { + if(f.hasAnnotation(annotation)) { mirrors.add(f); } } @@ -814,22 +903,55 @@ public Set getFieldsWithAnnotation(Class anno } /** - * Returns a list of methods that have been annotated with the specified - * annotation. This will work with annotations that have been declared with - * the {@link RetentionPolicy#CLASS} property. + * Unlike {@link #getFieldsWithAnnotation(java.lang.Class)}, this actually loads the matching field's containing + * classes into PermGen, and returns a Set of Field objects. This is useful if you are for sure going to use these + * fields immediately, and don't want to have to lazy load them individually. + * + * @param annotation + * @return + */ + public Set loadFieldsWithAnnotation(Class annotation) { + return loadFieldsWithAnnotation(annotation, ClassDiscovery.class.getClassLoader(), true); + } + + /** + * Unlike {@link #getFieldsWithAnnotation(java.lang.Class)}, this actually loads the matching field's containing + * classes into PermGen, and returns a Set of Field objects. This is useful if you are for sure going to use these + * fields immediately, and don't want to have to lazy load them individually. + * + * @param annotation + * @param loader + * @param initialize + * @return + */ + public Set loadFieldsWithAnnotation(Class annotation, ClassLoader loader, boolean initialize) { + Set ret = new HashSet<>(); + for(FieldMirror fm : getFieldsWithAnnotation(annotation)) { + try { + Field f = fm.loadField(loader, initialize); + ret.add(f); + } catch (ClassNotFoundException ex) { + throw new NoClassDefFoundError(ex.getMessage()); + } + } + return ret; + } + + /** + * Returns all methods, including constructors, with the specified annotations * * @param annotation * @return */ public Set getMethodsWithAnnotation(Class annotation) { - if (methodAnnotationCache.containsKey(annotation)) { - return new HashSet(methodAnnotationCache.get(annotation)); + if(methodAnnotationCache.containsKey(annotation)) { + return new HashSet<>(methodAnnotationCache.get(annotation)); } doDiscovery(); - Set mirrors = new HashSet(); - for (ClassMirror m : getKnownClasses()) { - for (MethodMirror mm : m.getMethods()) { - if (mm.hasAnnotation(annotation)) { + Set mirrors = new HashSet<>(); + for(ClassMirror m : getKnownClasses()) { + for(MethodMirror mm : m.getMethods()) { + if(mm.hasAnnotation(annotation)) { mirrors.add(mm); } } @@ -839,11 +961,9 @@ public Set getMethodsWithAnnotation(Class an } /** - * Unlike {@link #getMethodsWithAnnotation(java.lang.Class)}, this actually - * loads the matching method's containing classes into PermGen, and returns - * a Set of Method objects. This is useful if you are for sure going to use - * these methods immediately, and don't want to have to lazy load them - * individually. + * Unlike {@link #getMethodsWithAnnotation(java.lang.Class)}, this actually loads the matching method's containing + * classes into PermGen, and returns a Set of Method objects. This is useful if you are for sure going to use these + * methods immediately, and don't want to have to lazy load them individually. * * @param annotation * @return @@ -853,11 +973,9 @@ public Set loadMethodsWithAnnotation(Class annotat } /** - * Unlike {@link #getMethodsWithAnnotation(java.lang.Class)}, this actually - * loads the matching method's containing classes into PermGen, and returns - * a Set of Method objects. This is useful if you are for sure going to use - * these methods immediately, and don't want to have to lazy load them - * individually. + * Unlike {@link #getMethodsWithAnnotation(java.lang.Class)}, this actually loads the matching method's containing + * classes into PermGen, and returns a Set of Method objects. This is useful if you are for sure going to use these + * methods immediately, and don't want to have to lazy load them individually. * * @param annotation * @param loader @@ -866,30 +984,97 @@ public Set loadMethodsWithAnnotation(Class annotat */ public Set loadMethodsWithAnnotation(Class annotation, ClassLoader loader, boolean initialize) { try { - Set set = new HashSet(); - for (MethodMirror mm : getMethodsWithAnnotation(annotation)) { + Set set = new HashSet<>(); + for(MethodMirror mm : getMethodsWithAnnotation(annotation)) { set.add(mm.loadMethod(loader, initialize)); } return set; } catch (ClassNotFoundException ex) { - throw new NoClassDefFoundError(); + throw new NoClassDefFoundError(ex.getMessage()); } } /** - * Returns the ClassMirror object for a given class name. Either the JVM - * name, or canonical name works. + * Returns all ConstructorMirrors with the given annotation. + * + * @param annotation + * @return + */ + public Set> getConstructorsWithAnnotation(Class annotation) { + if(constructorAnnotationCache.containsKey(annotation)) { + return new HashSet<>(constructorAnnotationCache.get(annotation)); + } + doDiscovery(); + Set> mirrors = new HashSet<>(); + for(ClassMirror m : getKnownClasses()) { + for(ConstructorMirror mm : m.getConstructors()) { + if(mm.hasAnnotation(annotation)) { + mirrors.add(mm); + } + } + } + constructorAnnotationCache.put(annotation, mirrors); + return mirrors; + } + + /** + * Loads all Constructors with the given annotation. + * + * @param annotation + * @return + */ + public Set> loadConstructorsWithAnnotation(Class annotation) { + return loadConstructorsWithAnnotation(annotation, getDefaultClassLoader(), true); + } + + /** + * Loads all Constructors with the given annotation, using the specified classloader. + * + * @param annotation + * @param loader + * @param initialize + * @return + */ + public Set> loadConstructorsWithAnnotation(Class annotation, ClassLoader loader, boolean initialize) { + Set> set = new HashSet<>(); + for(AbstractMethodMirror m : getConstructorsWithAnnotation(annotation)) { + try { + Class c = m.getDeclaringClass().loadClass(loader, initialize); + outer: + for(Constructor cc : c.getDeclaredConstructors()) { + Class[] params = cc.getParameterTypes(); + if(m.getParams().size() != params.length) { + continue; + } + for(int i = 0; i < params.length; i++) { + ClassReferenceMirror crm = m.getParams().get(i); + ClassReferenceMirror crm2 = new ClassReferenceMirror<>(ClassUtils.getJVMName(params[i])); + if(!crm.equals(crm2)) { + continue outer; + } + } + set.add(cc); + } + } catch (ClassNotFoundException ex) { + throw new NoClassDefFoundError(); + } + } + return set; + } + + /** + * Returns the ClassMirror object for a given class name. Either the JVM name, or canonical name works. * * @param className * @return * @throws java.lang.ClassNotFoundException */ - public ClassMirror forName(String className) throws ClassNotFoundException { - if (forNameCache.containsKey(className)) { + public ClassMirror forName(String className) throws ClassNotFoundException { + if(forNameCache.containsKey(className)) { return forNameCache.get(className); } - for (ClassMirror c : getKnownClasses()) { - if (c.getClassName().equals(className) || c.getJVMClassName().equals(className)) { + for(ClassMirror c : getKnownClasses()) { + if(c.getClassName().equals(className) || c.getJVMClassName().equals(className)) { forNameCache.put(className, c); return c; } @@ -898,22 +1083,20 @@ public ClassMirror forName(String className) throws ClassNotFoundException { } /** - * Calls forFuzzyName with initialize true, and the class loader used to - * load this class. + * Calls forFuzzyName with initialize true, and the class loader used to load this class. * * @param packageRegex * @param className * @return */ - public ClassMirror forFuzzyName(String packageRegex, String className) { + public ClassMirror forFuzzyName(String packageRegex, String className) { return forFuzzyName(packageRegex, className, true, getDefaultClassLoader()); } /** - * Returns a class given a "fuzzy" package name, that is, the package name - * provided is a regex. The class name must match exactly, but the package - * name will be the closest match, or undefined if there is no clear - * candidate. If no matches are found, null is returned. + * Returns a class given a "fuzzy" package name, that is, the package name provided is a regex. The class name must + * match exactly, but the package name will be the closest match, or undefined if there is no clear candidate. If no + * matches are found, null is returned. * * @param packageRegex * @param className @@ -921,29 +1104,29 @@ public ClassMirror forFuzzyName(String packageRegex, String className) { * @param classLoader * @return */ - public ClassMirror forFuzzyName(String packageRegex, String className, boolean initialize, ClassLoader classLoader) { + public ClassMirror forFuzzyName(String packageRegex, String className, boolean initialize, ClassLoader classLoader) { String index = packageRegex + className; - if (fuzzyClassCache.containsKey(index)) { + if(fuzzyClassCache.containsKey(index)) { return fuzzyClassCache.get(index); } - Set found = new HashSet(); + Set> found = new HashSet<>(); Set> searchSpace = getKnownClasses(); - for (ClassMirror c : searchSpace) { - if (c.getPackage().getName().matches(packageRegex) && c.getSimpleName().equals(className)) { + for(ClassMirror c : searchSpace) { + if(c.getPackage().getName().matches(packageRegex) && c.getSimpleName().equals(className)) { found.add(c); } } - ClassMirror find; - if (found.size() == 1) { + ClassMirror find; + if(found.size() == 1) { find = found.iterator().next(); - } else if (found.isEmpty()) { + } else if(found.isEmpty()) { find = null; } else { - ClassMirror candidate = null; + ClassMirror candidate = null; int max = Integer.MAX_VALUE; - for (ClassMirror f : found) { + for(ClassMirror f : found) { int distance = StringUtils.LevenshteinDistance(f.getPackage().getName(), packageRegex); - if (distance < max) { + if(distance < max) { candidate = f; max = distance; } @@ -955,39 +1138,38 @@ public ClassMirror forFuzzyName(String packageRegex, String className, boolean i } private static void descend(File start, List fileList) { - if (start.isFile()) { - if (start.getName().endsWith(".class")) { + if(start.isFile()) { + if(start.getName().endsWith(".class")) { fileList.add(start); } } else { - File [] list = start.listFiles(); - if(list == null){ - System.out.println("Could not list files in " + start); + File[] list = start.listFiles(); + if(list == null) { + StreamUtils.GetSystemOut().println("Could not list files in " + start); return; } - for (File child : start.listFiles()) { + for(File child : start.listFiles()) { descend(child, fileList); } } } /** - * Returns the container url for this class. This varies based on whether or - * not the class files are in a zip/jar or not, so this method standardizes - * that. The method may return null, if the class is a dynamically generated - * class (perhaps with asm, or a proxy class) + * Returns the container url for this class. This varies based on whether or not the class files are in a zip/jar or + * not, so this method standardizes that. The method may return null, if the class is a dynamically generated class + * (perhaps with asm, or a proxy class) * * @param c * @return */ - public static URL GetClassContainer(Class c) { - if (c == null) { + public static URL GetClassContainer(Class c) { + if(c == null) { throw new NullPointerException("The Class passed to this method may not be null"); } - while (c.isMemberClass() || c.isAnonymousClass()) { + while(c.isMemberClass() || c.isAnonymousClass()) { c = c.getEnclosingClass(); //Get the actual enclosing file } - if (c.getProtectionDomain().getCodeSource() == null) { + if(c.getProtectionDomain().getCodeSource() == null) { //This is a proxy or other dynamically generated class, and has no physical container, //so just return null. return null; @@ -997,21 +1179,51 @@ public static URL GetClassContainer(Class c) { String thisClass = c.getResource(c.getSimpleName() + ".class").toString(); try { try { - packageRoot = StringUtils.replaceLast(thisClass, Pattern.quote(c.getName().replaceAll("\\.", "/") + ".class"), ""); + packageRoot = StringUtils.replaceLast(thisClass, Pattern.quote(c.getName().replace('.', '/') + ".class"), ""); } catch (Exception e) { - //Hmm, ok, try this then + + // Get location through protection domain. packageRoot = c.getProtectionDomain().getCodeSource().getLocation().toString(); } - packageRoot = URLDecoder.decode(packageRoot, "UTF-8"); - if (packageRoot.matches("jar:file:.*!/")) { - packageRoot = StringUtils.replaceLast(packageRoot, "!/", ""); - packageRoot = packageRoot.replaceFirst("jar:", ""); + + // Encode "+" character in URL, which is not encoded in URL creation like other special characters. + packageRoot = packageRoot.replace("+", "%2B"); + + // Strip jar file label and suffix. + if(packageRoot.matches("jar:.*!/")) { + packageRoot = packageRoot.substring("jar:".length(), packageRoot.length() - "!/".length()); } + + // Return encoded URL. return new URL(packageRoot); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("While interrogating " + c.getName() + ", an unexpected exception was thrown.", e); } catch (MalformedURLException e) { throw new RuntimeException("While interrogating " + c.getName() + ", an unexpected exception was thrown for potential URL: \"" + packageRoot + "\"", e); } } + + private static final Map, Class>, Annotation> ANNOTATION_CACHE = + new HashMap<>(); + + /** + * {@code Class.getAnnotation} is relatively slow, so this provides a utility cache in front of it, so that + * multiple calls will not incur the penalty hit. Only the first call does the lookup. + * + * Unlike the other methods in this class, there is no way to clear the cache, because this only works with + * concrete Java classes, and classes can't change annotations at runtime. + * @param The annotation type + * @param clazz The class to find the annotation on + * @param annotation The annotation class + * @return The annotation, or null if the specified class does not have that annotation. + */ + public static T GetClassAnnotation(Class clazz, Class annotation) { + Pair, Class> pair = new Pair(clazz, annotation); + Annotation t = ANNOTATION_CACHE.get(pair); + if(t == null) { + t = clazz.getAnnotation(annotation); + ANNOTATION_CACHE.put(pair, t); + } + // This cast should always work, but since we're shoving all the annotations into the cache, the compiler + // can't know that the returned type will be T. + return (T) t; + } } diff --git a/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassDiscoveryCache.java b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassDiscoveryCache.java index a147565000..4f54c9435b 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassDiscoveryCache.java +++ b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassDiscoveryCache.java @@ -1,4 +1,3 @@ - package com.laytonsmith.PureUtilities.ClassLoading; import com.laytonsmith.PureUtilities.Common.StringUtils; @@ -12,8 +11,6 @@ import java.net.URL; import java.net.URLDecoder; import java.security.MessageDigest; -import java.util.Enumeration; -import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.logging.Level; import java.util.logging.Logger; @@ -21,83 +18,84 @@ import java.util.zip.ZipOutputStream; /** - * This file represents a location on disk that can be used by the ClassDiscovery - * class to facilitate caching. Files will be automatically managed by this class, - * and it provides high level functions for getting a cache, regardless of whether - * or not it actually exists yet. + * This file represents a location on disk that can be used by the ClassDiscovery class to facilitate caching. Files + * will be automatically managed by this class, and it provides high level functions for getting a cache, regardless of + * whether or not it actually exists yet. */ public class ClassDiscoveryCache { - + /** - * This is the name of the jar annotation file. getResource(ClassDiscovery.OUTPUT_FILENAME) should - * return the file that was output during the build, for this jar for sure. Third party libs - * may not be using the same convention though, so this will fail. Regardless, the system should - * still function, though it will have to do one time cache setup first. + * This is the name of the jar annotation file. getResource(ClassDiscovery.OUTPUT_FILENAME) should return the file + * that was output during the build, for this jar for sure. Third party libs may not be using the same convention + * though, so this will fail. Regardless, the system should still function, though it will have to do one time cache + * setup first. */ public static final String OUTPUT_FILENAME = "jarInfo.ser"; - + /** * How much of the file we read in to hash to check for collisions. */ private static final int READ_SIZE = 2048; - private File cacheDir; + private final File cacheDir; private ProgressIterator progress; private Logger logger; - + /** - * Creates a new ClassDiscoveryCache. The File is the location on - * disk which is used to write the cache files to. - * @param cacheDir + * Creates a new ClassDiscoveryCache. The File is the location on disk which is used to write the cache files to. + * + * @param cacheDir */ - public ClassDiscoveryCache(File cacheDir){ + public ClassDiscoveryCache(File cacheDir) { this.cacheDir = cacheDir; } - + /** * If not null, informational output is logged to this logger. - * @param logger + * + * @param logger */ - public void setLogger(Logger logger){ + public void setLogger(Logger logger) { this.logger = logger; } - + + private static final Object CACHE_WRITE_LOCK = new Object(); + /** - * Given a file location, retrieves the ClassDiscoveryURLCache from it. - * If it is a jar, the file is hashed, and checked for a local cache copy, - * and if one exists, that cache is returned. - * If not, the jar is scanned for a jarInfo.ser. If one exists, it is returned. - * Otherwise, the jar is scanned, a local cache is saved to disk, then returned. - * - * No exceptions will be thrown from this class, if something fails, it will fall back - * to ultimately just regenerating the cache from source. - * @param fromClassLocation The jar to be cached. Note that if this doesn't - * denote a jar, the cache will not be written to disk, however a URLCache will - * be returned none-the-less. - * @return + * Given a file location, retrieves the ClassDiscoveryURLCache from it. If it is a jar, the file is hashed, and + * checked for a local cache copy, and if one exists, that cache is returned. If not, the jar is scanned for a + * jarInfo.ser. If one exists, it is returned. Otherwise, the jar is scanned, a local cache is saved to disk, then + * returned. + * + * No exceptions will be thrown from this class, if something fails, it will fall back to ultimately just + * regenerating the cache from source. + * + * @param fromClassLocation The jar to be cached. Note that if this doesn't denote a jar, the cache will not be + * written to disk, however a URLCache will be returned none-the-less. + * @return */ - public ClassDiscoveryURLCache getURLCache(URL fromClassLocation){ - if(fromClassLocation.toString().endsWith(".jar")){ + public ClassDiscoveryURLCache getURLCache(URL fromClassLocation) { + if(fromClassLocation.toString().endsWith(".jar")) { File cacheOutputName = null; - + try { File jarFile = new File(URLDecoder.decode(fromClassLocation.getFile(), "UTF8")); - + byte[] data; - try (FileInputStream fis = new FileInputStream(jarFile)) { + try(FileInputStream fis = new FileInputStream(jarFile)) { data = new byte[READ_SIZE]; fis.read(data); } - + MessageDigest digest = java.security.MessageDigest.getInstance("MD5"); digest.update(data); - + String fileName = StringUtils.toHex(digest.digest()); cacheOutputName = new File(cacheDir, fileName); - if(cacheOutputName.exists()){ + if(cacheOutputName.exists()) { //Cool, already exists, so we'll just return this. //Note that we write the data out as a zip, since it is - //huge otherwise, and compresses quite well, so we have + //huge otherwise, and compresses quite well, so we have //to read it in as a zip now. ZipReader cacheReader = new ZipReader(new File(cacheOutputName, "data")); return new ClassDiscoveryURLCache(fromClassLocation, cacheReader.getInputStream()); @@ -107,43 +105,41 @@ public ClassDiscoveryURLCache getURLCache(URL fromClassLocation){ } catch (Exception ex) { //Hmm. Ok, well, we'll just regenerate. } - + JarFile jfile; try { jfile = new JarFile(URLDecoder.decode(fromClassLocation.getFile(), "UTF8")); - - for (Enumeration ent = jfile.entries(); ent.hasMoreElements();) { - JarEntry entry = ent.nextElement(); - - if (entry.getName().equals(ClassDiscoveryCache.OUTPUT_FILENAME)) { - InputStream is = jfile.getInputStream(entry); - - try { - return new ClassDiscoveryURLCache(fromClassLocation, is); - } catch (Exception ex) { - //Do nothing, we'll just re-load from disk. - } + + InputStream is = jfile.getInputStream(new ZipEntry(OUTPUT_FILENAME)); + if(is != null) { + try { + return new ClassDiscoveryURLCache(fromClassLocation, is); + } catch (Exception ex) { + // } } } catch (IOException ex) { Logger.getLogger(ClassDiscoveryCache.class.getName()).log(Level.SEVERE, null, ex); } - - if(logger != null){ + + if(logger != null) { logger.log(Level.INFO, "Performing one time scan of {0}, this may take a few moments.", fromClassLocation); } - + ClassDiscoveryURLCache cache = new ClassDiscoveryURLCache(fromClassLocation, progress); - - if(cacheOutputName != null){ + + if(cacheOutputName != null) { try { - try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(cacheOutputName, false))) { - zos.putNextEntry(new ZipEntry("data")); - cache.writeDescriptor(zos); + synchronized(CACHE_WRITE_LOCK) { + cacheOutputName.getParentFile().mkdirs(); + try(ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(cacheOutputName, false))) { + zos.putNextEntry(new ZipEntry("data")); + cache.writeDescriptor(zos); + } } } catch (IOException ex) { //Well, we couldn't write it out, so report the error, but continue anyways. - if(logger != null){ + if(logger != null) { logger.log(Level.SEVERE, null, ex); } else { //Report errors even if the logger passed in is null. @@ -151,14 +147,14 @@ public ClassDiscoveryURLCache getURLCache(URL fromClassLocation){ } } } - + return cache; } else { return new ClassDiscoveryURLCache(fromClassLocation, progress); } } - - public void setProgressIterator(ProgressIterator progress){ + + public void setProgressIterator(ProgressIterator progress) { this.progress = progress; } } diff --git a/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassDiscoveryURLCache.java b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassDiscoveryURLCache.java index d87da800f8..1aca072e68 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassDiscoveryURLCache.java +++ b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassDiscoveryURLCache.java @@ -1,4 +1,3 @@ - package com.laytonsmith.PureUtilities.ClassLoading; import com.laytonsmith.PureUtilities.ClassLoading.ClassMirror.ClassMirror; @@ -14,33 +13,32 @@ import java.util.List; /** - * This file represents a jar, and can tell you what annotations - * are available on each class, method, and field. This class has methods - * to serialize and deserialize from json, a descriptor, which can be used to - * rebuild this class with. + * This file represents a jar, and can tell you what annotations are available on each class, method, and field. This + * class has methods to serialize and deserialize from json, a descriptor, which can be used to rebuild this class with. */ public class ClassDiscoveryURLCache { - + private final List> list; /** - * Creates a new ClassDiscoveryURLCache. This operation may - * take a long time, depending on the size of the url that needs scanning. - * @param url + * Creates a new ClassDiscoveryURLCache. This operation may take a long time, depending on the size of the url that + * needs scanning. + * + * @param url */ - public ClassDiscoveryURLCache(URL url){ - this(url, (ProgressIterator)null); + public ClassDiscoveryURLCache(URL url) { + this(url, (ProgressIterator) null); } - + /** - * Creates a new ClassDiscoveryURLCache. This operation - * may take a long time, depending on the size of the url that needs scanning. - * The ProgressIterator can be null, but if provided is passed into the - * internal ClassDiscovery object that is used. + * Creates a new ClassDiscoveryURLCache. This operation may take a long time, depending on the size of the url that + * needs scanning. The ProgressIterator can be null, but if provided is passed into the internal ClassDiscovery + * object that is used. + * * @param url - * @param progress + * @param progress */ - public ClassDiscoveryURLCache(URL url, ProgressIterator progress){ + public ClassDiscoveryURLCache(URL url, ProgressIterator progress) { list = new ArrayList>(); ClassDiscovery discovery = new ClassDiscovery(); discovery.setProgressIterator(progress); @@ -48,70 +46,78 @@ public ClassDiscoveryURLCache(URL url, ProgressIterator progress){ //we would get stuck in an infinite loop. discovery.setClassDiscoveryCache(null); discovery.addDiscoveryLocation(url); - - for(ClassMirror m : discovery.getKnownClasses(url)){ + + for(ClassMirror m : discovery.getKnownClasses(url)) { ReflectionUtils.set(ClassMirror.class, m, "originalURL", url); list.add(m); } } - + /** - * Creates a new ClassDiscoveryURLCache object from a descriptor that was - * created earlier with writeDescriptor. The url may be null, but if - * provided, will be used as a fallback in case an error occurs - * with the descriptor. + * Creates a new ClassDiscoveryURLCache object from a descriptor that was created earlier with writeDescriptor. The + * url may be null, but if provided, will be used as a fallback in case an error occurs with the descriptor. + * * @param url * @param descriptor - * @throws IOException - * @throws java.lang.ClassNotFoundException + * @throws IOException + * @throws java.lang.ClassNotFoundException */ - public ClassDiscoveryURLCache(URL url, InputStream descriptor) throws IOException, ClassNotFoundException{ - List> _list; + public ClassDiscoveryURLCache(URL url, InputStream descriptor) throws IOException, ClassNotFoundException { + List> list; ObjectInputStream ois = new ObjectInputStream(descriptor); try { - _list = (List>) ois.readObject(); - } catch(ClassNotFoundException ex){ - if(url != null){ + // This is the single most inefficient call in the startup sequence. It may be worth figuring out another + // way of doing this that doesn't require unzipping the descriptor first, perhaps unzipping it, putting + // it in the cache, and using that instead? Then the complexity will be gone on the second run on. Though + // it's not clear to me that this will have that great of an effect. If not, it might be better to come up + // with a simpler protocol for describing the object, and then building it manually, instead of using the + // default java serialization mechanisms. Alternatively to that, it might be worth looking into alternative + // serialization libraries, instead of using default java serialization. + list = (List>) ois.readObject(); + } catch (ClassNotFoundException ex) { + if(url != null) { //We can recover from this one, but it won't be instant. - _list = new ClassDiscoveryURLCache(url).list; + list = new ClassDiscoveryURLCache(url).list; } else { throw ex; } } ois.close(); - for (ClassMirror m : _list) { + for(ClassMirror m : list) { ReflectionUtils.set(ClassMirror.class, m, "originalURL", url); } - - this.list = _list; + + this.list = list; } - - public void writeDescriptor(OutputStream out) throws IOException{ + + public void writeDescriptor(OutputStream out) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(out); - oos.writeObject(list); + oos.writeObject(this.list); oos.close(); } - + @Override - public String toString(){ - return "[" + ClassDiscoveryURLCache.class.getSimpleName() + ": " + list.size() + "]"; + public String toString() { + return "[" + ClassDiscoveryURLCache.class.getSimpleName() + ": " + this.list.size() + "]"; } - + /** * Package private, no copy is made. + * * @return */ - /* package */ List> getClasses(){ + /* package */ List> getClasses() { return list; } - + /** * Returns the classes in this cache. - * @return + * + * @return */ - public List getClassList(){ + public List getClassList() { return new ArrayList(list); } - + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/AbstractElementMirror.java b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/AbstractElementMirror.java index 1fb8c2972d..7dc91f38ad 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/AbstractElementMirror.java +++ b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/AbstractElementMirror.java @@ -1,10 +1,11 @@ - package com.laytonsmith.PureUtilities.ClassLoading.ClassMirror; import com.laytonsmith.PureUtilities.Common.ClassUtils; import java.io.Serializable; import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; import java.lang.reflect.Field; +import java.lang.reflect.Member; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; @@ -14,12 +15,11 @@ * This is the superclass of any element type, such as a field or method. */ abstract class AbstractElementMirror implements Serializable { + /** - * Version History: - * 1 - Initial version - * 2 - Parent was added, and it cannot be null. This is an incompatible change, and - * all extensions will need to be recompiled to get the compilation caching benefit. - * (Old caches will fail, and cause a re-scan, but will work.) + * Version History: 1 - Initial version 2 - Parent was added, and it cannot be null. This is an incompatible change, + * and all extensions will need to be recompiled to get the compilation caching benefit. (Old caches will fail, and + * cause a re-scan, but will work.) */ private static final long serialVersionUID = 2L; /** @@ -31,51 +31,73 @@ abstract class AbstractElementMirror implements Serializable { */ protected final String name; /** - * The type of the element, or in the case of methods or other - * composite types, the return type. + * The type of the element, or in the case of methods or other composite types, the return type. */ protected final ClassReferenceMirror type; /** - * Any annotations on the element. This isn't final, because - * the fields and methods are created before they necessarily know their annotations. + * Any annotations on the element. This isn't final, because the fields and methods are created before they + * necessarily know their annotations. */ protected List annotations; /** * The parent class of the element */ private final ClassReferenceMirror parent; - - protected AbstractElementMirror(Field field){ + + + protected final ElementSignature signature; + + protected AbstractElementMirror(Field field) { Objects.requireNonNull(field); this.type = ClassReferenceMirror.fromClass(field.getType()); this.modifiers = new ModifierMirror(field.getModifiers()); this.name = field.getName(); List list = new ArrayList<>(); - for(Annotation a : field.getDeclaredAnnotations()){ + for(Annotation a : field.getDeclaredAnnotations()) { list.add(new AnnotationMirror(a)); } this.annotations = list; this.parent = ClassReferenceMirror.fromClass(field.getDeclaringClass()); Objects.requireNonNull(this.parent); + this.signature = null; } - - protected AbstractElementMirror(Method method){ + + protected AbstractElementMirror(Member method) { Objects.requireNonNull(method); - this.type = ClassReferenceMirror.fromClass(method.getReturnType()); + if(method instanceof Method) { + this.type = ClassReferenceMirror.fromClass(((Method) method).getReturnType()); + } else { + //It's a constructor. I hope. + this.type = ClassReferenceMirror.fromClass(((Constructor) method).getDeclaringClass()); + } this.modifiers = new ModifierMirror(method.getModifiers()); this.name = method.getName(); List list = new ArrayList<>(); - for(Annotation a : method.getDeclaredAnnotations()){ - list.add(new AnnotationMirror(a)); + // TODO: After Java 1.8, switch this behavior +// for(Annotation a : method.getDeclaredAnnotations()){ +// list.add(new AnnotationMirror(a)); +// } + if(method instanceof Method) { + for(Annotation a : ((Method) method).getDeclaredAnnotations()) { + list.add(new AnnotationMirror(a)); + } + } else if(method instanceof Constructor) { + for(Annotation a : ((Constructor) method).getDeclaredAnnotations()) { + list.add(new AnnotationMirror(a)); + } + } else { + throw new Error("Unexpected method type"); } this.annotations = list; this.parent = ClassReferenceMirror.fromClass(method.getDeclaringClass()); Objects.requireNonNull(this.parent); + this.signature = null; } - - protected AbstractElementMirror(ClassReferenceMirror parent, List annotations, ModifierMirror modifiers, ClassReferenceMirror type, String name){ + + protected AbstractElementMirror(ClassReferenceMirror parent, List annotations, + ModifierMirror modifiers, ClassReferenceMirror type, String name, String signature) { this.annotations = annotations; - if(this.annotations == null){ + if(this.annotations == null) { this.annotations = new ArrayList<>(); } this.modifiers = modifiers; @@ -86,91 +108,101 @@ protected AbstractElementMirror(ClassReferenceMirror parent, List getAnnotations(){ + public List getAnnotations() { return new ArrayList<>(annotations); } - + /** * Gets the annotation on this field/method. + * * @param annotation - * @return + * @return */ - public AnnotationMirror getAnnotation(Class annotation){ + public AnnotationMirror getAnnotation(Class annotation) { String jvmName = ClassUtils.getJVMName(annotation); - for(AnnotationMirror a : getAnnotations()){ - if(a.getType().getJVMName().equals(jvmName)){ + for(AnnotationMirror a : getAnnotations()) { + if(a.getType().getJVMName().equals(jvmName)) { return a; } } return null; } - + /** * Returns true if this element has the specified annotation attached to it. + * * @param annotation - * @return + * @return */ - public boolean hasAnnotation(Class annotation){ + public boolean hasAnnotation(Class annotation) { return getAnnotation(annotation) != null; } - + /** - * Loads the corresponding Annotation type for this field - * or method. This actually loads the Annotation class into memory. - * This is equivalent to getAnnotation(type).getProxy(type), however - * this checks for null first, and returns null instead of causing a NPE. + * Loads the corresponding Annotation type for this field or method. This actually loads the Annotation class into + * memory. This is equivalent to getAnnotation(type).getProxy(type), however this checks for null first, and returns + * null instead of causing a NPE. + * * @param * @param type - * @return + * @return */ public T loadAnnotation(Class type) { AnnotationMirror mirror = getAnnotation(type); - if(mirror == null){ + if(mirror == null) { return null; } return mirror.getProxy(type); } - + /** * Returns the class that this is declared in. - * @return + * + * @return */ - public final ClassReferenceMirror getDeclaringClass(){ + public final ClassReferenceMirror getDeclaringClass() { return this.parent; } - - /* package */ void addAnnotation(AnnotationMirror annotation){ + + /* package */ void addAnnotation(AnnotationMirror annotation) { annotations.add(annotation); } @@ -184,21 +216,53 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (obj == null) { + if(obj == null) { return false; } - if (getClass() != obj.getClass()) { + if(getClass() != obj.getClass()) { return false; } final AbstractElementMirror other = (AbstractElementMirror) obj; - if (!Objects.equals(this.name, other.name)) { + if(!Objects.equals(this.name, other.name)) { return false; } - if (!Objects.equals(this.parent, other.parent)) { + if(!Objects.equals(this.parent, other.parent)) { return false; } return true; } - - + + /** + * Loads the class that contains this method, using the default class loader. + * + * @return + * @throws java.lang.ClassNotFoundException + */ + public Class loadParentClass() throws ClassNotFoundException { + return loadParentClass(AbstractElementMirror.class.getClassLoader(), true); + } + + /** + * Loads the class that contains this element. + * + * @param loader + * @param initialize + * @return + * @throws java.lang.ClassNotFoundException + */ + public Class loadParentClass(ClassLoader loader, boolean initialize) throws ClassNotFoundException { + ClassReferenceMirror p = getDeclaringClass(); + Objects.requireNonNull(p, "Declaring class is null!"); + return p.loadClass(loader, initialize); + } + + /** + * Returns the ElementSignature for this element. This will be null if the element was not defined with any + * generic parameters, or if it was constructed from an instance of a real class. + * @return + */ + public ElementSignature getElementSignature() { + return signature; + } + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/AbstractMethodMirror.java b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/AbstractMethodMirror.java new file mode 100644 index 0000000000..55e9c4db42 --- /dev/null +++ b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/AbstractMethodMirror.java @@ -0,0 +1,163 @@ +package com.laytonsmith.PureUtilities.ClassLoading.ClassMirror; + +import com.laytonsmith.PureUtilities.Common.StringUtils; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * An {@link AbstractMethodMirror} encompasses both methods and constructors. + */ +public abstract class AbstractMethodMirror extends AbstractElementMirror { + + private static final long serialVersionUID = 1L; + + private final List params; + private boolean isVararg = false; + private boolean isSynthetic = false; + private int lineNumber = 0; + + /** + * TODO: Once we switch to Java 1.8, this should be replaced by java.lang.reflect.Executable. + */ + private Member underlyingMethod = null; + + public AbstractMethodMirror(ClassReferenceMirror parentClass, List annotations, + ModifierMirror modifiers, ClassReferenceMirror type, String name, List params, + boolean isVararg, boolean isSynthetic, String signature) { + super(parentClass, annotations, modifiers, type, name, signature); + Objects.requireNonNull(params, "params cannot be null"); + this.params = params; + this.isVararg = isVararg; + this.isSynthetic = isSynthetic; + } + + public AbstractMethodMirror(Member method) { + super(method); + this.underlyingMethod = method; + this.params = null; + } + + /* package */ AbstractMethodMirror(ClassReferenceMirror parentClass, ModifierMirror modifiers, + ClassReferenceMirror type, String name, List params, boolean isVararg, + boolean isSynthetic, String signature) { + super(parentClass, null, modifiers, type, name, signature); + annotations = new ArrayList<>(); + Objects.requireNonNull(params, "params cannot be null"); + this.params = params; + this.isVararg = isVararg; + this.isSynthetic = isSynthetic; + } + + /** + * Returns a list of params in this method. + * + * @return + */ + public List getParams() { + if(underlyingMethod != null) { + List list = new ArrayList<>(); + for(Class p : ((Method) underlyingMethod).getParameterTypes()) { + list.add(ClassReferenceMirror.fromClass(p)); + } + return list; + } + return new ArrayList<>(params); + } + + /** + * Returns true if this method is vararg. + * + * @return + */ + public boolean isVararg() { + if(underlyingMethod != null) { + return ((Method) underlyingMethod).isVarArgs(); + } + return isVararg; + } + + /** + * Returns true if this method is synthetic. + * + * @return + */ + public boolean isSynthetic() { + if(underlyingMethod != null) { + return underlyingMethod.isSynthetic(); + } + return isSynthetic; + } + + /** + * Returns the line number of the first executable instruction within a method. + * Note that this information is only available through + * the class files, so Mirrors constructed with real Members will return 0 for this. Also note that due to the + * jvm specification, only lines with executable instructions contain values in the line number table. Therefore, + * things like empty methods will not be in the line number table at all, and thus will return line 0 for this + * value. Therefore, it is very important to not rely on this returning a usable value, and having a fallback + * mechanism if this returns 0. It's also important to note that this will not return the line number for the + * method itself, since that isn't an executable statement. + * @return + */ + public int getLineNumber() { + return lineNumber; + } + + /*package*/ final void setLineNumber(int i) { + lineNumber = i; + } + + @Override + public String toString() { + if(underlyingMethod != null) { + return underlyingMethod.toString(); + } + List sParams = new ArrayList<>(); + for(int i = 0; i < params.size(); i++) { + if(i == params.size() - 1 && isVararg) { + sParams.add(params.get(i).getComponentType().toString() + "..."); + } else { + sParams.add(params.get(i).toString()); + } + } + return StringUtils.Join(annotations, "\n") + (annotations.isEmpty() ? "" : "\n") + (modifiers.toString() + + " " + type).trim() + " " + name + "(" + StringUtils.Join(sParams, ", ") + "){}"; + } + + @Override + public boolean equals(Object obj) { + if(!(obj instanceof MethodMirror)) { + return false; + } + if(!super.equals(obj)) { + return false; + } + AbstractMethodMirror m = (AbstractMethodMirror) obj; + return Objects.equals(this.params, m.params) + && this.isVararg == m.isVararg + && this.isSynthetic == m.isSynthetic; + } + + @Override + public int hashCode() { + int hash = 5; + hash = 31 * hash + super.hashCode(); + hash = 31 * hash + Objects.hashCode(this.params); + hash = 31 * hash + (this.isVararg ? 1 : 0); + hash = 31 * hash + (this.isSynthetic ? 1 : 0); + return hash; + } + + /** + * Returns the underlying executable (or null, if it was constructed artificially). + * + * @return + */ + protected Member getExecutable() { + return (Member) underlyingMethod; + } + +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/AnnotationMirror.java b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/AnnotationMirror.java index b162db651b..02d1a25704 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/AnnotationMirror.java +++ b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/AnnotationMirror.java @@ -1,6 +1,6 @@ - package com.laytonsmith.PureUtilities.ClassLoading.ClassMirror; +import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery; import com.laytonsmith.PureUtilities.Common.ClassUtils; import com.laytonsmith.PureUtilities.Common.StringUtils; import java.io.Serializable; @@ -14,24 +14,26 @@ import java.util.Objects; /** - * Represents an Annotation. Most features available to annotations are available here, - * though finding the default value of an annotation does require loading the annotation. + * Represents an Annotation. Most features available to annotations are available here, though finding the default value + * of an annotation does require loading the annotation. */ public class AnnotationMirror implements Serializable { + private static final long serialVersionUID = 1L; private final ClassReferenceMirror type; private final boolean visible; private final List values; - + /** * Creates a new AnnotationMirror based an a loaded {@link Annotation}. - * @param annotation + * + * @param annotation */ - public AnnotationMirror(Annotation annotation){ + public AnnotationMirror(Annotation annotation) { this.type = ClassReferenceMirror.fromClass(annotation.annotationType()); this.visible = true; values = new ArrayList<>(); - for(Method m : annotation.annotationType().getDeclaredMethods()){ + for(Method m : annotation.annotationType().getDeclaredMethods()) { try { values.add(new AnnotationValue(m.getName(), m.invoke(annotation))); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { @@ -39,62 +41,69 @@ public AnnotationMirror(Annotation annotation){ } } } - - /* package */ AnnotationMirror(ClassReferenceMirror type, boolean visible){ + + /* package */ AnnotationMirror(ClassReferenceMirror type, boolean visible) { this.type = type; this.visible = visible; this.values = new ArrayList<>(); } - - /* package */ void addAnnotationValue(String name, Object value){ + + /* package */ void addAnnotationValue(String name, Object value) { values.add(new AnnotationValue(name, value)); } - + /** - * Returns the value for this annotation. Note that this won't resolve - * default annotations, as that requires actually loading the annotation class - * into memory. See {@link #getValueWithDefault} if you are ok with loading + * Returns the value for this annotation. Note that this won't resolve default annotations, as that requires + * actually loading the annotation class into memory. See {@link #getValueWithDefault} if you are ok with loading * the annotation class into memory. Null is returned if this value doesn't exist. + * + * If the underlying value's type is of type Class, the class name of it is returned as a String, which you can + * then choose to load yourself (with either {@link ClassDiscovery#forName} or {@link Class#forName}). This is done + * to prevent loading classes referenced in annotations by default. + * + * It is important to note that for array types, if there are no values defined, the value returned here will + * be null. + * * @param forName - * @return + * @return */ - public Object getValue(String forName){ - for(AnnotationValue value : values){ - if(value.name.equals(forName)){ + public Object getValue(String forName) { + for(AnnotationValue value : values) { + if(value.name.equals(forName)) { return value.value; } } return null; } - + /** - * Returns the list of defined values in this annotation. Note that this - * won't resolve default annotations, as that requires actually loading the - * annotation class into memory. See {@link #getDefinedValuesWithDefault} if you - * are ok with loading the annotation class into memory. - * @return + * Returns the list of defined values in this annotation. Note that this won't resolve default annotations, as that + * requires actually loading the annotation class into memory. See {@link #getDefinedValuesWithDefault} if you are + * ok with loading the annotation class into memory. + * + * @return */ - public List getDefinedValues(){ + public List getDefinedValues() { List list = new ArrayList<>(); - for(AnnotationValue value : values){ + for(AnnotationValue value : values) { list.add(value.name); } return list; } - + /** - * Gets the value of this annotation. If the value wasn't defined - * in this annotation, the default is returned by loading the annotation - * Class into memory, and finding the default, and returning that. Calling - * this method doesn't guarantee that the class will be loaded, however. - * If the value doesn't exist, at all, this will return null. + * Gets the value of this annotation. If the value wasn't defined in this annotation, the default is returned by + * loading the annotation Class into memory, and finding the default, and returning that. Calling this method + * doesn't guarantee that the class will be loaded, however. If the value doesn't exist, at all, this will return + * null. + * * @param forName - * @return - * @throws java.lang.ClassNotFoundException + * @return + * @throws java.lang.ClassNotFoundException */ - public Object getValueWithDefault(String forName) throws ClassNotFoundException{ + public Object getValueWithDefault(String forName) throws ClassNotFoundException { Object value = getValue(forName); - if(value != null){ + if(value != null) { return value; } //Nope, have to load it. @@ -108,59 +117,59 @@ public Object getValueWithDefault(String forName) throws ClassNotFoundException{ throw new RuntimeException(ex); } } - + /** - * Loads the class into memory, and returns all the annotation value names. - * This includes default values that weren't specified by the actual instance - * of the annotation. The values returned from here must be used with + * Loads the class into memory, and returns all the annotation value names. This includes default values that + * weren't specified by the actual instance of the annotation. The values returned from here must be used with * {@link #getValueWithDefault} to ensure they will return a value properly. + * * @return - * @throws ClassNotFoundException + * @throws ClassNotFoundException */ - public List getDefinedValuesWithDefault() throws ClassNotFoundException{ + public List getDefinedValuesWithDefault() throws ClassNotFoundException { List ret = new ArrayList<>(); Class c = type.loadClass(); - for(Method m : c.getDeclaredMethods()){ + for(Method m : c.getDeclaredMethods()) { ret.add(m.getName()); } return ret; } - + /** * Returns the type of this annotation. - * @return + * + * @return */ - public ClassReferenceMirror getType(){ + public ClassReferenceMirror getType() { return type; } - + /** * Returns true if this annotation is visible. - * @return + * + * @return */ - public boolean isVisible(){ + public boolean isVisible() { return visible; } - + /** - * Gets a proxy annotation. When retrieving the annotation value, - * getValueWithDefault is called, and the annotation's Class will for sure have - * already been loaded. - * - * This allows for annotation values to be read from an element without having - * to actually load that element (just the annotation Class is loaded), and - * allowing the type safe checks of compile time. + * Gets a proxy annotation. When retrieving the annotation value, getValueWithDefault is called, and the + * annotation's Class will for sure have already been loaded. + * + * This allows for annotation values to be read from an element without having to actually load that element (just + * the annotation Class is loaded), and allowing the type safe checks of compile time. + * * @param * @param type - * @return - * @throws IllegalArgumentException If AnnotationMirror doesn't represent the type - * requested. + * @return + * @throws IllegalArgumentException If AnnotationMirror doesn't represent the type requested. */ public T getProxy(Class type) throws IllegalArgumentException { - if(!this.type.getJVMName().equals(ClassUtils.getJVMName(type))){ + if(!this.type.getJVMName().equals(ClassUtils.getJVMName(type))) { throw new IllegalArgumentException(); } - + return (T) Proxy.newProxyInstance(AnnotationMirror.class.getClassLoader(), new Class[]{type}, new InvocationHandler() { @Override @@ -175,8 +184,7 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl || ("notify".equals(method.getName()) && matches(args)) || ("notifyAll".equals(method.getName()) && matches(args)) || ("finalize".equals(method.getName()) && matches(args)) - || ("clone".equals(method.getName()) && matches(args)) - ){ + || ("clone".equals(method.getName()) && matches(args))) { // Currently, we just throw an exception, because they are // actual methods defined in Object, not annotation values. // I don't know how to make this work correctly yet. @@ -186,14 +194,18 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl } }); } - - private static boolean matches(Object[] args, Class ... types){ - if(args.length != types.length){ + + private static boolean matches(Object[] args, Class... types) { + if(args == null) { + return types.length == 0; + } + + if(args.length != types.length) { return false; } - for(int i = 0; i < args.length; i++){ + for(int i = 0; i < args.length; i++) { //Can't just use == here, since the class might be a subclass - if(!types[i].isAssignableFrom(args[i].getClass())){ + if(!types[i].isAssignableFrom(args[i].getClass())) { return false; } } @@ -214,33 +226,33 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (obj == null) { + if(obj == null) { return false; } - if (getClass() != obj.getClass()) { + if(getClass() != obj.getClass()) { return false; } final AnnotationMirror other = (AnnotationMirror) obj; - if (!Objects.equals(this.type, other.type)) { + if(!Objects.equals(this.type, other.type)) { return false; } return true; } - - + private static class AnnotationValue implements Serializable { + private static final long serialVersionUID = 1L; private String name; private Object value; - - public AnnotationValue(String name, Object value){ + + public AnnotationValue(String name, Object value) { this.name = name; this.value = value; } @Override public String toString() { - if(value instanceof String){ + if(value instanceof String) { return name + " = " + StringUtils.toCodeString(value.toString()); } else { return name + " = " + value.toString(); @@ -257,23 +269,21 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (obj == null) { + if(obj == null) { return false; } - if (getClass() != obj.getClass()) { + if(getClass() != obj.getClass()) { return false; } final AnnotationValue other = (AnnotationValue) obj; - if (!Objects.equals(this.name, other.name)) { + if(!Objects.equals(this.name, other.name)) { return false; } - if (!Objects.equals(this.value, other.value)) { + if(!Objects.equals(this.value, other.value)) { return false; } return true; } - - - + } } diff --git a/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/ClassMirror.java b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/ClassMirror.java index 63b45c044d..069de61eff 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/ClassMirror.java +++ b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/ClassMirror.java @@ -1,14 +1,8 @@ - package com.laytonsmith.PureUtilities.ClassLoading.ClassMirror; import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery; import com.laytonsmith.PureUtilities.Common.ClassUtils; import com.laytonsmith.PureUtilities.Common.StringUtils; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.annotation.RetentionPolicy; @@ -17,309 +11,295 @@ import java.net.URL; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import org.objectweb.asm.AnnotationVisitor; -import org.objectweb.asm.Attribute; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.FieldVisitor; -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; /** - * This class gathers information about a class, without actually loading - * the class into memory. Most of the methods in {@link java.lang.Class} are + * This class gathers information about a class, without actually loading the + * class into memory. Most of the methods in {@link java.lang.Class} are * available in this class (or have an equivalent Mirror version). + * * @param */ public class ClassMirror implements Serializable { + private static final long serialVersionUID = 1L; - private final ClassInfo info = new ClassInfo(); - //Transient, because it's only used during construction - private final transient org.objectweb.asm.ClassReader reader; - + private final ClassInfo info; + /** - * If this is just a wrapper for an already loaded Class, this will - * be non-null, and should override all the existing methods with - * the wrapped return. + * If this is just a wrapper for an already loaded Class, this will be + * non-null, and should override all the existing methods with the wrapped + * return. */ - private final Class underlyingClass; - + private final Class underlyingClass; + /** * The original URL that houses this class. - **/ - private final URL originalURL; - - /** - * Creates a ClassMirror object for a given input stream representing - * a class file. - * @param is - * @param container - * @throws IOException - */ - public ClassMirror(InputStream is, URL container) throws IOException { - reader = new org.objectweb.asm.ClassReader(is); - underlyingClass = null; - originalURL = container; - - parse(); - } - - /** - * Creates a ClassMirror object for a given class file. - * @param file - * @throws FileNotFoundException - * @throws IOException */ - public ClassMirror(File file) throws FileNotFoundException, IOException { - this(new FileInputStream(file), file.toURI().toURL()); + private URL originalURL; // reflectively modified in ClassDiscoveryURLCache + + protected ClassMirror(ClassInfo info, URL originalURL) { + this.underlyingClass = null; + this.originalURL = originalURL; + this.info = info; } - + /** * Creates a ClassMirror object from an already loaded Class. While this * obviously defeats the purpose of not loading the Class into PermGen, this * does allow already loaded classes to fit into the ClassMirror ecosystem. - * Essentially, calls to the ClassMirror are simply forwarded to the Class and - * the return re-wrapped in sub mirror types. Some operations are not possible, - * namely non-runtime annotation processing, but in general, all other operations - * work the same. - * @param c + * Essentially, calls to the ClassMirror are simply forwarded to the Class + * and the return re-wrapped in sub mirror types. Some operations are not + * possible, namely non-runtime annotation processing and generics + * information, but in general, all other operations work the same. + * + * @param c */ - public ClassMirror(Class c){ + public ClassMirror(Class c) { this.underlyingClass = c; - reader = null; originalURL = ClassDiscovery.GetClassContainer(c); + this.info = new ClassInfo<>(); } - - private void parse(){ - reader.accept(info, org.objectweb.asm.ClassReader.SKIP_CODE - | org.objectweb.asm.ClassReader.SKIP_DEBUG - | org.objectweb.asm.ClassReader.SKIP_FRAMES); - } - + /** * Return the container that houses this class. - * @return + * + * @return */ public URL getContainer() { return originalURL; } - + /** * Returns the modifiers on this class. - * @return + * + * @return */ - public ModifierMirror getModifiers(){ - if(underlyingClass != null){ + public ModifierMirror getModifiers() { + if(underlyingClass != null) { return new ModifierMirror(underlyingClass.getModifiers()); } return info.modifiers; } - + /** - * Returns the name of this class as recognized by the JVM, not the - * common class name. Use {@link #getClassName()} instead, if you want - * the common name. - * @return + * Returns the name of this class as recognized by the JVM, not the common + * class name. Use {@link #getClassName()} instead, if you want the common + * name. + * + * @return */ - public String getJVMClassName(){ - if(underlyingClass != null){ + public String getJVMClassName() { + if(underlyingClass != null) { return ClassUtils.getJVMName(underlyingClass); } return "L" + info.name + ";"; } - + /** - * Returns the class name of this class. This is the "normal" name, that - * is, what you would type in code to reference a class, without / or $. - * @return + * Returns the class name of this class. This is the "normal" name, that is, + * what you would type in code to reference a class, without / or $. + * + * @return */ - public String getClassName(){ - if(underlyingClass != null){ - return underlyingClass.getName().replace("$", "."); + public String getClassName() { + if(underlyingClass != null) { + return underlyingClass.getName().replace('$', '.'); } return info.name.replaceAll("[/$]", "."); } - + /** * Returns true if this class is an enum - * @return + * + * @return */ - public boolean isEnum(){ - if(underlyingClass != null){ + public boolean isEnum() { + if(underlyingClass != null) { return underlyingClass.isEnum(); } return info.isEnum; } - + /** * Returns true if this class is an interface. - * @return + * + * @return */ - public boolean isInterface(){ - if(underlyingClass != null){ + public boolean isInterface() { + if(underlyingClass != null) { return underlyingClass.isInterface(); } return info.isInterface; } - + /** - * Returns true iff the underlying class is an abstract class (not an interface). - * @return + * Returns true iff the underlying class is an abstract class (not an + * interface). + * + * @return */ public boolean isAbstract() { - if(underlyingClass != null){ + if(underlyingClass != null) { return (underlyingClass.getModifiers() & Modifier.ABSTRACT) > 0; } return info.modifiers.isAbstract(); } - + /** * Returns a {@link ClassReferenceMirror} to the class's superclass. - * @return + * + * @return */ - public ClassReferenceMirror getSuperClass(){ - if(underlyingClass != null){ + public ClassReferenceMirror getSuperClass() { + if(underlyingClass != null) { return ClassReferenceMirror.fromClass(underlyingClass.getSuperclass()); } - return new ClassReferenceMirror("L" + info.superClass + ";"); + return new ClassReferenceMirror<>("L" + info.superClass + ";"); } - + /** - * Returns a list of {@link ClassReferenceMirror}s of all the interfaces that - * this implements. - * @return + * Returns a list of {@link ClassReferenceMirror}s of all the interfaces + * that this implements. + * + * @return */ - public List getInterfaces(){ - List l = new ArrayList<>(); - if(underlyingClass != null){ - for(Class inter : underlyingClass.getInterfaces()){ + public List> getInterfaces() { + List> l = new ArrayList<>(); + if(underlyingClass != null) { + for(Class inter : underlyingClass.getInterfaces()) { l.add(ClassReferenceMirror.fromClass(inter)); } } else { - for(String inter : info.interfaces){ - l.add(new ClassReferenceMirror("L" + inter + ";")); + for(String inter : info.interfaces) { + l.add(new ClassReferenceMirror<>("L" + inter + ";")); } } return l; } - + /** - * Returns true if this class contains the annotation specified. + * Returns true if this class contains the annotation specified. + * * @param annotation - * @return + * @return */ - public boolean hasAnnotation(Class annotation){ - if(underlyingClass != null){ + public boolean hasAnnotation(Class annotation) { + if(underlyingClass != null) { return underlyingClass.getAnnotation(annotation) != null; } String name = ClassUtils.getJVMName(annotation); - for(AnnotationMirror a : info.annotations){ - if(a.getType().getJVMName().equals(name)){ + for(AnnotationMirror a : info.annotations) { + if(a.getType().getJVMName().equals(name)) { return true; } } return false; } - - /** - * Because ClassMirror works with annotations that were declared as - * either {@link RetentionPolicy#CLASS} or {@link RetentionPolicy#RUNTIME}, - * you may also want to check visibility. If this returns false, then the - * class does have the annotation, but {@link Class#getAnnotation(java.lang.Class)} - * would return false. If the class doesn't have the annotation, null is returned. - * Note that if this ClassMirror was initialized from a loaded Class object, this - * may not return correct information, because it essentially will be returning - * the result of {@link #hasAnnotation(java.lang.Class)}, since there is no way - * to tell if an annotation is anything but runtime. + + /** + * Because ClassMirror works with annotations that were declared as either + * {@link RetentionPolicy#CLASS} or {@link RetentionPolicy#RUNTIME}, you may + * also want to check visibility. If this returns false, then the class does + * have the annotation, but {@link Class#getAnnotation(java.lang.Class)} + * would return false. If the class doesn't have the annotation, null is + * returned. Note that if this ClassMirror was initialized from a loaded + * Class object, this may not return correct information, because it + * essentially will be returning the result of + * {@link #hasAnnotation(java.lang.Class)}, since there is no way to tell if + * an annotation is anything but runtime. + * * @param annotation - * @return + * @return */ - public Boolean isAnnotationVisible(Class annotation){ - if(underlyingClass != null){ + public Boolean isAnnotationVisible(Class annotation) { + if(underlyingClass != null) { return hasAnnotation(annotation); } String name = ClassUtils.getJVMName(annotation); - for(AnnotationMirror a : info.annotations){ - if(a.getType().getJVMName().equals(name)){ + for(AnnotationMirror a : info.annotations) { + if(a.getType().getJVMName().equals(name)) { return a.isVisible(); } } return null; } - + /** * Returns the annotation defined on this class. + * * @param clazz - * @return + * @return */ - public AnnotationMirror getAnnotation(Class clazz){ - if(underlyingClass != null){ + public AnnotationMirror getAnnotation(Class clazz) { + if(underlyingClass != null) { Annotation ann = underlyingClass.getAnnotation(clazz); - if(ann == null){ + if(ann == null) { return null; } return new AnnotationMirror(ann); } String name = ClassUtils.getJVMName(clazz); - for(AnnotationMirror a : info.annotations){ - if(a.getType().getJVMName().equals(name)){ + for(AnnotationMirror a : info.annotations) { + if(a.getType().getJVMName().equals(name)) { return a; } } return null; } - + /** * Returns a list of annotations on this class. - * @return + * + * @return */ - public List getAnnotations(){ - if(underlyingClass != null){ + public List getAnnotations() { + if(underlyingClass != null) { List list = new ArrayList<>(); - for(Annotation a : underlyingClass.getAnnotations()){ + for(Annotation a : underlyingClass.getAnnotations()) { list.add(new AnnotationMirror(ClassReferenceMirror.fromClass(a.annotationType()), true)); } return list; } return new ArrayList<>(info.annotations); } - + /** - * Loads the corresponding Annotation type for this field - * or method. This actually loads the Annotation class into memory. - * This is equivalent to getAnnotation(type).getProxy(type), however - * this checks for null first, and returns null instead of causing a NPE. - * In the case that this is a wrapper for a real Class object, this simply - * returns the real Annotation object (or null). + * Loads the corresponding Annotation type for this field or method. This + * actually loads the Annotation class into memory. This is equivalent to + * getAnnotation(type).getProxy(type), however this checks for null first, + * and returns null instead of causing a NPE. In the case that this is a + * wrapper for a real Class object, this simply returns the real Annotation + * object (or null). + * * @param * @param type - * @return + * @return */ public T loadAnnotation(Class type) { - if(underlyingClass != null){ - return (T) underlyingClass.getAnnotation(type); + if(underlyingClass != null) { + return underlyingClass.getAnnotation(type); } AnnotationMirror mirror = getAnnotation(type); - if(mirror == null){ + if(mirror == null) { return null; } return mirror.getProxy(type); } - + /** - * Returns the fields in this class. This works like - * {@link Class#getDeclaredFields()}, as only the methods in - * this class are loaded. - * @return + * Returns the fields in this class. This works like + * {@link Class#getDeclaredFields()}, as only the methods in this class are + * loaded. + * + * @return */ - public FieldMirror[] getFields(){ - if(underlyingClass != null){ + public FieldMirror[] getFields() { + if(underlyingClass != null) { FieldMirror[] fields = new FieldMirror[this.underlyingClass.getDeclaredFields().length]; - for(int i = 0; i < fields.length; i++){ + for(int i = 0; i < fields.length; i++) { Field f = this.underlyingClass.getDeclaredFields()[i]; fields[i] = new FieldMirror(f); } @@ -327,174 +307,249 @@ public FieldMirror[] getFields(){ } return info.fields.toArray(new FieldMirror[info.fields.size()]); } - + + /** + * This method returns a map for all classes which this class + * extends/implements of the the generic parameters. For instance, if a + * class has the signature + * {@code class C extends E implements J, K} then this + * method would return the map: {@code {E: [String], J[Integer], K:[]}}. + * Note that for the purposes of this method, interfaces and classes are not + * distinguished, and while the extended class will be first in the list, + * the first item in the list is not necessarily a class. + * + * @return + * @throws IllegalArgumentException If the underlying mechanism backing this + * ClassMirror object is a real loaded class, this method will throw an + * IllegalArgumentException, because real classes don't know their generic types. + */ + public Map, List>> getGenerics() throws IllegalArgumentException { + if(underlyingClass != null) { + throw new IllegalArgumentException("Cannot get generics of a real class"); + } + Map, List>> map = new HashMap<>(info.genericParameters.size()); + for(Map.Entry, List>> k : info.genericParameters.entrySet()) { + map.put(k.getKey(), new ArrayList<>(k.getValue())); + } + return map; + } + /** - * Returns the field, given by name. This does not traverse the - * Object hierarchy, unlike {@link Class#getField(java.lang.String)}. + * Returns the field, given by name. This does not traverse the Object + * hierarchy, unlike {@link Class#getField(java.lang.String)}. + * * @param name - * @return - * @throws java.lang.NoSuchFieldException + * @return + * @throws java.lang.NoSuchFieldException */ public FieldMirror getField(String name) throws NoSuchFieldException { - for(FieldMirror m : getFields()){ - if(m.getName().equals(name)){ + for(FieldMirror m : getFields()) { + if(m.getName().equals(name)) { return m; } } throw new NoSuchFieldException("The field \"" + name + "\" was not found."); } - + /** * Returns the methods in this class. This traverses the parent Object - * heirarchy if the methods are apart of the visible interface, as well as + * hierarchy if the methods are apart of the visible interface, as well as * private methods in this class itself. - * @return + * + * @return + */ + public MethodMirror[] getMethods() { + List l = new ArrayList<>(); + for(AbstractMethodMirror m : getAllMethods()) { + if(m instanceof MethodMirror) { + l.add((MethodMirror) m); + } + } + return l.toArray(new MethodMirror[l.size()]); + } + + /** + * Returns the Constructors in this class. + * + * @return + */ + @SuppressWarnings("unchecked") + public ConstructorMirror[] getConstructors() { + List> l = new ArrayList<>(); + for(AbstractMethodMirror m : getAllMethods()) { + if(m instanceof ConstructorMirror) { + l.add((ConstructorMirror) m); + } + } + return l.toArray(new ConstructorMirror[l.size()]); + } + + /** + * Returns all methods in this class, including constructors. + * + * @return */ - public MethodMirror[] getMethods(){ - if(underlyingClass != null){ + public AbstractMethodMirror[] getAllMethods() { + if(underlyingClass != null) { MethodMirror[] mirrors = new MethodMirror[underlyingClass.getDeclaredMethods().length]; - for(int i = 0; i < mirrors.length; i++){ + for(int i = 0; i < mirrors.length; i++) { mirrors[i] = new MethodMirror(underlyingClass.getDeclaredMethods()[i]); } return mirrors; } - return info.methods.toArray(new MethodMirror[info.methods.size()]); + return info.methods.toArray(new AbstractMethodMirror[info.methods.size()]); } - + /** - * Returns the method, given by name. This traverses the parent Object heirarchy - * if the methods are apart of the visible interface, as well as private methods - * in this class itself. + * Returns the method, given by name. This traverses the parent Object + * hierarchy if the methods are apart of the visible interface, as well as + * private methods in this class itself. + * * @param name * @param params - * @return - * @throws java.lang.NoSuchMethodException + * @return + * @throws java.lang.NoSuchMethodException */ - public MethodMirror getMethod(String name, Class...params) throws NoSuchMethodException{ - ClassReferenceMirror mm [] = new ClassReferenceMirror[params.length]; - for(int i = 0; i < params.length; i++){ - mm[i] = new ClassReferenceMirror(ClassUtils.getJVMName(params[i])); + public MethodMirror getMethod(String name, Class... params) throws NoSuchMethodException { + ClassReferenceMirror mm[] = new ClassReferenceMirror[params.length]; + for(int i = 0; i < params.length; i++) { + mm[i] = new ClassReferenceMirror<>(ClassUtils.getJVMName(params[i])); } return getMethod(name, mm); } - + /** - * Returns the method, given by name. This traverses the parent Object heirarchy - * if the methods are apart of the visible interface, as well as private methods - * in this class itself. + * Returns the method, given by name. This traverses the parent Object + * hierarchy if the methods are apart of the visible interface, as well as + * private methods in this class itself. + * * @param name * @param params * @return - * @throws NoSuchMethodException + * @throws NoSuchMethodException */ - public MethodMirror getMethod(String name, ClassReferenceMirror... params) throws NoSuchMethodException { - List crmParams = new ArrayList<>(); + public MethodMirror getMethod(String name, ClassReferenceMirror... params) throws NoSuchMethodException { + List> crmParams = new ArrayList<>(); crmParams.addAll(Arrays.asList(params)); - for(MethodMirror m : getMethods()){ - if(m.getName().equals(name) && m.getParams().equals(crmParams)){ - return m; + for(AbstractMethodMirror m : getAllMethods()) { + if(m instanceof MethodMirror && m.getName().equals(name) && m.getParams().equals(crmParams)) { + return (MethodMirror) m; } } throw new NoSuchMethodException("No method matching the signature " + name + "(" + StringUtils.Join(crmParams, ", ") + ") was found."); - + } - + /** - * Loads the class into memory and returns the class object. For this - * call to succeed, the class must otherwise be on the class path. The standard + * Loads the class into memory and returns the class object. For this call + * to succeed, the class must otherwise be on the class path. The standard * class loader is used, and the class is initialized. If this is a wrapper * for an already loaded Class object, that object is simply returned. - * @return + * + * @return */ + @SuppressWarnings("unchecked") public Class loadClass() throws NoClassDefFoundError { - if(underlyingClass != null){ - return underlyingClass; + if(underlyingClass != null) { + return (Class) underlyingClass; } - try{ - return (Class)info.classReferenceMirror.loadClass(); - } catch(ClassNotFoundException ex){ - throw new NoClassDefFoundError(); + try { + return info.classReferenceMirror.loadClass(); + } catch (ClassNotFoundException ex) { + throw new NoClassDefFoundError(ex.getMessage()); } } - + /** - * Loads the class into memory and returns the class object. For this - * call to succeed, the classloader specified must be able to find the class. - * If this is a wrapper for an already loaded Class object, that object is simply - * returned. + * Loads the class into memory and returns the class object. For this call + * to succeed, the classloader specified must be able to find the class. If + * this is a wrapper for an already loaded Class object, that object is + * simply returned. + * * @param loader * @param initialize - * @return + * @return */ + @SuppressWarnings("unchecked") public Class loadClass(ClassLoader loader, boolean initialize) throws NoClassDefFoundError { - if(underlyingClass != null){ - return underlyingClass; + if(underlyingClass != null) { + return (Class) underlyingClass; } - try{ + try { return info.classReferenceMirror.loadClass(loader, initialize); - } catch(ClassNotFoundException ex){ + } catch (ClassNotFoundException ex) { throw new NoClassDefFoundError(ex.getMessage()); } } - + /** * Returns true if this class either extends or implements the class * specified, or is the same as that class. Note that if it transiently - * extends from this class, it can't necessarily find that information without - * actually loading the intermediate class, so this is a less useful method - * than {@link Class#isAssignableFrom(java.lang.Class)}, however, in combination - * with a system that is aware of all classes in a class ecosystem, this can - * be used to piece together that information without actually loading the - * classes. + * extends from this class, it can't necessarily find that information + * without actually loading the intermediate class, so this is a less useful + * method than {@link Class#isAssignableFrom(java.lang.Class)}, however, in + * combination with a system that is aware of all classes in a class + * ecosystem, this can be used to piece together that information without + * actually loading the classes. + * * @param superClass - * @return + * @return */ - public boolean directlyExtendsFrom(Class superClass){ - if(underlyingClass != null){ - return (underlyingClass.getSuperclass() == superClass); + public boolean directlyExtendsFrom(Class superClass) { + if(underlyingClass != null) { + if(underlyingClass == superClass) { + return true; + } + if(underlyingClass.isInterface()) { + return Arrays.asList(underlyingClass.getInterfaces()).contains(superClass); + } else { + return (underlyingClass.getSuperclass() == superClass); + } } - String name = superClass.getName().replace(".", "/"); - if(info.superClass.equals(name)){ + String name = superClass.getName().replace('.', '/'); + if(name.equals(info.superClass)) { return true; } - for(String in : info.interfaces){ - if(in.equals(name)){ + for(String in : info.interfaces) { + if(in.equals(name)) { return true; } } return false; } - + /** - * Returns the Package this class is in. If this is not in a package, - * null is returned. - * @return + * Returns the Package this class is in. If this is not in a package, null + * is returned. + * + * @return */ - public PackageMirror getPackage(){ - if(underlyingClass != null){ + public PackageMirror getPackage() { + if(underlyingClass != null) { return new PackageMirror(underlyingClass.getPackage().getName()); } String[] split = getClassName().split("\\."); - if(split.length == 1){ + if(split.length == 1) { return null; } StringBuilder b = new StringBuilder(); - for(int i = 0; i < split.length - 1; i++){ - if(i != 0){ + for(int i = 0; i < split.length - 1; i++) { + if(i != 0) { b.append("."); } b.append(split[i]); } return new PackageMirror(b.toString()); } - + /** - * Returns the simple name of this class. I.e. for java.lang.String, "String" is - * returned. - * @return + * Returns the simple name of this class. I.e. for java.lang.String, + * "String" is returned. + * + * @return */ - public String getSimpleName(){ - if(underlyingClass != null){ + public String getSimpleName() { + if(underlyingClass != null) { return underlyingClass.getSimpleName(); } String[] split = getClassName().split("\\."); @@ -502,32 +557,34 @@ public String getSimpleName(){ } /** - * Returns a string representation of this object. The string will match the toString - * that would be generated by that of the Class object. - * @return + * Returns a string representation of this object. The string will match the + * toString that would be generated by that of the Class object. + * + * @return */ @Override public String toString() { - return (isInterface()? - "interface": - (isEnum()?"enum":"class")) + return (isInterface() + ? "interface" + : (isEnum() ? "enum" : "class")) + " " + getClassName(); } /** - * Returns a {@link ClassReferenceMirror} to the object. This is useful - * for classes that may not exist, as it doesn't require an actual known + * Returns a {@link ClassReferenceMirror} to the object. This is useful for + * classes that may not exist, as it doesn't require an actual known * reference to the class to exist. - * @return + * + * @return */ - public ClassReferenceMirror getClassReference() { - if(underlyingClass != null){ + @SuppressWarnings("unchecked") + public ClassReferenceMirror getClassReference() { + if(underlyingClass != null) { return ClassReferenceMirror.fromClass(underlyingClass); } - return new ClassReferenceMirror(getJVMClassName()); + return new ClassReferenceMirror<>(getJVMClassName()); } - @Override public int hashCode() { int hash = 5; @@ -537,20 +594,19 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (obj == null) { + if(obj == null) { return false; } - if (getClass() != obj.getClass()) { + if(getClass() != obj.getClass()) { return false; } final ClassMirror other = (ClassMirror) obj; return Objects.equals(this.getJVMClassName(), other.getJVMClassName()); } - - private static class ClassInfo implements ClassVisitor, Serializable { + protected static class ClassInfo implements Serializable { + private static final long serialVersionUID = 1L; - public ModifierMirror modifiers; public String name; public String superClass; @@ -558,293 +614,16 @@ private static class ClassInfo implements ClassVisitor, Serializable { public List annotations = new ArrayList<>(); public boolean isInterface = false; public boolean isEnum = false; - public ClassReferenceMirror classReferenceMirror; + public ClassReferenceMirror classReferenceMirror; public List fields = new ArrayList<>(); - public List methods = new ArrayList<>(); - - @Override - public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - if((access & Opcodes.ACC_ENUM) > 0){ - isEnum = true; - } - if((access & Opcodes.ACC_INTERFACE) > 0){ - isInterface = true; - } - this.modifiers = new ModifierMirror(ModifierMirror.Type.CLASS, access); - this.name = name; - //We know we aren't an array or a primitive, so we just add L...; to make - //the binary name, which is what ClassReferenceMirror expects. - this.classReferenceMirror = new ClassReferenceMirror("L" + name + ";"); - this.superClass = superName; - this.interfaces = interfaces; - } - - @Override - public void visitSource(String source, String debug) { - //Ignored - } - - @Override - public void visitOuterClass(String owner, String name, String desc) { - //Ignored - } - - @Override - public AnnotationVisitor visitAnnotation(String desc, boolean visible) { - AnnotationMirror am = new AnnotationMirror(new ClassReferenceMirror(desc), visible); - annotations.add(am); - return new AnnotationV(am); - } - - @Override - public void visitAttribute(Attribute attr) { - - } - - @Override - public void visitInnerClass(String name, String outerName, String innerName, int access) { - - } - - @Override - public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { - final FieldMirror fm = new FieldMirror(classReferenceMirror, new ModifierMirror(ModifierMirror.Type.FIELD, access), - new ClassReferenceMirror(desc), name, value); - fields.add(fm); - return new FieldVisitor() { - - @Override - public AnnotationVisitor visitAnnotation(String desc, boolean visible) { - AnnotationMirror m = new AnnotationMirror(new ClassReferenceMirror(desc), visible); - fm.addAnnotation(m); - return new AnnotationV(m); - } - - @Override - public void visitAttribute(Attribute attr) { - - } - - @Override - public void visitEnd() { - - } - }; - } - - private static transient final Pattern METHOD_SIGNATURE_PATTERN = Pattern.compile("^\\((.*)\\)(.*)$"); - - @Override - public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - if("".equals(name) || "".equals(name)){ - //For now, we aren't interested in constructors or static initializers - return null; - } - - Matcher m = METHOD_SIGNATURE_PATTERN.matcher(desc); - if(!m.find()){ - //The desc type didn't match? - throw new Error("No match found for " + desc); - } - String inner = m.group(1); - String ret = m.group(2); - List params = new ArrayList(); - //Parsing the params list is a bit more complicated than it should be. - StringBuilder b = new StringBuilder(); - boolean inObject = false; - for(char c : inner.toCharArray()){ - b.append(c); - if(inObject){ - if(c == ';'){ - inObject = false; - params.add(new ClassReferenceMirror(b.toString())); - b = new StringBuilder(); - } - } else { - if(c == 'L'){ - inObject = true; - } else if(c != '['){ - params.add(new ClassReferenceMirror(b.toString())); - b = new StringBuilder(); - } //otherwise, it's an array, continue. - } - } - final MethodMirror mm = new MethodMirror(classReferenceMirror, new ModifierMirror(ModifierMirror.Type.METHOD, access), - new ClassReferenceMirror(ret), name, params, - (access & Opcodes.ACC_VARARGS) > 0, (access & Opcodes.ACC_SYNTHETIC) > 0); - methods.add(mm); - return new MethodVisitor() { - - @Override - public AnnotationVisitor visitAnnotationDefault() { - return null; - } - - @Override - public AnnotationVisitor visitAnnotation(String desc, boolean visible) { - AnnotationMirror am = new AnnotationMirror(new ClassReferenceMirror(desc), visible); - mm.addAnnotation(am); - return new AnnotationV(am); - } - - @Override - public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { - return null; - } - - @Override - public void visitAttribute(Attribute attr) { - - } - - @Override - public void visitCode() { - - } - - @Override - public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { - - } - - @Override - public void visitInsn(int opcode) { - - } - - @Override - public void visitIntInsn(int opcode, int operand) { - - } - - @Override - public void visitVarInsn(int opcode, int var) { - - } - - @Override - public void visitTypeInsn(int opcode, String type) { - - } - - @Override - public void visitFieldInsn(int opcode, String owner, String name, String desc) { - - } - - @Override - public void visitMethodInsn(int opcode, String owner, String name, String desc) { - - } - - @Override - public void visitJumpInsn(int opcode, Label label) { - - } - - @Override - public void visitLabel(Label label) { - - } - - @Override - public void visitLdcInsn(Object cst) { - - } - - @Override - public void visitIincInsn(int var, int increment) { - - } - - @Override - public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) { - - } - - @Override - public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { - - } - - @Override - public void visitMultiANewArrayInsn(String desc, int dims) { - - } - - @Override - public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { - - } - - @Override - public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { - - } - - @Override - public void visitLineNumber(int line, Label start) { - - } - - @Override - public void visitMaxs(int maxStack, int maxLocals) { - - } - - @Override - public void visitEnd() { - - } - }; - } - - @Override - public void visitEnd() { - - } - - } - - private static class AnnotationV implements AnnotationVisitor { - - private final AnnotationMirror mirror; - public AnnotationV(AnnotationMirror mirror){ - this.mirror = mirror; - } - - @Override - public void visit(String name, Object value) { - if(value instanceof org.objectweb.asm.Type){ - //Type can't serialize, so we need to store a reference to it. - //This will only happen if it's a class type, so a ClassReferenceMirror - //is what we need anyways. - org.objectweb.asm.Type type = (org.objectweb.asm.Type) value; - value = new ClassReferenceMirror(type.getDescriptor()); - } - mirror.addAnnotationValue(name, value); - } - - @Override - public void visitEnum(String name, String desc, String value) { - - } - - @Override - public AnnotationVisitor visitAnnotation(String name, String desc) { - return null; - } - - @Override - public AnnotationVisitor visitArray(String name) { - return null; - } - - @Override - public void visitEnd() { - - } - + public List methods = new ArrayList<>(); + /** + * Maps inherited classes to the generic parameters passed along to the + * inherited class. For instance, if we have {@code class Base implements + * A, B {...}} then this object would contain {A: + * [Integer, Long], B: [String]} + */ + public Map, List>> genericParameters + = new HashMap<>(); } - } diff --git a/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/ClassMirrorVisitor.java b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/ClassMirrorVisitor.java new file mode 100644 index 0000000000..2a84e386c5 --- /dev/null +++ b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/ClassMirrorVisitor.java @@ -0,0 +1,294 @@ +package com.laytonsmith.PureUtilities.ClassLoading.ClassMirror; + +import com.laytonsmith.PureUtilities.Common.ArrayUtils; +import com.laytonsmith.PureUtilities.Common.ClassUtils; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +import com.laytonsmith.PureUtilities.Common.StringUtils; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +import static org.objectweb.asm.Opcodes.ACC_ENUM; +import static org.objectweb.asm.Opcodes.ACC_INTERFACE; +import static org.objectweb.asm.Opcodes.ACC_VARARGS; +import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC; + +public class ClassMirrorVisitor extends ClassVisitor { + + private final ClassMirror.ClassInfo classInfo; + + ClassMirrorVisitor(ClassMirror.ClassInfo info) { + super(Opcodes.ASM9); + this.classInfo = info; + } + + public ClassMirrorVisitor() { + this(new ClassMirror.ClassInfo()); + } + + public ClassMirror getMirror(URL source) { + if(!done) { + throw new IllegalStateException(String.format( + "Not done visiting %s", classInfo.name == null ? "none" : classInfo.name + )); + } + return new ClassMirror<>(this.classInfo, source); + } + + private boolean done; + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + if(done) { + // Ensure we never visit more than one class + throw new IllegalStateException(String.format( + "Can't visit %s, because we already visited %s", name, classInfo.name + )); + } + if((access & ACC_ENUM) == ACC_ENUM) { + classInfo.isEnum = true; + } + if((access & ACC_INTERFACE) == ACC_INTERFACE) { + classInfo.isInterface = true; + } + classInfo.modifiers = new ModifierMirror(ModifierMirror.Type.CLASS, access); + classInfo.name = name; + classInfo.classReferenceMirror = new ClassReferenceMirror(Type.getObjectType(name).getDescriptor()); + classInfo.superClass = superName; + classInfo.interfaces = interfaces; + if(signature != null) { + boolean inGeneric = false; + ClassReferenceMirror top = null; + StringBuilder buffer = new StringBuilder(); + for(char c : signature.toCharArray()) { + if(c == '<') { + inGeneric = true; + top = new ClassReferenceMirror<>(buffer.toString() + ";"); + classInfo.genericParameters.put(top, new ArrayList<>()); + buffer = new StringBuilder(); + continue; + } + if(c == '>') { + inGeneric = false; + top = null; + buffer = new StringBuilder(); + continue; + } + if(c == ';') { + if(buffer.toString().isEmpty()) { + // this happens at Lclass; because ; always follows > + continue; + } + if(inGeneric) { + classInfo.genericParameters + .get(top).add(new ClassReferenceMirror<>(buffer.toString() + ";")); + buffer = new StringBuilder(); + } else { + classInfo.genericParameters + .put(new ClassReferenceMirror<>(buffer.toString() + ";"), new ArrayList<>()); + } + continue; + } + buffer.append(c); + } + } + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + final AnnotationMirror mirror = new AnnotationMirror(new ClassReferenceMirror(desc), visible); + return new AnnotationMirrorVisitor(super.visitAnnotation(desc, visible), mirror) { + @Override + public void visitEnd() { + classInfo.annotations.add(mirror); + super.visitEnd(); + } + }; + } + + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + final FieldMirror fieldMirror = new FieldMirror( + classInfo.classReferenceMirror, + new ModifierMirror(ModifierMirror.Type.FIELD, access), + new ClassReferenceMirror(desc), + name, + value, + signature + ); + return new FieldVisitor(Opcodes.ASM9, super.visitField(access, name, desc, signature, value)) { + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + final AnnotationMirror annotationMirror = new AnnotationMirror(new ClassReferenceMirror(desc), visible); + return new AnnotationMirrorVisitor(super.visitAnnotation(desc, visible), annotationMirror) { + @Override + public void visitEnd() { + fieldMirror.addAnnotation(annotationMirror); + super.visitEnd(); + } + }; + } + + @Override + public void visitEnd() { + classInfo.fields.add(fieldMirror); + super.visitEnd(); + } + }; + } + + private static final Pattern STATIC_INITIALIZER_PATTERN = Pattern.compile(""); + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + if(STATIC_INITIALIZER_PATTERN.matcher(name).matches()) { + return null; // Ignore static initializers + } + if(ConstructorMirror.INIT.matches(name)) { + // We want to replace the V at the end with the parent class type. + // Yes, technically a constructor really does return void, but.. not really. + desc = StringUtils.replaceLast(desc, "V", classInfo.classReferenceMirror.getJVMName()); + } + List parameterMirrors = new ArrayList<>(); + for(Type type : Type.getArgumentTypes(desc)) { + parameterMirrors.add(new ClassReferenceMirror(type.getDescriptor())); + } + final AbstractMethodMirror methodMirror; + if(ConstructorMirror.INIT.equals(name)) { + methodMirror = new ConstructorMirror( + classInfo.classReferenceMirror, + new ModifierMirror(ModifierMirror.Type.METHOD, access), + new ClassReferenceMirror(Type.getReturnType(desc).getDescriptor()), + name, + parameterMirrors, + (access & ACC_VARARGS) == ACC_VARARGS, + (access & ACC_SYNTHETIC) == ACC_SYNTHETIC, + signature + ); + } else { + methodMirror = new MethodMirror( + classInfo.classReferenceMirror, + new ModifierMirror(ModifierMirror.Type.METHOD, access), + new ClassReferenceMirror(Type.getReturnType(desc).getDescriptor()), + name, + parameterMirrors, + (access & ACC_VARARGS) == ACC_VARARGS, + (access & ACC_SYNTHETIC) == ACC_SYNTHETIC, + signature + ); + } + + return new MethodVisitor(Opcodes.ASM9, super.visitMethod(access, name, desc, signature, exceptions)) { + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + final AnnotationMirror annotationMirror = new AnnotationMirror(new ClassReferenceMirror<>(desc), visible); + return new AnnotationMirrorVisitor(super.visitAnnotation(desc, visible), annotationMirror) { + @Override + public void visitEnd() { + methodMirror.addAnnotation(annotationMirror); + } + }; + } + + @Override + public void visitLineNumber(int line, Label start) { + // Each line of code will be visited here. We only want the lowest line number though, since that's + // the closest to the method declaration line, which is what we're really after. + int lowest = methodMirror.getLineNumber(); + if(lowest == 0) { + methodMirror.setLineNumber(line); + } else { + methodMirror.setLineNumber(Math.min(lowest, line)); + } + } + + @Override + public void visitEnd() { + classInfo.methods.add(methodMirror); + super.visitEnd(); + } + }; + } + + @Override + public void visitEnd() { + this.done = true; + super.visitEnd(); + } + + private static class AnnotationMirrorVisitor extends AnnotationVisitor { + + private final AnnotationMirror mirror; + + public AnnotationMirrorVisitor(AnnotationVisitor next, AnnotationMirror mirror) { + super(Opcodes.ASM9, next); + this.mirror = mirror; + } + + @Override + public void visit(String name, Object value) { + if(value instanceof Type) { + value = ((Type) value).getClassName(); + } + mirror.addAnnotationValue(name, value); + super.visit(name, value); + } + + @Override + public AnnotationVisitor visitArray(String name) { + return new ArrayAnnotationVisitor(name, mirror); + } + + } + + private static class ArrayAnnotationVisitor extends AnnotationVisitor { + private final AnnotationMirror mirror; + private final List types = new ArrayList<>(); + private final String name; + private Class type; + + public ArrayAnnotationVisitor(String name, AnnotationMirror mirror) { + super(Opcodes.ASM9); + this.name = name; + this.mirror = mirror; + } + + + + @Override + public void visit(String name, Object value) { + type = value.getClass(); + if(value instanceof Type) { + value = ((Type) value).getClassName(); + type = String.class; + } + types.add(value); + super.visit(name, value); + } + + @Override + public void visitEnd() { + // The underlying type is necessarily null if we did not get any values. This should still be ok, + // but code needs to check for this value in array types. + Object array = null; + if(type != null) { + array = ArrayUtils.cast(types.toArray(new Object[types.size()]), + ClassUtils.getArrayClassFromType(type)); + } + mirror.addAnnotationValue(name, array); + super.visitEnd(); + } + + + } +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/ClassReferenceMirror.java b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/ClassReferenceMirror.java index 876f6ac404..b2da87ac83 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/ClassReferenceMirror.java +++ b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/ClassReferenceMirror.java @@ -1,91 +1,203 @@ - package com.laytonsmith.PureUtilities.ClassLoading.ClassMirror; +import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery; import com.laytonsmith.PureUtilities.Common.ClassUtils; import java.io.Serializable; +import java.util.Arrays; /** - * A class reference mirror is a wrapper around a simple class name reference. - * It cannot directly get more information about a class without actually loading it, - * so minimal information is available directly, though there is a method for - * loading the actual class referenced, at which point more information could be - * retrieved. + * A class reference mirror is a wrapper around a simple class name reference. It cannot directly get more information + * about a class without actually loading it, so minimal information is available directly, though there is a method for + * loading the actual class referenced, at which point more information could be retrieved. + * @param */ public class ClassReferenceMirror implements Serializable { + private static final long serialVersionUID = 1L; - + + /** + * A ClassReferenceMirror that represents the wildcard class. This can only naturally occur in generic declarations, + * and is not a real class that can be loaded. Calls against this object to loadClass will fail with + * {@link ClassNotFoundException}. + */ + public static final ClassReferenceMirror WILDCARD = new ClassReferenceMirror("*"); + /** - * Returns a ClassReferenceMirror for a given real class. This is - * useful when doing comparisons. + * Returns a ClassReferenceMirror for a given real class. This is useful when doing comparisons. Note that + * generic data is lost during compilation, and therefore this method is not suitable when doing comparisons + * that include generic data. The generic data is not included in the equals or hashCode comparison, however. + * * @param c - * @return + * @return */ - public static ClassReferenceMirror fromClass(Class c){ + public static ClassReferenceMirror fromClass(Class c) { return new ClassReferenceMirror(ClassUtils.getJVMName(c)); } - + private final String name; + private final String templateTypeName; + private final ClassReferenceMirror[] genericParameters; + + /** + * Constructs a new ClassReferenceMirror, using the java class binary name. + * + * @param name The JVM binary name for this class. The name should look similar to e.g.: "Ljava/lang/Object;" + * or "I". + */ + public ClassReferenceMirror(String name) { + this.name = name; + this.genericParameters = new ClassReferenceMirror[0]; + templateTypeName = null; + } + /** - * The name should look similar to e.g.: "Ljava/lang/Object;" or "I" - * @param name The JVM binary name for this class. + * Constructs a ClassReferenceMirror which has generic parameters. + * @param name + * @param genericParameters */ - public ClassReferenceMirror(String name){ + public ClassReferenceMirror(String name, ClassReferenceMirror[] genericParameters) { this.name = name; + this.genericParameters = genericParameters; + templateTypeName = null; } + /** + * Constructs a ClassReferenceMirror which is a template type definition. This is only present on methods, and + * only if they define a template type, for instance in the method definition {@code T method(Class)} + * then the templateTypeName would be {@code T} and the name would be {@code Ljava/lang/Object;}. In the example + * {@code J method(Class)} the templateTypeName would be {@code J} and the name would + * be {@code Ljava/util/List;} + * @param name + * @param templateTypeName + */ + public ClassReferenceMirror(String name, String templateTypeName) { + this.name = name; + this.genericParameters = null; + this.templateTypeName = templateTypeName; + } + + /** * Returns the java binary name for this class reference. - * @return + * + * @return */ - public String getJVMName(){ + public String getJVMName() { return name; } - + /** - * Loads the class into memory and returns the class object. For this - * call to succeed, the class must otherwise be on the class path. The standard - * class loader is used, and the class is initialized. - * @return + * Loads the class into memory and returns the class object. For this call to succeed, the class must otherwise be + * on the class path. The standard class loader is used, and the class is initialized. + * + * @return * @throws java.lang.ClassNotFoundException If the class can't be found */ - public Class loadClass() throws ClassNotFoundException{ - return loadClass(ClassReferenceMirror.class.getClassLoader(), true); + public Class loadClass() throws ClassNotFoundException { + return loadClass(ClassDiscovery.getDefaultInstance().getDefaultClassLoader(), true); } - + /** - * Loads the class into memory and returns the class object. For this - * call to succeed, the classloader specified must be able to find the class. + * Loads the class into memory and returns the class object. For this call to succeed, the classloader specified + * must be able to find the class. + * * @param loader - * @return + * @param initialize + * @return + * @throws java.lang.ClassNotFoundException */ - public Class loadClass(ClassLoader loader, boolean initialize) throws ClassNotFoundException{ + public Class loadClass(ClassLoader loader, boolean initialize) throws ClassNotFoundException { return ClassUtils.forCanonicalName(ClassUtils.getCommonNameFromJVMName(name), initialize, loader); } - + /** * Returns true if this represents an array type. - * @return + * + * @return */ - public boolean isArray(){ + public boolean isArray() { return name.startsWith("["); } - + + /** + * Returns true if this is a template type declaration. Template definitions cannot have generic parameters, so + * getGenerics will return null if this method returns true. + *

+ * A template usage is a usage of the a template declaration, which is defined elsewhere. A template definition is + * where that template type is defined, which may be either on a method i.e. {@code T method()} or on the class + * definition i.e. {@code class MyClass{}}. + * @return + */ + public boolean isTemplateDefinition() { + return this.templateTypeName != null; + } + + /** + * Returns the name of the template type declaration. This will be null if this is not a template type declaration. + * This is different than this object being a template usage. + *

+ * A template usage is a usage of the a template declaration, which is defined elsewhere. A template definition is + * where that template type is defined, which may be either on a method i.e. {@code T method()} or on the class + * definition i.e. {@code class MyClass{}}. + * @return + */ + public String getTemplateTypeName() { + return this.templateTypeName; + } + + /** + * If this is a template type used with generics. This is different than this object being a template definition. + *

+ * A template usage is a usage of the a template declaration, which is defined elsewhere. A template definition is + * where that template type is defined, which may be either on a method i.e. {@code T method()} or on the class + * definition i.e. {@code class MyClass{}}. + * @return + */ + public boolean isTemplateUsage() { + return name.startsWith("T"); + } + + /** + * Returns the generic parameters that this reference was declared with. + * @return + */ + public ClassReferenceMirror[] getGenerics() { + return Arrays.copyOf(genericParameters, genericParameters.length); + } + /** - * If this is an array type, returns the component type. - * For instance, for a String[], String would be returned. Null - * is returned if this isn't an array type. - * @return + * If this is an array type, returns the component type. For instance, for a String[], String would be returned. + * Null is returned if this isn't an array type. + * + * @return */ - public ClassReferenceMirror getComponentType(){ - if(!isArray()){ + public ClassReferenceMirror getComponentType() { + if(!isArray()) { return null; } return new ClassReferenceMirror(name.substring(1)); } - + @Override public String toString() { - return ClassUtils.getCommonNameFromJVMName(name); + String ret; + if(isTemplateDefinition()) { + + } + ret = ClassUtils.getCommonNameFromJVMName(name); + if(genericParameters != null && genericParameters.length != 0) { + ret += "<"; + boolean first = true; + for(ClassReferenceMirror r : genericParameters) { + if(!first) { + ret += ", "; + } + first = false; + ret += r.toString(); + } + ret += ">"; + } + return ret; } @Override @@ -97,18 +209,14 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (obj == null) { + if(obj == null) { return false; } - if (getClass() != obj.getClass()) { + if(getClass() != obj.getClass()) { return false; } final ClassReferenceMirror other = (ClassReferenceMirror) obj; return !((this.name == null) ? (other.name != null) : !this.name.equals(other.name)); } - - - - - + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/ConstructorMirror.java b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/ConstructorMirror.java new file mode 100644 index 0000000000..e12c5d5191 --- /dev/null +++ b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/ConstructorMirror.java @@ -0,0 +1,86 @@ +package com.laytonsmith.PureUtilities.ClassLoading.ClassMirror; + +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.List; + +/** + * This wraps a constructor, which is essentially a method. + * @param + */ +public class ConstructorMirror extends AbstractMethodMirror { + + /** + * This is the method name for constructors in the JVM. It is the string "<init>". + */ + public static final String INIT = ""; + + public ConstructorMirror(ClassReferenceMirror parentClass, List annotations, + ModifierMirror modifiers, ClassReferenceMirror type, String name, List params, + boolean isVararg, boolean isSynthetic, String signature) { + super(parentClass, annotations, modifiers, type, name, params, isVararg, isSynthetic, signature); + } + + public ConstructorMirror(Constructor cons) { + super(cons); + } + + /* package */ ConstructorMirror(ClassReferenceMirror parentClass, ModifierMirror modifiers, + ClassReferenceMirror type, String name, List params, boolean isVararg, + boolean isSynthetic, String signature) { + super(parentClass, modifiers, type, name, params, isVararg, isSynthetic, signature); + } + + public ConstructorMirror(MethodMirror copy) { + super(copy.getDeclaringClass(), copy.modifiers, copy.type, copy.name, copy.getParams(), copy.isVararg(), + copy.isSynthetic(), copy.signature.toString()); + this.setLineNumber(copy.getLineNumber()); + if(!INIT.equals(copy.name)) { + throw new IllegalArgumentException("Only constructors may be mirrored by " + + this.getClass().getSimpleName()); + } + } + + /** + * Gets the Constructor object. This inherently loads the parent class using the default classloader. + * + * This also loads all parameter type's classes as well. + * + * @return + * @throws java.lang.ClassNotFoundException + */ + public Constructor loadConstructor() throws ClassNotFoundException { + return (Constructor) loadConstructor(ConstructorMirror.class.getClassLoader(), true); + } + + /** + * This loads the parent class, and returns the {@link Constructor} object. This also loads all parameter type's + * classes as well. + *

+ * If this class was created with an actual Constructor, then that is simply returned. + * + * This also loads all parameter type's classes as well. + * + * @param loader + * @param initialize + * @return + * @throws ClassNotFoundException + */ + public Constructor loadConstructor(ClassLoader loader, boolean initialize) throws ClassNotFoundException { + if(getExecutable() != null) { + return (Constructor) getExecutable(); + } + Class parent = loadParentClass(loader, initialize); + List cParams = new ArrayList<>(); + for(ClassReferenceMirror c : getParams()) { + cParams.add(c.loadClass(loader, initialize)); + } + try { + return parent.getConstructor(cParams.toArray(new Class[cParams.size()])); + } catch (NoSuchMethodException | SecurityException ex) { + //There's really no way for any exception to happen here, so just rethrow + throw new RuntimeException(ex); + } + } + +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/ElementSignature.java b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/ElementSignature.java new file mode 100644 index 0000000000..6dbc142e02 --- /dev/null +++ b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/ElementSignature.java @@ -0,0 +1,262 @@ +package com.laytonsmith.PureUtilities.ClassLoading.ClassMirror; + +import com.laytonsmith.PureUtilities.Common.StringUtils; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * An element signature is a JVM format string which represents the type of an element that interacts with generics. + * This will be null for all types where it is just a plain type, and in + * that case, no generics were used. However, if you have a field that is declared such as {@code List} then + * this would have the value {@code Ljava/util/List;} and the type stored in type + * would be {@code Ljava/util/List;}. The type is the only thing available in actual classes, the remaining + * generics data is normally lost, but we retain this information to allow for reification at runtime! + *

+ * It is worth pointing out that normally a new data type classification which is normally unavailable can be + * present here. If the data type starts with the JVM name {@code T}, then it is a template reference. The base + * type of the declaration will be java.lang.Object if the generic definition was unbounded, and whatever the + * value in type is if it was bounded. + *

+ * There are a few formats of values, depending on the complexity of the definition. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
SignatureOriginal JavaDescription
TT;T fieldTemplate type declaration on a field, with no generic parameters. + * Note that normally, if an element is declared without generics, the signature will be null, so having + * a template parameter in the signature is the only way you won't have generics.
()TT;T method()Template declaration on a method return value
(Ljava/lang/String;Ljava/util/Map<[Ljava/lang/String;Ljava/lang/String;>;)Ljava/lang/String;String method(String, Map<String[], String>)Method declaration with String return type, and 2 parameters, including inner generics
Ljava/util/Set<Ljava/lang/Class<+Ljava/util/List;>;>;Set<Class<? extends List>> fieldInner declaration that has wildcard
<J:Ljava/lang/Object;>(Ljava/lang/Class<TJ;>;)TJ;<J> J method(Class<J> c)Template type declared on method
<J:Ljava/lang/String;>(Ljava/lang/Class<TJ;>;)TJ;<J extends String> J method(Class<J> c)Template type defined on method, and upper bounded with extends
(Ljava/lang/Class<*>;)Ljava/lang/Class<*>;Class<*> method(Class<?> c)Wildcard template
(Ljava/lang/Class<*>;)Ljava/lang/Class<-Ljava/lang/String;>;Class<? super String> method(Class>?> c)A lower bounded wildcard
()Lmy/obj/Name<TT;>.InnerClassInnerClass method()A method that returns an instance of a non-static inner class
+ *

+ * The main points to notice here are the general format is {@code (params;...)returnType;} for methods, and just + * {@code returnType;} for fields, and {@code ? extends Class} is {@code +Class}, and generic parameters are + * included in angle brackets. + *

+ * Where templates are present, it can be difficult to calculate the effective compile time value, since the value + * can come from either the class definition, the caller site, or just be undefined entirely with naked references. + * However, there are a few guidelines for determining the best reified type for a given template. If the signature + * is {@code TT;} or {@code ()TT;} then this may mean that T is defined in the class signature, in which case it + * can be found by calling {@link ClassMirror#getGenerics()}. + */ +public final class ElementSignature implements Serializable { + + private static final long serialVersionUID = 1L; + + private static final ClassReferenceMirror[] EMPTY_CLASS_REFERENCE_MIRROR = new ClassReferenceMirror[0]; + + /** + * Returns a new ElementSignature object with the specified signature, or null if the signature is null. + * @param signature + * @return + */ + public static ElementSignature GetSignature(String signature) { + // Inner non-static classes are not currently handled, so let's just disable processing of this for now. + // We can revisit this later if we actually need this functionality, but the parsing logic needs to change. +// if(signature == null) { + return null; +// } +// return new ElementSignature(signature); + } + + private final String signature; + + private final ClassReferenceMirror type; + private final ClassReferenceMirror genericDeclaration; + private final ClassReferenceMirror[] parameters; + + private ElementSignature(String signature) { + this.signature = signature; + // Parse the signature. + + boolean inGenericDeclaration = false; + boolean inParameters = false; + + char start = signature.charAt(0); + if(start == '<') { + inGenericDeclaration = true; + signature = signature.substring(1); + } + + if(start == '(') { + inParameters = true; + signature = signature.substring(1); + } + + StringBuilder buffer = new StringBuilder(); + + @SuppressWarnings("LocalVariableHidesMemberVariable") + ClassReferenceMirror genericDeclaration = null; + @SuppressWarnings("LocalVariableHidesMemberVariable") + ClassReferenceMirror[] parameters = null; + try { + for(int i = 0; i < signature.length(); i++) { + char c = signature.charAt(i); + if(inGenericDeclaration) { + if(c == '>') { + String[] parts = buffer.toString().split(":"); + genericDeclaration = new ClassReferenceMirror(parts[1], parts[0]); + buffer = new StringBuilder(); + inGenericDeclaration = false; + // Parameters always follow generic templates, since fields can't define a template + inParameters = true; + i++; + continue; + } else { + buffer.append(c); + } + } + if(inParameters) { + if(c == ')') { + parameters = parseParameters(buffer.toString()); + buffer = new StringBuilder(); + inParameters = false; + continue; + } else { + buffer.append(c); + } + } + if(!inGenericDeclaration && !inParameters) { + buffer.append(c); + } + } + + this.genericDeclaration = genericDeclaration; + this.parameters = parameters; + this.type = parseParameters(buffer.toString())[0]; + } catch (StackOverflowError e) { + throw new Error("Got SOE while processing " + signature, e); + } + } + + private ClassReferenceMirror[] parseParameters(String params) { + if("".equals(params)) { + return EMPTY_CLASS_REFERENCE_MIRROR; + } + + List ret = new ArrayList<>(); + int arrayStack = 0; + for(int i = 0; i < params.length(); i++) { + char c = params.charAt(i); + if(c == 'Z') { + // Boolean + ret.add(new ClassReferenceMirror(StringUtils.stringMultiply(arrayStack, "[") + "Z")); + arrayStack = 0; + } else if(c == 'B') { + // Byte + ret.add(new ClassReferenceMirror(StringUtils.stringMultiply(arrayStack, "[") + "B")); + arrayStack = 0; + } else if(c == 'S') { + // Short + ret.add(new ClassReferenceMirror(StringUtils.stringMultiply(arrayStack, "[") + "S")); + arrayStack = 0; + } else if(c == 'I') { + // Int + ret.add(new ClassReferenceMirror(StringUtils.stringMultiply(arrayStack, "[") + "I")); + arrayStack = 0; + } else if(c == 'J') { + // Long + ret.add(new ClassReferenceMirror(StringUtils.stringMultiply(arrayStack, "[") + "J")); + arrayStack = 0; + } else if(c == 'F') { + // Float + ret.add(new ClassReferenceMirror(StringUtils.stringMultiply(arrayStack, "[") + "F")); + arrayStack = 0; + } else if(c == 'D') { + // Double + ret.add(new ClassReferenceMirror(StringUtils.stringMultiply(arrayStack, "[") + "D")); + arrayStack = 0; + } else if(c == 'C') { + // Char + ret.add(new ClassReferenceMirror(StringUtils.stringMultiply(arrayStack, "[") + "C")); + arrayStack = 0; + } else if(c == 'V') { + // Void (can't have an array of void) + ret.add(new ClassReferenceMirror("V")); + } else if(c == '[') { + arrayStack++; + } else if(c == 'L' || c == 'T') { + boolean isObject = c == 'L'; + StringBuilder b = new StringBuilder(); + int angleStack = 0; + for(int j = i + 1; j < params.length(); j++) { + i = j; + char z = params.charAt(j); + if(z == ';' && angleStack == 0) { + ret.add(parseObject(c + b.toString() + z)); + break; + } else if(z == '<') { + angleStack++; + b.append(z); + } else if(z == '>') { + angleStack--; + b.append(z); + } else { + b.append(z); + } + } + } + } + return ret.toArray(new ClassReferenceMirror[ret.size()]); + } + + private ClassReferenceMirror parseObject(String obj) { + if("*".equals(obj)) { + return ClassReferenceMirror.WILDCARD; + } + if(!obj.contains("<")) { + // Not a generic, just create it as is + return new ClassReferenceMirror<>(obj); + } + String rootType = obj.replaceAll("(.*?)<.*>;", "$1"); + ClassReferenceMirror[] params = parseParameters(obj.replaceAll(".*?<(.*)>;", "$1")); + return new ClassReferenceMirror(rootType + ";", params); + } + + public boolean isMethod() { + return signature.contains("("); + } + + /** + * This is the type of the element. For methods, this will be the return type, for constructors + * @return + */ + public ClassReferenceMirror getType() { + return new ClassReferenceMirror(signature.replaceAll("(?:<.*>)?(?:\\(.*\\))?(.*?)", "$1")); + } + + @Override + public String toString() { + return (genericDeclaration == null ? "" : genericDeclaration.toString()) + + (parameters == null ? "" : Arrays.deepToString(parameters)) + + type.toString(); + } + +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/FieldMirror.java b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/FieldMirror.java index 32c3eac7bd..885f2c6e01 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/FieldMirror.java +++ b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/FieldMirror.java @@ -1,4 +1,3 @@ - package com.laytonsmith.PureUtilities.ClassLoading.ClassMirror; import com.laytonsmith.PureUtilities.Common.StringUtils; @@ -6,69 +5,134 @@ import java.util.List; /** - * This class gathers information about a field, without actually loading - * the containing class into memory. Most of the methods in {@link java.lang.reflect.Field} are - * available in this class (or have an equivalent Mirror version). + * This class gathers information about a field, without actually loading the containing class into memory. Most of the + * methods in {@link java.lang.reflect.Field} are available in this class (or have an equivalent Mirror version). */ public class FieldMirror extends AbstractElementMirror { + private static final long serialVersionUID = 1L; private final Object value; - + private Field field; + /** * Creates a new FieldMirror based on an actual field, for easy comparisons. - * @param field + * + * @param field */ - public FieldMirror(Field field){ + public FieldMirror(Field field) { super(field); - Object _value = null; + this.field = field; + @SuppressWarnings("LocalVariableHidesMemberVariable") + Object value = null; try { - _value = field.get(null); - } catch (IllegalArgumentException | IllegalAccessException ex) { + // Try to get the value. This will work if the value is hardcoded, i.e. public int i = 5; but will fail + // in some cases. In those cases, that's ok, we just will have a null value here. + value = field.get(null); + } catch (IllegalArgumentException | IllegalAccessException | NullPointerException ex) { // } - this.value = _value; + this.value = value; } - + /** * Creates a new FieldMirror based on the specified parameters. + * * @param annotations * @param modifiers * @param type * @param name * @param value - * @param parent + * @param parent + * @param signature */ - public FieldMirror(ClassReferenceMirror parent, List annotations, ModifierMirror modifiers, ClassReferenceMirror type, String name, Object value){ - super(parent, annotations, modifiers, type, name); + public FieldMirror(ClassReferenceMirror parent, List annotations, ModifierMirror modifiers, + ClassReferenceMirror type, String name, Object value, String signature) { + super(parent, annotations, modifiers, type, name, signature); this.value = value; } - + /** - * Gets the initial value of this field. If various conditions are not met, this - * returns null. Namely, if the field does not have an initial value, - * it is not an Integer, a Float, a Long, a Double or a String (for int, float, - * long or String fields respectively), or the field is not static. - * + * Gets the initial value of this field. If various conditions are not met, this returns null. Namely, if the field + * does not have an initial value, it is not an Integer, a Float, a Long, a Double or a String (for int, float, long + * or String fields respectively), or the field is not static. + * *

- * For FieldMirrors created with an actual Field, the value is simply the current - * static field's value, and doesn't follow the rules listed above. - * - * @return + * For FieldMirrors created with an actual Field, the value is simply the current static field's value, and doesn't + * follow the rules listed above. + * + * @return */ - public Object getValue(){ + public Object getValue() { return value; } @Override public String toString() { - return StringUtils.Join(annotations, "\n") + (annotations.isEmpty()?"":"\n") + (modifiers.toString() - + " " + type).trim() + " " + name + " = " + (value == null?"null":value.toString()) + ";"; + return StringUtils.Join(annotations, "\n") + (annotations.isEmpty() ? "" : "\n") + (modifiers.toString() + + " " + type).trim() + " " + name + " = " + (value == null ? "null" : value.toString()) + ";"; } - + + // Package methods/constructor - /* package */ FieldMirror(ClassReferenceMirror parent, ModifierMirror modifiers, ClassReferenceMirror type, String name, Object value){ - super(parent, null, modifiers, type, name); + /* package */ FieldMirror(ClassReferenceMirror parent, ModifierMirror modifiers, ClassReferenceMirror type, + String name, Object value, String signature) { + super(parent, null, modifiers, type, name, signature); this.value = value; } + /** + * This loads the parent class, and returns the {@link Field} object. This also loads all field's type's class + * as well. + *

+ * If this class was created with an actual Field, then that is simply returned. + * + * @return + * @throws java.lang.ClassNotFoundException + */ + public Field loadField() throws ClassNotFoundException { + return this.loadField(FieldMirror.class.getClassLoader(), true); + } + + /** + * This loads the parent class, and returns the {@link Method} object. + *

+ * If this class was created with an actual Method, then that is simply returned. + * + * @param loader + * @param initialize + * @return + * @throws ClassNotFoundException + */ + public Field loadField(ClassLoader loader, boolean initialize) throws ClassNotFoundException { + if(field != null) { + return field; + } + Class parent = loadParentClass(loader, initialize); + NoSuchFieldException nsfe = null; + do { + try { + field = parent.getDeclaredField(name); + break; + } catch (NoSuchFieldException ex) { + nsfe = ex; + } catch (SecurityException ex) { + throw new RuntimeException(ex); + } + parent = parent.getSuperclass(); + } while(parent != null); + // Try one last time + try { + field = loadParentClass(loader, initialize).getField(name); + } catch (NoSuchFieldException ex) { + nsfe = ex; + } catch (SecurityException ex) { + throw new RuntimeException(ex); + } + + if(field == null && nsfe != null) { + throw new RuntimeException(nsfe); + } + return field; + } + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/MethodMirror.java b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/MethodMirror.java index 0fcc779e65..8ca5bd31d2 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/MethodMirror.java +++ b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/MethodMirror.java @@ -1,145 +1,110 @@ - package com.laytonsmith.PureUtilities.ClassLoading.ClassMirror; -import com.laytonsmith.PureUtilities.Common.StringUtils; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; -import java.util.Objects; /** - * This class gathers information about a method, without actually loading - * the containing class into memory. Most of the methods in {@link java.lang.reflect.Method} are - * available in this class (or have an equivalent Mirror version). + * This class gathers information about a method, without actually loading the containing class into memory. Most of the + * methods in {@link java.lang.reflect.Method} are available in this class (or have an equivalent Mirror version). */ -public class MethodMirror extends AbstractElementMirror { - private static final long serialVersionUID = 1L; - - private final List params; - private boolean isVararg = false; - private boolean isSynthetic = false; - - private Method underlyingMethod = null; - - public MethodMirror(ClassReferenceMirror parentClass, List annotations, ModifierMirror modifiers, - ClassReferenceMirror type, String name, List params, boolean isVararg, boolean isSynthetic){ - super(parentClass, annotations, modifiers, type, name); - this.params = params; - this.isVararg = isVararg; - this.isSynthetic = isSynthetic; +public class MethodMirror extends AbstractMethodMirror { + + private static final long serialVersionUID = 2L; + + public MethodMirror(ClassReferenceMirror parentClass, List annotations, ModifierMirror modifiers, + ClassReferenceMirror type, String name, List params, boolean isVararg, + boolean isSynthetic, String signature) { + super(parentClass, annotations, modifiers, type, name, params, isVararg, isSynthetic, signature); } - - public MethodMirror(Method method){ + + public MethodMirror(Method method) { super(method); - this.underlyingMethod = method; - this.params = null; - } - - /* package */ MethodMirror(ClassReferenceMirror parentClass, ModifierMirror modifiers, ClassReferenceMirror type, - String name, List params, boolean isVararg, boolean isSynthetic){ - super(parentClass, null, modifiers, type, name); - annotations = new ArrayList<>(); - this.params = params; - this.isVararg = isVararg; - this.isSynthetic = isSynthetic; } - - /** - * Returns a list of params in this method. - * @return - */ - public List getParams(){ - if(underlyingMethod != null){ - List list = new ArrayList<>(); - for(Class p : underlyingMethod.getParameterTypes()){ - list.add(ClassReferenceMirror.fromClass(p)); - } - return list; - } - return new ArrayList<>(params); - } - - /** - * Returns true if this method is vararg. - * @return - */ - public boolean isVararg(){ - if(underlyingMethod != null){ - return underlyingMethod.isVarArgs(); - } - return isVararg; - } - - /** - * Returns true if this method is synthetic. - * @return - */ - public boolean isSynthetic(){ - if(underlyingMethod != null){ - return underlyingMethod.isSynthetic(); - } - return isSynthetic; + + /* package */ MethodMirror(ClassReferenceMirror parentClass, ModifierMirror modifiers, ClassReferenceMirror type, + String name, List params, boolean isVararg, boolean isSynthetic, String signature) { + super(parentClass, null, modifiers, type, name, params, isVararg, isSynthetic, signature); } - + /** - * This loads the parent class, and returns the {@link Method} object. - * This also loads all parameter type's classes as well. + * This loads the parent class, and returns the {@link Method} object. This also loads all parameter type's classes + * as well. *

* If this class was created with an actual Method, then that is simply returned. - * @return - * @throws java.lang.ClassNotFoundException + * + * @return + * @throws java.lang.ClassNotFoundException */ - public Method loadMethod() throws ClassNotFoundException{ - if(underlyingMethod != null){ - return underlyingMethod; + public Method loadMethod() throws ClassNotFoundException { + if(getExecutable() != null) { + return (Method) getExecutable(); } return loadMethod(MethodMirror.class.getClassLoader(), true); } - + /** - * This loads the parent class, and returns the {@link Method} object. - * This also loads all parameter type's classes as well. + * This loads the parent class, and returns the {@link Method} object. This also loads all parameter type's classes + * as well. *

* If this class was created with an actual Method, then that is simply returned. + * * @param loader * @param initialize * @return - * @throws ClassNotFoundException + * @throws ClassNotFoundException */ - public Method loadMethod(ClassLoader loader, boolean initialize) throws ClassNotFoundException{ - if(underlyingMethod != null){ - return underlyingMethod; + public Method loadMethod(ClassLoader loader, boolean initialize) throws ClassNotFoundException { + if(getExecutable() != null) { + return (Method) getExecutable(); } - ClassReferenceMirror p = getDeclaringClass(); - Objects.requireNonNull(p, "Declaring class is null!"); - Class parent = p.loadClass(loader, initialize); + + Class parent = loadParentClass(loader, initialize); List cParams = new ArrayList<>(); - for(ClassReferenceMirror c : params){ + for(ClassReferenceMirror c : getParams()) { cParams.add(c.loadClass(loader, initialize)); } + + NoSuchMethodException nsfe = null; + Method method = null; + do { + try { + method = parent.getDeclaredMethod(name, cParams.toArray(new Class[cParams.size()])); + break; + } catch (NoSuchMethodException ex) { + nsfe = ex; + } catch (SecurityException ex) { + throw new RuntimeException(ex); + } + parent = parent.getSuperclass(); + } while(parent != null); + + // Try one last time try { - return parent.getMethod(name, cParams.toArray(new Class[cParams.size()])); - } catch (Exception ex) { - //There's really no way for any exception to happen here, so just rethrow + method = loadParentClass(loader, initialize).getMethod(name, cParams.toArray(new Class[cParams.size()])); + } catch (NoSuchMethodException ex) { + nsfe = ex; + } catch (SecurityException ex) { throw new RuntimeException(ex); } + + if(method == null && nsfe != null) { + throw new RuntimeException(nsfe); + } + return method; } @Override - public String toString() { - if(underlyingMethod != null){ - return underlyingMethod.toString(); - } - List sParams = new ArrayList<>(); - for(int i = 0; i < params.size(); i++){ - if(i == params.size() - 1 && isVararg){ - sParams.add(params.get(i).getComponentType().toString() + "..."); - } else { - sParams.add(params.get(i).toString()); - } + public boolean equals(Object obj) { + if(!(obj instanceof MethodMirror)) { + return false; } - return StringUtils.Join(annotations, "\n") + (annotations.isEmpty()?"":"\n") + (modifiers.toString() - + " " + type).trim() + " " + name + "(" + StringUtils.Join(sParams, ", ") + "){}"; + return super.equals(obj); } - + + @Override + public int hashCode() { + return super.hashCode(); + } + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/ModifierMirror.java b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/ModifierMirror.java index 952e292f7b..fc45a910c0 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/ModifierMirror.java +++ b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/ModifierMirror.java @@ -1,4 +1,3 @@ - package com.laytonsmith.PureUtilities.ClassLoading.ClassMirror; import com.laytonsmith.PureUtilities.Common.StringUtils; @@ -9,100 +8,103 @@ import org.objectweb.asm.Opcodes; /** - * This is a mirror for the {@link java.lang.reflect.Modifier} class. - * This provides is* methods for all the modifiers, instead of requiring - * bitwise logic to determine if various modifiers are present. + * This is a mirror for the {@link java.lang.reflect.Modifier} class. This provides is* methods for all the modifiers, + * instead of requiring bitwise logic to determine if various modifiers are present. */ public class ModifierMirror implements Serializable { + private static final long serialVersionUID = 1L; private final int access; private int modifiers = 0; /** * This is the canonical order of modifiers, used in the toString method. */ - private static transient final Object[] order = new Object[]{ - Modifier.PUBLIC, "public", - Modifier.PRIVATE, "private", - Modifier.PROTECTED, "protected", - Modifier.STATIC, "static", - Modifier.FINAL, "final", - Modifier.SYNCHRONIZED, "synchronized", - Modifier.VOLATILE, "volatile", - Modifier.TRANSIENT, "transient", - Modifier.NATIVE, "native", - Modifier.INTERFACE, "interface", - Modifier.ABSTRACT, "abstract", + private static final transient Object[] ORDER = new Object[]{ + Modifier.PUBLIC, "public", + Modifier.PRIVATE, "private", + Modifier.PROTECTED, "protected", + Modifier.STATIC, "static", + Modifier.FINAL, "final", + Modifier.SYNCHRONIZED, "synchronized", + Modifier.VOLATILE, "volatile", + Modifier.TRANSIENT, "transient", + Modifier.NATIVE, "native", + Modifier.INTERFACE, "interface", + Modifier.ABSTRACT, "abstract", Modifier.STRICT, "strictfp"}; - + /** - * This constructor is used when mirroring an already loaded class. The modifier - * is just the modifier returned by Class. - * @param modifier + * This constructor is used when mirroring an already loaded class. The modifier is just the modifier returned by + * Class. + * + * @param modifier */ - public ModifierMirror(int modifier){ + public ModifierMirror(int modifier) { this.access = 0; this.modifiers = modifier; } /** - * Creates a new ModifierMirror. Type is needed to determine which flags are applicable, and access - * is the access modifier stored with the class file. - * @param type Since asm encodes the parameters slightly differently for various types, the type must also be provided. - * @param access + * Creates a new ModifierMirror. Type is needed to determine which flags are applicable, and access is the access + * modifier stored with the class file. + * + * @param type Since asm encodes the parameters slightly differently for various types, the type must also be + * provided. + * @param access */ public ModifierMirror(Type type, int access) { this.access = access; //public, private, protected, final are all valid on all three types - if(type == Type.CLASS || type == Type.METHOD || type == Type.FIELD){ - if (hasFlag(Opcodes.ACC_PRIVATE)) { + if(type == Type.CLASS || type == Type.METHOD || type == Type.FIELD) { + if(hasFlag(Opcodes.ACC_PRIVATE)) { modifiers |= Modifier.PRIVATE; } - if (hasFlag(Opcodes.ACC_PROTECTED)) { + if(hasFlag(Opcodes.ACC_PROTECTED)) { modifiers |= Modifier.PROTECTED; } - if (hasFlag(Opcodes.ACC_PUBLIC)) { + if(hasFlag(Opcodes.ACC_PUBLIC)) { modifiers |= Modifier.PUBLIC; } - if (hasFlag(Opcodes.ACC_FINAL)) { + if(hasFlag(Opcodes.ACC_FINAL)) { modifiers |= Modifier.FINAL; } } //static is only valid on fields and methods - if (type == Type.FIELD || type == Type.METHOD){ + if(type == Type.FIELD || type == Type.METHOD) { if(hasFlag(Opcodes.ACC_STATIC)) { modifiers |= Modifier.STATIC; } } //interface is only valid on a class - if(type == Type.CLASS){ - if (hasFlag(Opcodes.ACC_INTERFACE)) { + if(type == Type.CLASS) { + if(hasFlag(Opcodes.ACC_INTERFACE)) { modifiers |= Modifier.INTERFACE; } } //abstract is only valid on classes or methods - if(type == Type.CLASS || type == Type.METHOD){ - if (hasFlag(Opcodes.ACC_ABSTRACT)) { + if(type == Type.CLASS || type == Type.METHOD) { + if(hasFlag(Opcodes.ACC_ABSTRACT)) { modifiers |= Modifier.ABSTRACT; } } //native, strict, and synchronized are only valid on methods - if(type == Type.METHOD){ - if (hasFlag(Opcodes.ACC_NATIVE)) { + if(type == Type.METHOD) { + if(hasFlag(Opcodes.ACC_NATIVE)) { modifiers |= Modifier.NATIVE; } - if (hasFlag(Opcodes.ACC_STRICT)) { + if(hasFlag(Opcodes.ACC_STRICT)) { modifiers |= Modifier.STRICT; } - if (hasFlag(Opcodes.ACC_SYNCHRONIZED)) { + if(hasFlag(Opcodes.ACC_SYNCHRONIZED)) { modifiers |= Modifier.SYNCHRONIZED; } } //transient and volatile are only valid on fields - if(type == Type.FIELD){ - if (hasFlag(Opcodes.ACC_TRANSIENT)) { + if(type == Type.FIELD) { + if(hasFlag(Opcodes.ACC_TRANSIENT)) { modifiers |= Modifier.TRANSIENT; } - if (hasFlag(Opcodes.ACC_VOLATILE)) { + if(hasFlag(Opcodes.ACC_VOLATILE)) { modifiers |= Modifier.VOLATILE; } } @@ -114,7 +116,8 @@ private boolean hasFlag(int flag) { /** * Returns true if the abstract modifier was present. - * @return + * + * @return */ public boolean isAbstract() { return (modifiers & Modifier.ABSTRACT) > 0; @@ -122,7 +125,8 @@ public boolean isAbstract() { /** * Returns true if the final modifier was present - * @return + * + * @return */ public boolean isFinal() { return (modifiers & Modifier.FINAL) > 0; @@ -130,7 +134,8 @@ public boolean isFinal() { /** * Returns true if the class is an interface - * @return + * + * @return */ public boolean isInterface() { return (modifiers & Modifier.ABSTRACT) > 0; @@ -138,7 +143,8 @@ public boolean isInterface() { /** * Returns true if the native modifier was present - * @return + * + * @return */ public boolean isNative() { return (modifiers & Modifier.NATIVE) > 0; @@ -146,7 +152,8 @@ public boolean isNative() { /** * Returns true if the private modifier was present - * @return + * + * @return */ public boolean isPrivate() { return (modifiers & Modifier.PRIVATE) > 0; @@ -154,7 +161,8 @@ public boolean isPrivate() { /** * Returns true if the protected modifier was present - * @return + * + * @return */ public boolean isProtected() { return (modifiers & Modifier.PROTECTED) > 0; @@ -162,30 +170,29 @@ public boolean isProtected() { /** * Returns true if the public modifier was present - * @return + * + * @return */ public boolean isPublic() { return (modifiers & Modifier.PUBLIC) > 0; } - + /** - * Returns true if this is package private, that is, - * it is not public, private, or protected. No access - * modifier is present, rather this is the lack of - * the other three modifiers. - * @return + * Returns true if this is package private, that is, it is not public, private, or protected. No access modifier is + * present, rather this is the lack of the other three modifiers. + * + * @return */ - public boolean isPackagePrivate(){ - return !( - isPrivate() + public boolean isPackagePrivate() { + return !(isPrivate() || isProtected() - || isPublic() - ); + || isPublic()); } /** * Returns true if the static modifier was present - * @return + * + * @return */ public boolean isStatic() { return (modifiers & Modifier.STATIC) > 0; @@ -193,7 +200,8 @@ public boolean isStatic() { /** * Returns true if the strictfp modifier was present - * @return + * + * @return */ public boolean isStrict() { return (modifiers & Modifier.STRICT) > 0; @@ -201,7 +209,8 @@ public boolean isStrict() { /** * Returns true if the synchronized modifier was present - * @return + * + * @return */ public boolean isSynchronized() { return (modifiers & Modifier.SYNCHRONIZED) > 0; @@ -209,7 +218,8 @@ public boolean isSynchronized() { /** * Returns true if the transient modifier was present - * @return + * + * @return */ public boolean isTransient() { return (modifiers & Modifier.TRANSIENT) > 0; @@ -217,15 +227,16 @@ public boolean isTransient() { /** * Returns true if the volatile modifier was present - * @return + * + * @return */ public boolean isVolatile() { return (modifiers & Modifier.VOLATILE) > 0; } /** - * Returns an int compatible with the {@link java.lang.reflect.Modifier} - * class. + * Returns an int compatible with the {@link java.lang.reflect.Modifier} class. + * * @return */ public int getModifiers() { @@ -235,17 +246,17 @@ public int getModifiers() { @Override public String toString() { List build = new ArrayList(); - for (int i = 0; i < order.length; i++) { - int type = (Integer) order[i]; - String name = (String) order[++i]; - if ((modifiers & type) > 0) { + for(int i = 0; i < ORDER.length; i++) { + int type = (Integer) ORDER[i]; + String name = (String) ORDER[++i]; + if((modifiers & type) > 0) { build.add(name); } } return StringUtils.Join(build, " "); } - - public static enum Type{ + + public static enum Type { CLASS, METHOD, FIELD; } diff --git a/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/PackageMirror.java b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/PackageMirror.java index 0c389ec923..ab1bf23b42 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/PackageMirror.java +++ b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/ClassMirror/PackageMirror.java @@ -7,27 +7,29 @@ * A package mirror provides information about the package a class is in. */ public class PackageMirror implements Serializable { + private static final long serialVersionUID = 1L; private final String name; - - public PackageMirror(String name){ + + public PackageMirror(String name) { this.name = name; } - + /** * Returns the name of the package - * @return + * + * @return */ - public String getName(){ + public String getName() { return name; } - + /** - * Attempts to load the underlying {@link java.lang.Package} object. - * If the package doesn't exist, null is returned. - * @return + * Attempts to load the underlying {@link java.lang.Package} object. If the package doesn't exist, null is returned. + * + * @return */ - public Package loadPackage(){ + public Package loadPackage() { return Package.getPackage(name); } @@ -40,18 +42,17 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (obj == null) { + if(obj == null) { return false; } - if (getClass() != obj.getClass()) { + if(getClass() != obj.getClass()) { return false; } final PackageMirror other = (PackageMirror) obj; - if (!Objects.equals(this.name, other.name)) { + if(!Objects.equals(this.name, other.name)) { return false; } return true; } - - + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/DynamicClassLoader.java b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/DynamicClassLoader.java index 6dff42ee0e..5101b2799d 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/DynamicClassLoader.java +++ b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/DynamicClassLoader.java @@ -1,4 +1,3 @@ - package com.laytonsmith.PureUtilities.ClassLoading; import com.laytonsmith.PureUtilities.Common.ReflectionUtils; @@ -7,124 +6,145 @@ import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Arrays; +import java.util.Enumeration; import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; /** - * This class extends ClassLoader, but allows for new jars/classpath elements - * to be added at runtime. Note that already loaded classes won't be affected - * if a new jar is added, but new requests for classes will be. + * This class extends ClassLoader, but allows for new jars/classpath elements to be added at runtime. Note that already + * loaded classes won't be affected if a new jar is added, but new requests for classes will be. */ public class DynamicClassLoader extends ClassLoader { - - private final Map classLoaders = new LinkedHashMap(); - private final Set urls = new HashSet(); + + private final Map classLoaders = new LinkedHashMap<>(); + private final Set urls = new HashSet<>(); private boolean destroyed = false; - + /** - * Adds a jar to this class loader instance. This can be done at runtime, - * and if a jar already defines a class (or the class has already been loaded) - * it cannot be changed inside this instance. Adding the same URL twice has - * no effect. + * Adds a jar to this class loader instance. This can be done at runtime, and if a jar already defines a class (or + * the class has already been loaded) it cannot be changed inside this instance. Adding the same URL twice has no + * effect. + * * @param url The url to the jar. */ - public synchronized void addJar(URL url){ + public synchronized void addJar(URL url) { checkDestroy(); - if(urls.contains(url)){ + if(urls.contains(url)) { return; } urls.add(url); classLoaders.put(url, new URLClassLoader(new URL[]{url}, DynamicClassLoader.class.getClassLoader())); } - + /** - * Remove a jar from this class loader instance. Adding the same URL twice has - * no effect. - * - * @param url + * Remove a jar from this class loader instance. Adding the same URL twice has no effect. + * + * @param url */ public synchronized void removeJar(URL url) { - checkDestroy(); - - if(!urls.contains(url)){ + checkDestroy(); + + if(!urls.contains(url)) { return; } - + urls.remove(url); URLClassLoader l = classLoaders.remove(url); - + try { l.close(); } catch (IOException ex) { // Whatever. } - } + } @Override protected synchronized Package getPackage(String name) { - for(ClassLoader c : classLoaders.values()){ + for(ClassLoader c : classLoaders.values()) { Package p = (Package) ReflectionUtils.invokeMethod(c.getClass(), c, "getPackage", new Class[]{String.class}, new Object[]{name}); - if(p != null){ + if(p != null) { return p; } } return null; } + @Override + public Enumeration getResources(String name) throws IOException { + List locations = new ArrayList<>(); + for(ClassLoader c : classLoaders.values()) { + Enumeration res = c.getResources(name); + while(res.hasMoreElements()) { + locations.add(res.nextElement()); + } + } + Iterator iterator = locations.iterator(); + return new Enumeration() { + @Override + public boolean hasMoreElements() { + return iterator.hasNext(); + } + + @Override + public Object nextElement() { + return iterator.next(); + } + }; + } + @Override protected synchronized Package[] getPackages() { - List packages = new ArrayList(); - for(ClassLoader c : classLoaders.values()){ - packages.addAll(Arrays.asList((Package[])ReflectionUtils.invokeMethod(c.getClass(), c, "getPackages"))); + List packages = new ArrayList<>(); + for(ClassLoader c : classLoaders.values()) { + packages.addAll(Arrays.asList((Package[]) ReflectionUtils.invokeMethod(c.getClass(), c, "getPackages"))); } return packages.toArray(new Package[packages.size()]); } - @Override protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { checkDestroy(); - try{ + try { //If the parent class knows about it, we're done. Class c = Class.forName(name, resolve, DynamicClassLoader.class.getClassLoader()); return c; - } catch(ClassNotFoundException ex){ + } catch (ClassNotFoundException ex) { //Otherwise we need to find the class ourselves. - for(URLClassLoader url : classLoaders.values()){ - try{ + for(URLClassLoader url : classLoaders.values()) { + try { Class c = url.loadClass(name); - if(resolve){ + if(resolve) { resolveClass(c); } return c; - } catch(ClassNotFoundException ex1){ + } catch (ClassNotFoundException ex1) { //Don't care, move on to the next class loader } } throw new ClassNotFoundException(name); } } - + /** - * When this class is no longer needed, this method can be called - * to destroy all the internal references and "lock" the class for - * future use. If any method (other than destroy) is attempted to be - * called in this instance after this method is called, it will throw - * a runtime exception, which should hopefully help catch errors faster. - * TODO: Use this in the code! + * When this class is no longer needed, this method can be called to destroy all the internal references and "lock" + * the class for future use. If any method (other than destroy) is attempted to be called in this instance after + * this method is called, it will throw a runtime exception, which should hopefully help catch errors faster. TODO: + * Use this in the code! */ - public synchronized void destroy(){ + public synchronized void destroy() { destroyed = true; classLoaders.clear(); urls.clear(); } - - private void checkDestroy(){ - if(destroyed){ - throw new RuntimeException("Cannot access this instance of " + DynamicClassLoader.class.getSimpleName() + ", as it has already been destroyed."); + + private void checkDestroy() { + if(destroyed) { + throw new RuntimeException("Cannot access this instance of " + DynamicClassLoader.class.getSimpleName() + + ", as it has already been destroyed."); } } @@ -134,5 +154,5 @@ protected void finalize() throws Throwable { super.finalize(); destroy(); } - + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/DynamicEnum.java b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/DynamicEnum.java new file mode 100644 index 0000000000..4ea55d8829 --- /dev/null +++ b/src/main/java/com/laytonsmith/PureUtilities/ClassLoading/DynamicEnum.java @@ -0,0 +1,30 @@ +package com.laytonsmith.PureUtilities.ClassLoading; + +/** + * + */ +public abstract class DynamicEnum { + + protected Abstracted abstracted; // field name reflectively accessed + protected Concrete concrete; + + public DynamicEnum(Abstracted abstracted, Concrete concrete) { + this.abstracted = abstracted; + this.concrete = concrete; + } + + public abstract String name(); + + public Abstracted getAbstracted() { + return abstracted; + } + + public Concrete getConcrete() { + return concrete; + } + + @Override + public String toString() { + return name(); + } +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/Color.java b/src/main/java/com/laytonsmith/PureUtilities/Color.java new file mode 100644 index 0000000000..f83b7fffb5 --- /dev/null +++ b/src/main/java/com/laytonsmith/PureUtilities/Color.java @@ -0,0 +1,846 @@ +package com.laytonsmith.PureUtilities; + +import java.beans.ConstructorProperties; + +/** + * This class is meant as a near drop in replacement for java.awt.Color, except it doesn't initialize the GUI framework, + * which is undesirable in cmdline applications. An instance of this class can be converted to a java.awt.Color, + * however, if absolutely necessary. + * + * While this is attempting to (mostly) be a drop in replacement, not all features are available, particularly the ones + * that rely on other java.awt.* classes. + */ +public class Color implements java.io.Serializable { + + /** + * The color white. In the default sRGB space. + */ + public static final Color WHITE = new Color(255, 255, 255); + + /** + * The color light gray. In the default sRGB space. + */ + public static final Color LIGHT_GRAY = new Color(192, 192, 192); + + /** + * The color gray. In the default sRGB space. + */ + public static final Color GRAY = new Color(128, 128, 128); + + /** + * The color dark gray. In the default sRGB space. + */ + public static final Color DARK_GRAY = new Color(64, 64, 64); + + /** + * The color black. In the default sRGB space. + */ + public static final Color BLACK = new Color(0, 0, 0); + + /** + * The color red. In the default sRGB space. + */ + public static final Color RED = new Color(255, 0, 0); + + /** + * The color pink. In the default sRGB space. + */ + public static final Color PINK = new Color(255, 175, 175); + + /** + * The color orange. In the default sRGB space. + */ + public static final Color ORANGE = new Color(255, 200, 0); + + /** + * The color yellow. In the default sRGB space. + */ + public static final Color YELLOW = new Color(255, 255, 0); + + /** + * The color green. In the default sRGB space. + */ + public static final Color GREEN = new Color(0, 255, 0); + + /** + * The color magenta. In the default sRGB space. + */ + public static final Color MAGENTA = new Color(255, 0, 255); + + /** + * The color cyan. In the default sRGB space. + */ + public static final Color CYAN = new Color(0, 255, 255); + + /** + * The color blue. In the default sRGB space. + */ + public static final Color BLUE = new Color(0, 0, 255); + + /** + * The color value. + * + * @serial + * @see #getRGB + */ + int value; + + /** + * The color value in the default sRGB ColorSpace as float components (no alpha). If + * null after object construction, this must be an sRGB color constructed with 8-bit precision, so + * compute from the int color value. + * + * @serial + * @see #getRGBColorComponents + * @see #getRGBComponents + */ + private float frgbvalue[] = null; + + /** + * The color value in the native ColorSpace as float components (no alpha). If + * null after object construction, this must be an sRGB color constructed with 8-bit precision, so + * compute from the int color value. + * + * @serial + * @see #getRGBColorComponents + * @see #getRGBComponents + */ + private float fvalue[] = null; + + /** + * The alpha value as a float component. If frgbvalue is null, this is not + * valid data, so compute from the int color value. + * + * @serial + * @see #getRGBComponents + * @see #getComponents + */ + private float falpha = 0.0f; + + /* + * JDK 1.1 serialVersionUID + */ + private static final long serialVersionUID = 118526816881161077L; + + /** + * Checks the color integer components supplied for validity. Throws an {@link IllegalArgumentException} if the + * value is out of range. + * + * @param r the Red component + * @param g the Green component + * @param b the Blue component + * + */ + private static void testColorValueRange(int r, int g, int b, int a) { + boolean rangeError = false; + String badComponentString = ""; + + if(a < 0 || a > 255) { + rangeError = true; + badComponentString = badComponentString + " Alpha"; + } + if(r < 0 || r > 255) { + rangeError = true; + badComponentString = badComponentString + " Red"; + } + if(g < 0 || g > 255) { + rangeError = true; + badComponentString = badComponentString + " Green"; + } + if(b < 0 || b > 255) { + rangeError = true; + badComponentString = badComponentString + " Blue"; + } + if(rangeError == true) { + throw new IllegalArgumentException("Color parameter outside of expected range:" + + badComponentString); + } + } + + /** + * Checks the color float components supplied for validity. Throws an + * IllegalArgumentException if the value is out of range. + * + * @param r the Red component + * @param g the Green component + * @param b the Blue component + * + */ + private static void testColorValueRange(float r, float g, float b, float a) { + boolean rangeError = false; + String badComponentString = ""; + if(a < 0.0 || a > 1.0) { + rangeError = true; + badComponentString = badComponentString + " Alpha"; + } + if(r < 0.0 || r > 1.0) { + rangeError = true; + badComponentString = badComponentString + " Red"; + } + if(g < 0.0 || g > 1.0) { + rangeError = true; + badComponentString = badComponentString + " Green"; + } + if(b < 0.0 || b > 1.0) { + rangeError = true; + badComponentString = badComponentString + " Blue"; + } + if(rangeError == true) { + throw new IllegalArgumentException("Color parameter outside of expected range:" + + badComponentString); + } + } + + /** + * Creates an opaque sRGB color with the specified red, green, and blue values in the range (0 - 255). The actual + * color used in rendering depends on finding the best match given the color space available for a given output + * device. Alpha is defaulted to 255. + * + * @throws IllegalArgumentException if r, g or b are outside of the range 0 + * to 255, inclusive + * @param r the red component + * @param g the green component + * @param b the blue component + * @see #getRed + * @see #getGreen + * @see #getBlue + * @see #getRGB + */ + public Color(int r, int g, int b) { + this(r, g, b, 255); + } + + /** + * Creates an sRGB color with the specified red, green, blue, and alpha values in the range (0 - 255). + * + * @throws IllegalArgumentException if r, g, b or a are outside + * of the range 0 to 255, inclusive + * @param r the red component + * @param g the green component + * @param b the blue component + * @param a the alpha component + * @see #getRed + * @see #getGreen + * @see #getBlue + * @see #getAlpha + * @see #getRGB + */ + @ConstructorProperties({"red", "green", "blue", "alpha"}) + public Color(int r, int g, int b, int a) { + value = ((a & 0xFF) << 24) + | ((r & 0xFF) << 16) + | ((g & 0xFF) << 8) + | ((b & 0xFF)); + testColorValueRange(r, g, b, a); + } + + /** + * Creates an opaque sRGB color with the specified combined RGB value consisting of the red component in bits 16-23, + * the green component in bits 8-15, and the blue component in bits 0-7. The actual color used in rendering depends + * on finding the best match given the color space available for a particular output device. Alpha is defaulted to + * 255. + * + * @param rgb the combined RGB components + * @see java.awt.image.ColorModel#getRGBdefault + * @see #getRed + * @see #getGreen + * @see #getBlue + * @see #getRGB + */ + public Color(int rgb) { + value = 0xff000000 | rgb; + } + + /** + * Creates an sRGB color with the specified combined RGBA value consisting of the alpha component in bits 24-31, the + * red component in bits 16-23, the green component in bits 8-15, and the blue component in bits 0-7. If the + * hasalpha argument is false, alpha is defaulted to 255. + * + * @param rgba the combined RGBA components + * @param hasalpha true if the alpha bits are valid; false otherwise + * @see java.awt.image.ColorModel#getRGBdefault + * @see #getRed + * @see #getGreen + * @see #getBlue + * @see #getAlpha + * @see #getRGB + */ + public Color(int rgba, boolean hasalpha) { + if(hasalpha) { + value = rgba; + } else { + value = 0xff000000 | rgba; + } + } + + /** + * Creates an opaque sRGB color with the specified red, green, and blue values in the range (0.0 - 1.0). Alpha is + * defaulted to 1.0. The actual color used in rendering depends on finding the best match given the color space + * available for a particular output device. + * + * @throws IllegalArgumentException if r, g or b are outside of the range 0.0 + * to 1.0, inclusive + * @param r the red component + * @param g the green component + * @param b the blue component + * @see #getRed + * @see #getGreen + * @see #getBlue + * @see #getRGB + */ + public Color(float r, float g, float b) { + this((int) (r * 255 + 0.5), (int) (g * 255 + 0.5), (int) (b * 255 + 0.5)); + testColorValueRange(r, g, b, 1.0f); + frgbvalue = new float[3]; + frgbvalue[0] = r; + frgbvalue[1] = g; + frgbvalue[2] = b; + falpha = 1.0f; + fvalue = frgbvalue; + } + + /** + * Creates an sRGB color with the specified red, green, blue, and alpha values in the range (0.0 - 1.0). The actual + * color used in rendering depends on finding the best match given the color space available for a particular output + * device. + * + * @throws IllegalArgumentException if r, g b or a are outside + * of the range 0.0 to 1.0, inclusive + * @param r the red component + * @param g the green component + * @param b the blue component + * @param a the alpha component + * @see #getRed + * @see #getGreen + * @see #getBlue + * @see #getAlpha + * @see #getRGB + */ + public Color(float r, float g, float b, float a) { + this((int) (r * 255 + 0.5), (int) (g * 255 + 0.5), (int) (b * 255 + 0.5), (int) (a * 255 + 0.5)); + frgbvalue = new float[3]; + frgbvalue[0] = r; + frgbvalue[1] = g; + frgbvalue[2] = b; + falpha = a; + fvalue = frgbvalue; + } + + /** + * Returns the red component in the range 0-255 in the default sRGB space. + * + * @return the red component. + * @see #getRGB + */ + public int getRed() { + return (getRGB() >> 16) & 0xFF; + } + + /** + * Returns the green component in the range 0-255 in the default sRGB space. + * + * @return the green component. + * @see #getRGB + */ + public int getGreen() { + return (getRGB() >> 8) & 0xFF; + } + + /** + * Returns the blue component in the range 0-255 in the default sRGB space. + * + * @return the blue component. + * @see #getRGB + */ + public int getBlue() { + return (getRGB()) & 0xFF; + } + + /** + * Returns the alpha component in the range 0-255. + * + * @return the alpha component. + * @see #getRGB + */ + public int getAlpha() { + return (getRGB() >> 24) & 0xff; + } + + /** + * Returns the RGB value representing the color in the default sRGB {@link ColorModel}. (Bits 24-31 are alpha, 16-23 + * are red, 8-15 are green, 0-7 are blue). + * + * @return the RGB value of the color in the default sRGB ColorModel. + * @see java.awt.image.ColorModel#getRGBdefault + * @see #getRed + * @see #getGreen + * @see #getBlue + * @since JDK1.0 + */ + public int getRGB() { + return value; + } + + private static final double FACTOR = 0.7; + + /** + * Creates a new Color that is a brighter version of this Color. + *

+ * This method applies an arbitrary scale factor to each of the three RGB components of this Color to + * create a brighter version of this Color. The {@code alpha} value is preserved. Although + * brighter and darker are inverse operations, the results of a series of invocations of + * these two methods might be inconsistent because of rounding errors. + * + * @return a new Color object that is a brighter version of this Color with the same + * {@code alpha} value. + * @see java.awt.Color#darker + * @since JDK1.0 + */ + public Color brighter() { + int r = getRed(); + int g = getGreen(); + int b = getBlue(); + int alpha = getAlpha(); + + /* From 2D group: + * 1. black.brighter() should return grey + * 2. applying brighter to blue will always return blue, brighter + * 3. non pure color (non zero rgb) will eventually return white + */ + int i = (int) (1.0 / (1.0 - FACTOR)); + if(r == 0 && g == 0 && b == 0) { + return new Color(i, i, i, alpha); + } + if(r > 0 && r < i) { + r = i; + } + if(g > 0 && g < i) { + g = i; + } + if(b > 0 && b < i) { + b = i; + } + + return new Color(Math.min((int) (r / FACTOR), 255), + Math.min((int) (g / FACTOR), 255), + Math.min((int) (b / FACTOR), 255), + alpha); + } + + /** + * Creates a new Color that is a darker version of this Color. + *

+ * This method applies an arbitrary scale factor to each of the three RGB components of this Color to + * create a darker version of this Color. The {@code alpha} value is preserved. Although + * brighter and darker are inverse operations, the results of a series of invocations of + * these two methods might be inconsistent because of rounding errors. + * + * @return a new Color object that is a darker version of this Color with the same + * {@code alpha} value. + * @see java.awt.Color#brighter + * @since JDK1.0 + */ + public Color darker() { + return new Color(Math.max((int) (getRed() * FACTOR), 0), + Math.max((int) (getGreen() * FACTOR), 0), + Math.max((int) (getBlue() * FACTOR), 0), + getAlpha()); + } + + /** + * Computes the hash code for this Color. + * + * @return a hash code value for this object. + * @since JDK1.0 + */ + @Override + public int hashCode() { + return value; + } + + /** + * Determines whether another object is equal to this Color. + *

+ * The result is true if and only if the argument is not null and is a Color + * object that has the same red, green, blue, and alpha values as this object. + * + * @param obj the object to test for equality with this Color + * @return true if the objects are the same; false otherwise. + * @since JDK1.0 + */ + @Override + public boolean equals(Object obj) { + return obj instanceof Color && ((Color) obj).getRGB() == this.getRGB(); + } + + /** + * Returns a string representation of this Color. This method is intended to be used only for debugging + * purposes. The content and format of the returned string might vary between implementations. The returned string + * might be empty but cannot be null. + * + * @return a string representation of this Color. + */ + @Override + public String toString() { + return getClass().getName() + "[r=" + getRed() + ",g=" + getGreen() + ",b=" + getBlue() + "]"; + } + + /** + * Converts a String to an integer and returns the specified opaque Color. This method + * handles string formats that are used to represent octal and hexadecimal numbers. + * + * @param nm a String that represents an opaque color as a 24-bit integer + * @return the new Color object. + * @see java.lang.Integer#decode + * @exception NumberFormatException if the specified string cannot be interpreted as a decimal, octal, or + * hexadecimal integer. + * @since JDK1.1 + */ + public static Color decode(String nm) throws NumberFormatException { + Integer intval = Integer.decode(nm); + int i = intval; + return new Color((i >> 16) & 0xFF, (i >> 8) & 0xFF, i & 0xFF); + } + + /** + * Finds a color in the system properties. + *

+ * The argument is treated as the name of a system property to be obtained. The string value of this property is + * then interpreted as an integer which is then converted to a Color object. + *

+ * If the specified property is not found or could not be parsed as an integer then null is returned. + * + * @param nm the name of the color property + * @return the Color converted from the system property. + * @see java.lang.System#getProperty(java.lang.String) + * @see java.lang.Integer#getInteger(java.lang.String) + * @see java.awt.Color#Color(int) + * @since JDK1.0 + */ + public static Color getColor(String nm) { + return getColor(nm, null); + } + + /** + * Finds a color in the system properties. + *

+ * The first argument is treated as the name of a system property to be obtained. The string value of this property + * is then interpreted as an integer which is then converted to a Color object. + *

+ * If the specified property is not found or cannot be parsed as an integer then the Color specified by + * the second argument is returned instead. + * + * @param nm the name of the color property + * @param v the default Color + * @return the Color converted from the system property, or the specified Color. + * @see java.lang.System#getProperty(java.lang.String) + * @see java.lang.Integer#getInteger(java.lang.String) + * @see java.awt.Color#Color(int) + * @since JDK1.0 + */ + public static Color getColor(String nm, Color v) { + Integer intval = Integer.getInteger(nm); + if(intval == null) { + return v; + } + int i = intval; + return new Color((i >> 16) & 0xFF, (i >> 8) & 0xFF, i & 0xFF); + } + + /** + * Finds a color in the system properties. + *

+ * The first argument is treated as the name of a system property to be obtained. The string value of this property + * is then interpreted as an integer which is then converted to a Color object. + *

+ * If the specified property is not found or could not be parsed as an integer then the integer value v + * is used instead, and is converted to a Color object. + * + * @param nm the name of the color property + * @param v the default color value, as an integer + * @return the Color converted from the system property or the Color converted from the + * specified integer. + * @see java.lang.System#getProperty(java.lang.String) + * @see java.lang.Integer#getInteger(java.lang.String) + * @see java.awt.Color#Color(int) + * @since JDK1.0 + */ + public static Color getColor(String nm, int v) { + Integer intval = Integer.getInteger(nm); + int i = (intval != null) ? intval : v; + return new Color((i >> 16) & 0xFF, (i >> 8) & 0xFF, (i) & 0xFF); + } + + /** + * Converts the components of a color, as specified by the HSB model, to an equivalent set of values for the default + * RGB model. + *

+ * The saturation and brightness components should be floating-point values between zero + * and one (numbers in the range 0.0-1.0). The hue component can be any floating-point number. The + * floor of this number is subtracted from it to create a fraction between 0 and 1. This fractional number is then + * multiplied by 360 to produce the hue angle in the HSB color model. + *

+ * The integer that is returned by HSBtoRGB encodes the value of a color in bits 0-23 of an integer + * value that is the same format used by the method {@link #getRGB() getRGB}. This integer can be supplied as an + * argument to the Color constructor that takes a single integer argument. + * + * @param hue the hue component of the color + * @param saturation the saturation of the color + * @param brightness the brightness of the color + * @return the RGB value of the color with the indicated hue, saturation, and brightness. + * @see java.awt.Color#getRGB() + * @see java.awt.Color#Color(int) + * @see java.awt.image.ColorModel#getRGBdefault() + * @since JDK1.0 + */ + public static int HSBtoRGB(float hue, float saturation, float brightness) { + int r = 0; + int g = 0; + int b = 0; + if(saturation == 0) { + r = g = b = (int) (brightness * 255.0f + 0.5f); + } else { + float h = (hue - (float) Math.floor(hue)) * 6.0f; + float f = h - (float) java.lang.Math.floor(h); + float p = brightness * (1.0f - saturation); + float q = brightness * (1.0f - saturation * f); + float t = brightness * (1.0f - (saturation * (1.0f - f))); + switch((int) h) { + case 0: + r = (int) (brightness * 255.0f + 0.5f); + g = (int) (t * 255.0f + 0.5f); + b = (int) (p * 255.0f + 0.5f); + break; + case 1: + r = (int) (q * 255.0f + 0.5f); + g = (int) (brightness * 255.0f + 0.5f); + b = (int) (p * 255.0f + 0.5f); + break; + case 2: + r = (int) (p * 255.0f + 0.5f); + g = (int) (brightness * 255.0f + 0.5f); + b = (int) (t * 255.0f + 0.5f); + break; + case 3: + r = (int) (p * 255.0f + 0.5f); + g = (int) (q * 255.0f + 0.5f); + b = (int) (brightness * 255.0f + 0.5f); + break; + case 4: + r = (int) (t * 255.0f + 0.5f); + g = (int) (p * 255.0f + 0.5f); + b = (int) (brightness * 255.0f + 0.5f); + break; + case 5: + r = (int) (brightness * 255.0f + 0.5f); + g = (int) (p * 255.0f + 0.5f); + b = (int) (q * 255.0f + 0.5f); + break; + } + } + return 0xff000000 | (r << 16) | (g << 8) | (b); + } + + /** + * Converts the components of a color, as specified by the default RGB model, to an equivalent set of values for + * hue, saturation, and brightness that are the three components of the HSB model. + *

+ * If the hsbvals argument is null, then a new array is allocated to return the result. + * Otherwise, the method returns the array hsbvals, with the values put into that array. + * + * @param r the red component of the color + * @param g the green component of the color + * @param b the blue component of the color + * @param hsbvals the array used to return the three HSB values, or null + * @return an array of three elements containing the hue, saturation, and brightness (in that order), of the color + * with the indicated red, green, and blue components. + * @see java.awt.Color#getRGB() + * @see java.awt.Color#Color(int) + * @see java.awt.image.ColorModel#getRGBdefault() + * @since JDK1.0 + */ + public static float[] RGBtoHSB(int r, int g, int b, float[] hsbvals) { + float hue; + float saturation; + float brightness; + if(hsbvals == null) { + hsbvals = new float[3]; + } + int cmax = (r > g) ? r : g; + if(b > cmax) { + cmax = b; + } + int cmin = (r < g) ? r : g; + if(b < cmin) { + cmin = b; + } + + brightness = cmax / 255.0f; + if(cmax != 0) { + saturation = (cmax - cmin) / ((float) cmax); + } else { + saturation = 0; + } + if(saturation == 0) { + hue = 0; + } else { + float redc = (cmax - r) / ((float) (cmax - cmin)); + float greenc = (cmax - g) / ((float) (cmax - cmin)); + float bluec = (cmax - b) / ((float) (cmax - cmin)); + if(r == cmax) { + hue = bluec - greenc; + } else if(g == cmax) { + hue = 2.0f + redc - bluec; + } else { + hue = 4.0f + greenc - redc; + } + hue = hue / 6.0f; + if(hue < 0) { + hue = hue + 1.0f; + } + } + hsbvals[0] = hue; + hsbvals[1] = saturation; + hsbvals[2] = brightness; + return hsbvals; + } + + /** + * Creates a Color object based on the specified values for the HSB color model. + *

+ * The s and b components should be floating-point values between zero and one (numbers in + * the range 0.0-1.0). The h component can be any floating-point number. The floor of this number is + * subtracted from it to create a fraction between 0 and 1. This fractional number is then multiplied by 360 to + * produce the hue angle in the HSB color model. + * + * @param h the hue component + * @param s the saturation of the color + * @param b the brightness of the color + * @return a Color object with the specified hue, saturation, and brightness. + * @since JDK1.0 + */ + public static Color getHSBColor(float h, float s, float b) { + return new Color(HSBtoRGB(h, s, b)); + } + + /** + * Returns a float array containing the color and alpha components of the Color, as + * represented in the default sRGB color space. If compArray is null, an array of length 4 + * is created for the return value. Otherwise, compArray must have length 4 or greater, and it is + * filled in with the components and returned. + * + * @param compArray an array that this method fills with color and alpha components and returns + * @return the RGBA components in a float array. + */ + public float[] getRGBComponents(float[] compArray) { + float[] f; + if(compArray == null) { + f = new float[4]; + } else { + f = compArray; + } + if(frgbvalue == null) { + f[0] = getRed() / 255f; + f[1] = getGreen() / 255f; + f[2] = getBlue() / 255f; + f[3] = getAlpha() / 255f; + } else { + f[0] = frgbvalue[0]; + f[1] = frgbvalue[1]; + f[2] = frgbvalue[2]; + f[3] = falpha; + } + return f; + } + + /** + * Returns a float array containing only the color components of the Color, in the default + * sRGB color space. If compArray is null, an array of length 3 is created for the return + * value. Otherwise, compArray must have length 3 or greater, and it is filled in with the components + * and returned. + * + * @param compArray an array that this method fills with color components and returns + * @return the RGB components in a float array. + */ + public float[] getRGBColorComponents(float[] compArray) { + float[] f; + if(compArray == null) { + f = new float[3]; + } else { + f = compArray; + } + if(frgbvalue == null) { + f[0] = getRed() / 255f; + f[1] = getGreen() / 255f; + f[2] = getBlue() / 255f; + } else { + f[0] = frgbvalue[0]; + f[1] = frgbvalue[1]; + f[2] = frgbvalue[2]; + } + return f; + } + + /** + * Returns a float array containing the color and alpha components of the Color, in the + * ColorSpace of the Color. If compArray is null, an array with + * length equal to the number of components in the associated ColorSpace plus one is created for the + * return value. Otherwise, compArray must have at least this length and it is filled in with the + * components and returned. + * + * @param compArray an array that this method fills with the color and alpha components of this Color + * in its ColorSpace and returns + * @return the color and alpha components in a float array. + */ + public float[] getComponents(float[] compArray) { + if(fvalue == null) { + return getRGBComponents(compArray); + } + float[] f; + int n = fvalue.length; + if(compArray == null) { + f = new float[n + 1]; + } else { + f = compArray; + } + System.arraycopy(fvalue, 0, f, 0, n); + f[n] = falpha; + return f; + } + + /** + * Returns a float array containing only the color components of the Color, in the + * ColorSpace of the Color. If compArray is null, an array with + * length equal to the number of components in the associated ColorSpace is created for the return + * value. Otherwise, compArray must have at least this length and it is filled in with the components + * and returned. + * + * @param compArray an array that this method fills with the color components of this Color in its + * ColorSpace and returns + * @return the color components in a float array. + */ + public float[] getColorComponents(float[] compArray) { + if(fvalue == null) { + return getRGBColorComponents(compArray); + } + float[] f; + int n = fvalue.length; + if(compArray == null) { + f = new float[n]; + } else { + f = compArray; + } + System.arraycopy(fvalue, 0, f, 0, n); + return f; + } + + /** + * Returns an equivalent {@code java.awt.Color} object based on this object. + * + * @return + */ + public java.awt.Color toJavaAWTColor() { + return new java.awt.Color(getRed(), getGreen(), getBlue(), getAlpha()); + } + +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/CommandExecutor.java b/src/main/java/com/laytonsmith/PureUtilities/CommandExecutor.java index 00a0baba02..fa3061bfdb 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/CommandExecutor.java +++ b/src/main/java/com/laytonsmith/PureUtilities/CommandExecutor.java @@ -1,77 +1,135 @@ package com.laytonsmith.PureUtilities; +import com.laytonsmith.PureUtilities.Common.StreamUtils; import com.laytonsmith.PureUtilities.Common.StringUtils; +import org.apache.commons.io.input.CloseShieldInputStream; +import org.apache.commons.io.output.CloseShieldOutputStream; + import java.io.BufferedInputStream; import java.io.BufferedOutputStream; +import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; /** - * Contains utilities to execute an external process and retrieve various - * results from it. + * Contains utilities to execute an external process and retrieve various results from it. * */ public class CommandExecutor { - + + /** + * If you're in a hurry, and all you want is to run a command and redirect inputs and outputs to the console, + * this will do it for you. The exit code is returned. + */ + public static void ExecuteWithRedirect(String command) throws IOException, InterruptedException { + ExecuteWithRedirect(StringToArray(command)); + } + + /** + * If you're in a hurry, and all you want is to run a command and redirect inputs and outputs to the console, + * this will do it for you. The exit code is returned. + * @param command + */ + public static int ExecuteWithRedirect(String... command) throws InterruptedException, IOException { + CommandExecutor exec = new CommandExecutor(command); + exec.setSystemInputsAndOutputs(); + exec.start(); + return exec.waitFor(); + } + /** - * If you're in a hurry, and all you want is to get the output of System.out - * from a process started with a string, this will do it for you. + * If you're in a hurry, and all you want is to get the output of System.out from a process started with a string, + * this will do it for you. + * * @param command * @return + * @throws java.lang.InterruptedException + * @throws java.io.IOException */ - public static String Execute(String command) throws InterruptedException, IOException{ + public static String Execute(String command) throws InterruptedException, IOException { + return Execute(new File("."), command); + } + /** + * If you're in a hurry, and all you want is to get the output of System.out from a process started with a string, + * this will do it for you. + * + * @param workingDir + * @param command + * @return + * @throws java.lang.InterruptedException + * @throws java.io.IOException + */ + public static String Execute(File workingDir, String command) throws InterruptedException, IOException { return Execute(StringToArray(command)); } /** - * If you're in a hurry, and all you want is to get the output of System.out - * from a process started with a list of arguments, this will do it for you. + * If you're in a hurry, and all you want is to get the output of System.out from a process started with a list of + * arguments, this will do it for you. + * * @param args * @return + * @throws java.lang.InterruptedException + * @throws java.io.IOException */ - public static String Execute(String [] args) throws InterruptedException, IOException{ - final List output = new ArrayList(); + public static String Execute(String... args) throws InterruptedException, IOException { + return Execute(new File("."), args); + } + + /** + * If you're in a hurry, and all you want is to get the output of System.out from a process started with a list of + * arguments, this will do it for you. + * + * @param workingDir + * @param args + * @return + * @throws java.lang.InterruptedException + * @throws java.io.IOException + */ + public static String Execute(File workingDir, String... args) throws InterruptedException, IOException { + final List output = new ArrayList<>(); CommandExecutor c = new CommandExecutor(args); + c.setWorkingDir(workingDir); OutputStream os = new BufferedOutputStream(new OutputStream() { @Override public void write(int next) throws IOException { - output.add((byte)next); + output.add((byte) next); } }); c.setSystemOut(os); c.start(); c.waitFor(); - Byte[] Bytes = new Byte[output.size()]; byte[] bytes = new byte[output.size()]; - Bytes = output.toArray(Bytes); - for(int i = 0; i < Bytes.length; i++){ - bytes[i] = Bytes[i]; + for(int i = 0; i < output.size(); i++) { + bytes[i] = output.get(i); } return new String(bytes, "UTF-8"); } - private static String [] StringToArray(String s){ + private static String[] StringToArray(String s) { List argList = StringUtils.ArgParser(s); - String [] args = new String[argList.size()]; + String[] args = new String[argList.size()]; args = argList.toArray(args); return args; } - - public CommandExecutor(String command){ + public CommandExecutor(String command) { this(StringToArray(command)); } - private String [] args; + private String[] args; private Process process; private InputStream in; private OutputStream out; @@ -80,100 +138,117 @@ public CommandExecutor(String command){ private Thread outThread; private Thread errThread; private Thread inThread; - public CommandExecutor(String [] command){ + private boolean inheritStandards = false; + private Map env = new HashMap<>(); + + public CommandExecutor(String... command) { args = command; } /** - * Starts this CommandExecutor. Afterwards, you can call .waitFor to wait - * until the process has finished. + * Sets an environment variable which is used by the subprocess. + * @param name + * @param value + */ + public void setEnvironmentVariable(String name, String value) { + this.env.put(name, value); + } + + /** + * Sets several environment variables, which are used by the subprocess. + * @param env + */ + public void setEnvironmentVariables(Map env) { + this.env.putAll(env); + } + + /** + * Starts this CommandExecutor. Afterwards, you can call {@link #waitFor()} to wait until the process has finished. + * * @return * @throws IOException */ - public CommandExecutor start() throws IOException{ + public CommandExecutor start() throws IOException { ProcessBuilder builder = new ProcessBuilder(args); + if(inheritStandards) { + builder.redirectError(ProcessBuilder.Redirect.INHERIT); + builder.redirectOutput(ProcessBuilder.Redirect.INHERIT); + builder.redirectInput(ProcessBuilder.Redirect.INHERIT); + } + builder.environment().putAll(env); builder.directory(workingDir); process = builder.start(); - outThread = new Thread(new Runnable() { - - @Override - public void run() { - InputStream bout = new BufferedInputStream(process.getInputStream()); - int ret; - try { - while((ret = bout.read()) != -1){ - if(out != null){ - out.write(ret); - } + outThread = new Thread(() -> { + int ret; + try(InputStream bout = new BufferedInputStream(process.getInputStream());) { + while((ret = bout.read()) != -1) { + if(out != null) { + out.write(ret); } - if(out != null){ - out.flush(); - } - } catch (IOException ex) { - Logger.getLogger(CommandExecutor.class.getName()).log(Level.SEVERE, null, ex); - } finally { - if(out != null){ - try { - out.close(); - } catch (IOException ex) { - Logger.getLogger(CommandExecutor.class.getName()).log(Level.SEVERE, null, ex); - } + } + if(out != null) { + out.flush(); + } + } catch (IOException ex) { + Logger.getLogger(CommandExecutor.class.getName()).log(Level.SEVERE, null, ex); + } finally { + if(out != null) { + try { + out.close(); + } catch (IOException ex) { + Logger.getLogger(CommandExecutor.class.getName()).log(Level.SEVERE, null, ex); } } } }, Arrays.toString(args) + "-output"); outThread.start(); - errThread = new Thread(new Runnable() { - - @Override - public void run() { - InputStream berr = new BufferedInputStream(process.getErrorStream()); - int ret; - try { - while((ret = berr.read()) != -1){ - if(err != null){ - err.write(ret); - } - } - if(err != null){ - err.flush(); + errThread = new Thread(() -> { + int ret; + try(InputStream berr = new BufferedInputStream(process.getErrorStream());) { + while((ret = berr.read()) != -1) { + if(err != null) { + err.write(ret); } - } catch (IOException ex) { - Logger.getLogger(CommandExecutor.class.getName()).log(Level.SEVERE, null, ex); - } finally { - if(err != null){ - try { - err.close(); - } catch (IOException ex) { - Logger.getLogger(CommandExecutor.class.getName()).log(Level.SEVERE, null, ex); - } + } + if(err != null) { + err.flush(); + } + } catch (IOException ex) { + Logger.getLogger(CommandExecutor.class.getName()).log(Level.SEVERE, null, ex); + } finally { + if(err != null) { + try { + err.close(); + } catch (IOException ex) { + Logger.getLogger(CommandExecutor.class.getName()).log(Level.SEVERE, null, ex); } } } }, Arrays.toString(args) + "-error"); errThread.start(); - if(in != null){ - inThread = new Thread(new Runnable() { - - @Override - public void run() { - OutputStream bin = new BufferedOutputStream(process.getOutputStream()); - int ret; - try { - while((ret = in.read()) != -1){ - bin.write(ret); - } - } catch (IOException ex) { - Logger.getLogger(CommandExecutor.class.getName()).log(Level.SEVERE, null, ex); - } finally { - if(in != null){ - try { - in.close(); - } catch (IOException ex) { - Logger.getLogger(CommandExecutor.class.getName()).log(Level.SEVERE, null, ex); + if(in != null) { + inThread = new Thread(() -> { + OutputStream bin = new BufferedOutputStream(process.getOutputStream()); + try(BufferedReader br = new BufferedReader(new InputStreamReader(in));) { + // This is necessary, because InputStream.read() blocks forever, and + // is not interruptable, so to prevent memory leaks, we must unfortunately + // busy wait. + while(!inThread.isInterrupted()) { + if(br.ready()) { + int ret; + if((ret = br.read()) != -1) { + bin.write(ret); } } + try { + Thread.sleep(10); + } catch (InterruptedException ex) { + // Will pick up in the while loop. + inThread.interrupt(); + } } + } catch (IOException ex) { + Logger.getLogger(CommandExecutor.class.getName()).log(Level.SEVERE, null, ex); } }, Arrays.toString(args) + "-input"); inThread.start(); @@ -181,60 +256,91 @@ public void run() { return this; } - public CommandExecutor setSystemIn(InputStream input){ - if(process != null){ + /** + * Sets the InputStream for the program. If passing in {@code System.in}, it's important to note that you should + * wrap this in a {@link CloseShieldInputStream}, as the code in general will attempt to close all streams after the + * program is done. + * + * @param input + * @return + */ + public CommandExecutor setSystemIn(InputStream input) { + if(process != null) { throw new RuntimeException("Process is already started! Cannot set a new InputStream!"); } in = input; return this; } - public CommandExecutor setSystemOut(OutputStream output){ - if(process != null){ + /** + * Sets the standard OutputStream for the program. If passing in {@code System.out} or {@code System.err}, + * it's important to note that you + * should wrap this in a {@link CloseShieldOutputStream}, as the code in general will attempt to close all streams + * after the program is done. + * @param output The output stream + * @return {@code this} for chaining. + */ + public CommandExecutor setSystemOut(OutputStream output) { + if(process != null) { throw new RuntimeException("Process is already started! Cannot set a new InputStream!"); } out = output; return this; } - public CommandExecutor setSystemErr(OutputStream error){ - if(process != null){ + /** + * Sets the error OutputStream for the program. If passing in {@code System.err} or {@code System.out}, + * it's important to note that you + * should wrap this in a {@link CloseShieldOutputStream}, as the code in general will attempt to close all streams + * after the program is done. + * @param error The output stream + * @return {@code this} for chaining + */ + public CommandExecutor setSystemErr(OutputStream error) { + if(process != null) { throw new RuntimeException("Process is already started! Cannot set a new OutputStream!"); } err = error; return this; } - public InputStream getSystemIn(){ + public InputStream getSystemIn() { return in; } - public OutputStream getSystemOut(){ + public OutputStream getSystemOut() { return out; } - public OutputStream getSystemErr(){ + public OutputStream getSystemErr() { return err; } - public CommandExecutor setWorkingDir(File workingDir){ - if(process != null){ + public CommandExecutor setWorkingDir(File workingDir) { + if(process != null) { throw new RuntimeException("Process is already started! Cannot set a new working directory!"); } this.workingDir = workingDir; return this; } + /** + * Blocks until the underlying process has finished. If the process has already finished, the method will return + * immediately. + * + * @return The process's exit code + * @throws InterruptedException + */ public int waitFor() throws InterruptedException { int ret = process.waitFor(); - if(out != null){ + if(out != null) { try { out.flush(); } catch (IOException ex) { Logger.getLogger(CommandExecutor.class.getName()).log(Level.SEVERE, null, ex); } } - if(err != null){ + if(err != null) { try { err.flush(); } catch (IOException ex) { @@ -243,17 +349,25 @@ public int waitFor() throws InterruptedException { } outThread.join(); errThread.join(); + if(inThread != null) { + inThread.interrupt(); + } return ret; } /** - * Sets the inputs and outputs to be System.in, System.out, and System.err. + * Sets the inputs and outputs to be System.in, StreamUtils.GetSystemOut(), and StreamUtils.GetSystemErr(). + *

+ * System.in is wrapped in a {@link CloseShieldInputStream}, so it won't be accidentally closed, and likewise + * out and err are wrapped in {@link CloseShieldOutputStream}. + * * @return */ - public CommandExecutor setSystemInputsAndOutputs(){ - setSystemOut(System.out); - setSystemErr(System.err); - setSystemIn(System.in); + public CommandExecutor setSystemInputsAndOutputs() { + setSystemOut(new CloseShieldOutputStream(StreamUtils.GetSystemOut())); + setSystemErr(new CloseShieldOutputStream(StreamUtils.GetSystemErr())); + setSystemIn(new CloseShieldInputStream(System.in)); + inheritStandards = true; return this; } diff --git a/src/main/java/com/laytonsmith/PureUtilities/Common/Annotations/AnnotationChecks.java b/src/main/java/com/laytonsmith/PureUtilities/Common/Annotations/AnnotationChecks.java new file mode 100644 index 0000000000..12bc3052cf --- /dev/null +++ b/src/main/java/com/laytonsmith/PureUtilities/Common/Annotations/AnnotationChecks.java @@ -0,0 +1,254 @@ +package com.laytonsmith.PureUtilities.Common.Annotations; + +import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery; +import com.laytonsmith.PureUtilities.ClassLoading.ClassMirror.ClassMirror; +import com.laytonsmith.PureUtilities.ClassLoading.ClassMirror.MethodMirror; +import com.laytonsmith.PureUtilities.Common.ReflectionUtils; +import com.laytonsmith.PureUtilities.Common.StringUtils; +import com.laytonsmith.PureUtilities.ExhaustiveVisitor; +import com.laytonsmith.annotations.NonInheritImplements; +import com.laytonsmith.annotations.typeof; +import com.laytonsmith.core.constructs.CClassType; +import java.lang.reflect.Constructor; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * This class is run by maven at compile time, and checks to ensure that the various annotations referenced here are + * checked, and fail if any of the parameters are missing. + */ +public final class AnnotationChecks { + + private AnnotationChecks() {} + + public static void checkForTypeInTypeofClasses() throws Exception { + Set> classes = ClassDiscovery.getDefaultInstance().getClassesWithAnnotation(typeof.class); + Set errors = new HashSet<>(); + for(ClassMirror clazz : classes) { + try { + // Make sure that TYPE has the same type as the typeof annotation + CClassType type = (CClassType) ReflectionUtils.get(clazz.loadClass(), "TYPE"); + if(type == null) { + errors.add("TYPE is null? " + clazz.getClassName()); + continue; + } + if(!type.val().equals(clazz.getAnnotation(typeof.class).getValue("value"))) { + errors.add(clazz.getClassName() + "'s TYPE value is different than the typeof annotation on it"); + } + } catch (ReflectionUtils.ReflectionException ex) { + errors.add(clazz.getClassName() + " needs to add the following:\n\t@SuppressWarnings(\"FieldNameHidesFieldInSuperclass\")\n" + + "\tpublic static final CClassType TYPE = CClassType.get(" + clazz.getSimpleName() + ".class);"); + } + } + if(!errors.isEmpty()) { + throw new Exception("\n" + StringUtils.Join(errors, "\n")); + } + } + + @SuppressWarnings("UnnecessaryLabelOnBreakStatement") + public static void checkForceImplementation() throws Exception { + Set uhohs = new HashSet<>(); + Set> set = ClassDiscovery.getDefaultInstance().loadConstructorsWithAnnotation(ForceImplementation.class); + for(Constructor cons : set) { + Class superClass = cons.getDeclaringClass(); + Set s = ClassDiscovery.getDefaultInstance().loadClassesThatExtend(superClass); + checkImplements: + for(Class c : s) { + // c is the class we want to check to make sure it implements cons + for(Constructor cCons : c.getDeclaredConstructors()) { + if(Arrays.equals(cons.getParameterTypes(), cCons.getParameterTypes())) { + continue checkImplements; + } + } + if(c.isMemberClass() && (c.getModifiers() & Modifier.STATIC) == 0) { + // Ok, so, an inner, non static class actually passes the super class's reference to the constructor as + // the first parameter, at a byte code level. So this is a different type of error, or at least, a different + // error message will be helpful. + uhohs.add(c.getName() + " must be static."); + } else { + uhohs.add(c.getName() + " must implement the constructor with signature (" + getSignature(cons) + "), but doesn't."); + } + } + } + + Set set2 = ClassDiscovery.getDefaultInstance().loadMethodsWithAnnotation(ForceImplementation.class); + for(Method cons : set2) { + Class superClass = cons.getDeclaringClass(); + @SuppressWarnings("unchecked") + Set> s = ClassDiscovery.getDefaultInstance().loadClassesThatExtend(superClass); + checkImplements: + for(Class c : s) { + if((c.getModifiers() & Modifier.ABSTRACT) != 0) { + // Abstract classes are not required to implement any ForceImplementation methods + continue; + } + if(c.isInterface()) { + // Interfaces are exempt from the requirement + continue; + } + // First, check if maybe it has a InterfaceRunner for it + findRunner: + for(Class ir : ClassDiscovery.getDefaultInstance().loadClassesWithAnnotation(InterfaceRunnerFor.class)) { + InterfaceRunnerFor ira = ir.getAnnotation(InterfaceRunnerFor.class); + if(ira.value() == c) { + // Aha! It does. Set c to ir, then break this for loop. + // The runner for this class will act in the stead of this + // class. + c = ir; + break findRunner; + } + } + // c is the class we want to check to make sure it implements cons + for(Method cCons : c.getDeclaredMethods()) { + if(cCons.getName().equals(cons.getName()) && Arrays.equals(cons.getParameterTypes(), cCons.getParameterTypes())) { + continue checkImplements; + } + } + uhohs.add(c.getName() + " must implement the method with signature " + cons.getName() + "(" + getSignature(cons) + "), but doesn't."); + } + } + + if(!uhohs.isEmpty()) { + List uhohsList = new ArrayList<>(uhohs); + Collections.sort(uhohsList); + throw new Exception("There " + StringUtils.PluralHelper(uhohs.size(), "error") + ". The following classes need to implement various methods:\n" + StringUtils.Join(uhohs, "\n")); + } + } + + private static String getSignature(Member executable) { + List l = new ArrayList<>(); +// for(Class cc : executable.getParameterTypes()){ +// l.add(cc.getName()); +// } + if(executable instanceof Method method) { + for(Class cc : method.getParameterTypes()) { + l.add(cc.getName()); + } + } else if(executable instanceof Constructor constructor) { + for(Class cc : constructor.getParameterTypes()) { + l.add(cc.getName()); + } + } else { + throw new Error("Unexpected executable type"); + } + return StringUtils.Join(l, ", "); + } + + public static void verifyExhaustiveVisitors() throws ClassNotFoundException { + Set> toVerify; + toVerify = ClassDiscovery.getDefaultInstance() + .getClassesThatExtend(ExhaustiveVisitor.class); + for(ClassMirror c : toVerify) { + ExhaustiveVisitor.verify(c); + } + } + + public static void verifyNonInheritImplements() throws ClassNotFoundException { + Set> toVerify; + toVerify = ClassDiscovery.getDefaultInstance() + .getClassesWithAnnotation(NonInheritImplements.class); + Set uhohs = new HashSet<>(); + for(ClassMirror c : toVerify) { + Class iface = Class.forName(c.getAnnotation(NonInheritImplements.class).getValue("value").toString()); + String[] sTypes = (String[]) c.getAnnotation(NonInheritImplements.class).getValue("parameterTypes"); + Class[] typeParameters = new Class[(sTypes == null ? 0 : sTypes.length)]; + for(int i = 0; i < typeParameters.length; i++) { + typeParameters[i] = Class.forName(sTypes[i]); + } + if(typeParameters.length != iface.getTypeParameters().length) { + uhohs.add(iface + " declares " + iface.getTypeParameters().length + " generic parameter(s), but " + + c.getClassName() + " does not provide enough parameters."); + } + for(int i = 0; i < typeParameters.length; i++) { + Class actualType = typeParameters[i]; + TypeVariable definedType = iface.getTypeParameters()[i]; + // TODO: Understand why this is an array. What causes this to happen? Using the + // operator? That's exceedingly rare, so this will probably not break for quite a while, but if I'm + // correct, then I really need a real use case to figure this out. For now, grabbing the first value + // is probably good enough. + Class t; + try { + t = Class.forName(definedType.getBounds()[0].getTypeName()); + } catch (ClassNotFoundException e) { + throw new Error(e); + } + // actualType needs to extend t + if(!t.isAssignableFrom(actualType)) { + uhohs.add("The type definition for the [" + i + "] index parameter for @NonInheritImplements" + + " parameterTypes value defined in " + c.getClassName() + " does not match the upper" + + " boundary, which is " + t + ". It must be a subclass of that."); + } + } + if(!iface.isInterface()) { + uhohs.add("The class given to @NonInheritImplements, tagged on " + c.getClassName() + " is not an" + + " interface, and must be."); + continue; + } + // It's an interface, so go through all the methods it has, and make sure that the class c contains all the + // methods. + for(Method im : iface.getDeclaredMethods()) { + // We have to go through the generic types, and replace them with the specified type in the annotation. + // Look through all the parameters in im.getGenericParameterTypes(), and if any of them are defined in + // iface.getTypeParameters(), use that instead of the value in im.getParameterTypes(). + Class[] parameters = new Class[im.getParameterTypes().length]; + outer: for(int i = 0; i < parameters.length; i++) { + Type ic = im.getGenericParameterTypes()[i]; + for(int j = 0; j < iface.getTypeParameters().length; j++) { + TypeVariable jt = iface.getTypeParameters()[j]; + if(jt.getName().equals(ic.getTypeName())) { + parameters[i] = typeParameters[j]; + continue outer; + } + } + parameters[i] = im.getParameterTypes()[i]; + } +// System.out.println("im: " + im + "; parameters: " + Arrays.toString(parameters) +// + " im.getGenericParameterTypes: " + Arrays.toString(im.getGenericParameterTypes())); + Class expectedReturnType = im.getReturnType(); + for(int j = 0; j < iface.getTypeParameters().length; j++) { + TypeVariable jt = iface.getTypeParameters()[j]; + if(jt.getName().equals(im.getGenericReturnType().getTypeName())) { + // Same thing as above, if this is a generic type, we need to replace the + // expected return type with the defined generic type + expectedReturnType = typeParameters[j]; + break; + } + } +// System.out.println("expectedReturnType: " + expectedReturnType); + try { + MethodMirror m = c.getMethod(im.getName(), parameters); + Class returnType = m.getType().loadClass(); +// System.out.println("returnType: " + returnType); + if(returnType != expectedReturnType) { + uhohs.add("Expected return type for " + m.loadMethod() + " is " + expectedReturnType + + " but in reality is " + returnType); + } + } catch (NoSuchMethodException ex) { + String msg = "The class " + c.getClassName() + " implements " + iface.getSimpleName() + " but does not" + + " implement the method public " + expectedReturnType.getSimpleName() + " " + im.getName() + "("; + List params = new ArrayList<>(); + for(int i = 0; i < im.getParameterCount(); i++) { + params.add(parameters[i].getSimpleName() + " " + im.getParameters()[i].getName()); + } + msg += StringUtils.Join(params, ", ", ", ", ", ", ""); + msg += ") {}"; + uhohs.add(msg); + } + } + } + if(!uhohs.isEmpty()) { + String error = StringUtils.Join(uhohs, "\n"); + throw new Error(error); + } + } + +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/Common/Annotations/CheckOverrides.java b/src/main/java/com/laytonsmith/PureUtilities/Common/Annotations/CheckOverrides.java index 1e49545370..b2219c36b4 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Common/Annotations/CheckOverrides.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Common/Annotations/CheckOverrides.java @@ -2,9 +2,15 @@ import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery; import com.laytonsmith.PureUtilities.ClassLoading.ClassMirror.ClassMirror; +import com.laytonsmith.PureUtilities.Common.ArrayUtils; import com.laytonsmith.PureUtilities.Common.ClassUtils; +import com.laytonsmith.PureUtilities.Common.StreamUtils; import com.laytonsmith.PureUtilities.Common.StringUtils; import com.laytonsmith.annotations.MustUseOverride; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; @@ -17,8 +23,6 @@ import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; -import java.util.logging.Level; -import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.processing.AbstractProcessor; @@ -34,73 +38,78 @@ * */ @SupportedAnnotationTypes({"java.lang.Override", "com.laytonsmith.annotations.MustUseOverride"}) -@SupportedSourceVersion(SourceVersion.RELEASE_7) +@SupportedSourceVersion(SourceVersion.RELEASE_16) public class CheckOverrides extends AbstractProcessor { - - private static final boolean enabled = true; + + @Target({ElementType.METHOD, ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + public static @interface SuppressCheckOverrides {} + + private static final boolean ENABLED = true; private static Map> methods = null; - private static final Set interfacesWithMustUseOverride = new HashSet<>(); + private static final Set INTERFACES_WITH_MUST_USE_OVERRIDE = new HashSet<>(); private static final Pattern METHOD_SIGNATURE = Pattern.compile("[a-zA-Z0-9_]+\\((.*)\\)"); private static final Pattern CLASS_TEMPLATES = Pattern.compile("^.*?<(.*)>?$"); @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { - if(!enabled){ + if(!ENABLED) { processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, "CheckOverrides processor is turned off!"); return false; } setup(); - if (!roundEnv.processingOver()) { - for (Element element : roundEnv.getElementsAnnotatedWith(MustUseOverride.class)) { + if(!roundEnv.processingOver()) { + for(Element element : roundEnv.getElementsAnnotatedWith(MustUseOverride.class)) { String className = element.toString(); Class c = null; try { c = getClassFromName(className); } catch (ClassNotFoundException ex) { - Logger.getLogger(CheckOverrides.class.getName()).log(Level.SEVERE, null, ex); + ex.printStackTrace(System.err); } - if (c != null) { - if (!c.isInterface()) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, - "Only interfaces may be annotated with " + MustUseOverride.class.getName()); + if(c != null) { + if(!c.isInterface()) { + String msg = "Only interfaces may be annotated with " + MustUseOverride.class.getName(); + System.err.println(msg); + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, msg); } - interfacesWithMustUseOverride.add(c); + INTERFACES_WITH_MUST_USE_OVERRIDE.add(c); } } - for (Element element : roundEnv.getElementsAnnotatedWith(Override.class)) { + for(Element element : roundEnv.getElementsAnnotatedWith(Override.class)) { String className = element.getEnclosingElement().toString(); Class c = null; try { c = getClassFromName(className); } catch (ClassNotFoundException ex) { - Logger.getLogger(CheckOverrides.class.getName()).log(Level.SEVERE, null, ex); + ex.printStackTrace(System.err); } - if (c != null && !c.isInterface()) { - //System.out.println("Dealing with " + c.getName() + ".." + element.toString()); + if(c != null && !c.isInterface()) { + //StreamUtils.GetSystemOut().println("Dealing with " + c.getName() + ".." + element.toString()); //We have to do a bit of massaging to turn "method(java.lang.String[], java.lang.String) //into a Method object. Matcher m = METHOD_SIGNATURE.matcher(element.toString()); String methodName = element.getSimpleName().toString(); Class[] argTypes; boolean isTemplate = false; - if (!m.find()) { + if(!m.find()) { argTypes = new Class[0]; } else { String inner = m.group(1); String[] args; - if ("".equals(inner.trim())) { - args = new String[0]; + if("".equals(inner.trim())) { + args = ArrayUtils.EMPTY_STRING_ARRAY; } else { //Take out generics, since we can't really deal with them, and they make parsing //the args harder. inner = removeGenerics(inner); args = StringUtils.trimSplit(inner, ","); } - //System.out.println("Args length: " + args.length); + //StreamUtils.GetSystemOut().println("Args length: " + args.length); argTypes = new Class[args.length]; - for (int i = 0; i < args.length; i++) { + for(int i = 0; i < args.length; i++) { try { argTypes[i] = getClassFromName(args[i]); } catch (ClassNotFoundException e) { @@ -109,11 +118,11 @@ public boolean process(Set annotations, RoundEnvironment String codeClassName = element.getEnclosingElement().asType().toString(); Matcher mm = CLASS_TEMPLATES.matcher(codeClassName); boolean found = false; - if (mm.find()) { + if(mm.find()) { String[] templates = removeGenerics(mm.group(1)).split(","); String baseClass = args[i].replaceAll("\\[\\]", ""); - for (String template : templates) { - if (baseClass.equals(template)) { + for(String template : templates) { + if(baseClass.equals(template)) { //Ok, it's found. isTemplate = true; found = true; @@ -122,19 +131,19 @@ public boolean process(Set annotations, RoundEnvironment } } } - if (!isTemplate || !found) { + if(!isTemplate || !found) { //Oh, there aren't any. Well, I don't know why this would happen. - Logger.getLogger(CheckOverrides.class.getName()).log(Level.SEVERE, null, e); + e.printStackTrace(System.err); } - try{ + try { argTypes[i] = Class.forName(args[i]); - } catch(ClassNotFoundException ex){ + } catch (ClassNotFoundException ex) { //Won't happen } } } } - if (isTemplate) { + if(isTemplate) { //Template parameters that extend something break this, because the annotation //processor doesn't provide the information to us. So, for instance, if you have //a template parameter MyClass and a method in that class @@ -146,9 +155,9 @@ public boolean process(Set annotations, RoundEnvironment //and remove all the methods with this name and type. We can, however, //avoid removing methods with different number of arguments, since we //can guarantee those aren't overridden. - for(Method method : c.getDeclaredMethods()){ + for(Method method : c.getDeclaredMethods()) { if(method.getName().equals(methodName) - && method.getParameterTypes().length == argTypes.length){ + && method.getParameterTypes().length == argTypes.length) { methods.get(c).remove(method); } } @@ -159,29 +168,29 @@ public boolean process(Set annotations, RoundEnvironment //Ok, remove it from the list of methods, cause we know it's overridden. //.remove won't work, because we need to also remove co-return types present Iterator it = methods.get(c).iterator(); - while (it.hasNext()) { + while(it.hasNext()) { Method next = it.next(); - if (next.getName().equals(method.getName()) + if(next.getName().equals(method.getName()) && checkSignatureForCompatibility(next.getParameterTypes(), method.getParameterTypes())) { it.remove(); } } } catch (NoSuchMethodException | SecurityException ex) { - Logger.getLogger(CheckOverrides.class.getName()).log(Level.SEVERE, null, ex); + ex.printStackTrace(System.err); } } - if (methods.get(c).isEmpty()) { + if(methods.get(c).isEmpty()) { methods.remove(c); } } } //Now all the overridden methods have been removed from the list of methods in all the //classes. We now need to go through and find out which of the remaining methods *could* - //be overriden, as many may not be overrides anyways. + //be overridden, as many may not be overrides anyways. Set methodsInError = new HashSet<>(); - for (Class c : methods.keySet()) { + for(Class c : methods.keySet()) { Set mm = methods.get(c); - for (Method m : mm) { + for(Method m : mm) { //Get the superclass/superinterfaces that this class extends/implements //all the way up to Object Set supers = new HashSet<>(); @@ -189,20 +198,30 @@ && checkSignatureForCompatibility(next.getParameterTypes(), method.getParameterT //Ok, now look through all the superclasses' methods, and find any that //match the signature. If they do, it's an error. List compare = new ArrayList<>(); - for (Class s : supers) { + for(Class s : supers) { compare.addAll(getOverridableMethods(s)); } - for (Method superM : compare) { - if (m.getName().equals(superM.getName())) { - if (checkSignatureForCompatibility(superM.getParameterTypes(), m.getParameterTypes())) { + methodLoop: for(Method superM : compare) { + if(m.getName().equals(superM.getName())) { + if(checkSignatureForCompatibility(superM.getParameterTypes(), m.getParameterTypes())) { //Oops, found a bad method. + if(m.isAnnotationPresent(SuppressCheckOverrides.class)) { + continue; + } + Class container = m.getDeclaringClass(); + do { + if(container.isAnnotationPresent(SuppressCheckOverrides.class)) { + continue methodLoop; + } + container = container.getEnclosingClass(); + } while(container != null); methodsInError.add(m); } } //else different method altogether } } } - if (!methodsInError.isEmpty()) { + if(!methodsInError.isEmpty()) { //Some package names are pretty verbose, and will more than //likely be included with an import, so let's trim the //error message down some so it's easier to read @@ -213,14 +232,14 @@ && checkSignatureForCompatibility(next.getParameterTypes(), method.getParameterT }); //Build a sorted set, so these go in order. SortedSet stringMethodsInError = new TreeSet<>(); - for (Method m : methodsInError) { + for(Method m : methodsInError) { stringMethodsInError.add(m.getDeclaringClass().getName() + "." + m.getName() + "(" + StringUtils.Join(Arrays.asList(m.getParameterTypes()), ", ", ", ", ", ", "", new StringUtils.Renderer>() { @Override public String toString(Class item) { String name = ClassUtils.getCommonName(item); - for (String v : verbosePackages) { - if (name.matches(Pattern.quote(v) + "\\.([^\\.]*?)$")) { + for(String v : verbosePackages) { + if(name.matches(Pattern.quote(v) + "\\.([^\\.]*?)$")) { return name.replaceFirst(Pattern.quote(v) + "\\.", ""); } } @@ -231,26 +250,27 @@ public String toString(Class item) { final StringBuilder b = new StringBuilder(); b.append("There ") .append(StringUtils.PluralTemplateHelper(stringMethodsInError.size(), - "is a method which overrides or implements a method in a super class/super interface," - + " but doesn't use the @Override tag. Please tag this method", - "are %d methods which override or implement a method in a super class/super interface" - + " but don't use the @Override tag. Please tag these methods")) + "is a method which overrides or implements a method in a super class/super interface," + + " but doesn't use the @Override tag. Please tag this method", + "are %d methods which override or implement a method in a super class/super interface" + + " but don't use the @Override tag. Please tag these methods")) .append(" with @Override to continue the build process.") - .append(StringUtils.nl) - .append(StringUtils.Join(stringMethodsInError, StringUtils.nl)); + .append(StringUtils.NL) + .append(StringUtils.Join(stringMethodsInError, StringUtils.NL)); + System.err.println(b.toString()); processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, b.toString()); } else { - System.out.println("No @Override annotations were found to be missing."); + StreamUtils.GetSystemOut().println("No @Override annotations were found to be missing."); } } return false; } private static void getAllSupers(Class c, Set building, boolean first) { - if (c == null || c == Object.class) { + if(c == null || c == Object.class) { return; } - if (!first) { + if(!first) { building.add(c); } else { //everything extends Object, and we're gonna use @@ -259,27 +279,26 @@ private static void getAllSupers(Class c, Set building, boolean first) { building.add(Object.class); } getAllSupers(c.getSuperclass(), building, false); - for (Class cc : c.getInterfaces()) { - if (interfacesWithMustUseOverride.contains(cc)) { + for(Class cc : c.getInterfaces()) { + if(INTERFACES_WITH_MUST_USE_OVERRIDE.contains(cc)) { building.add(cc); } } } /** - * Checks to see if an argument signature is compatible. That is, if the - * parameter types match. + * Checks to see if an argument signature is compatible. That is, if the parameter types match. * * @param superArgs The super class arguments to check * @param subArgs The sub class arguments to check * @return */ private static boolean checkSignatureForCompatibility(Class[] superArgs, Class[] subArgs) { - if (superArgs.length != subArgs.length) { + if(superArgs.length != subArgs.length) { return false; } - for (int i = 0; i < superArgs.length; i++) { - if (superArgs[i] != subArgs[i]) { + for(int i = 0; i < superArgs.length; i++) { + if(superArgs[i] != subArgs[i]) { return false; } } @@ -295,17 +314,17 @@ private static boolean checkSignatureForCompatibility(Class[] superArgs, Class[] private static String removeGenerics(String identifier) { StringBuilder b = new StringBuilder(); int genericCount = 0; - for (int i = 0; i < identifier.length(); i++) { + for(int i = 0; i < identifier.length(); i++) { char ch = identifier.charAt(i); - if (ch == '<') { + if(ch == '<') { genericCount++; continue; } - if (ch == '>') { + if(ch == '>') { genericCount--; continue; } - if (genericCount == 0) { + if(genericCount == 0) { b.append(ch); } } @@ -317,17 +336,17 @@ private static Class getClassFromName(String className) throws ClassNotFoundExce } private static void setup() { - if (methods == null) { + if(methods == null) { methods = new HashMap<>(); - + List> classes = ClassDiscovery.getDefaultInstance().getKnownClasses(ClassDiscovery.GetClassContainer(CheckOverrides.class)); - for (ClassMirror cm : classes) { + for(ClassMirror cm : classes) { Class c = cm.loadClass(CheckOverrides.class.getClassLoader(), false); - if (c.isInterface()) { + if(c.isInterface() || c.isRecord()) { continue; } Set mm = getPotentiallyOverridingMethods(c); - if (!mm.isEmpty()) { + if(!mm.isEmpty()) { methods.put(c, mm); } } @@ -335,17 +354,16 @@ private static void setup() { } /** - * Returns a list of potentially overriding methods in a class. That is, the - * non-private, non-static methods. + * Returns a list of potentially overriding methods in a class. That is, the non-private, non-static methods. * * @param c * @return */ private static Set getPotentiallyOverridingMethods(Class c) { Set methodList = new HashSet<>(); - for (Method m : c.getDeclaredMethods()) { + for(Method m : c.getDeclaredMethods()) { //Ignore static or public methods, since those can't override anything - if ((m.getModifiers() & Modifier.PRIVATE) == 0 && (m.getModifiers() & Modifier.STATIC) == 0 + if((m.getModifiers() & Modifier.PRIVATE) == 0 && (m.getModifiers() & Modifier.STATIC) == 0 && !m.isSynthetic()) { methodList.add(m); } @@ -354,16 +372,15 @@ private static Set getPotentiallyOverridingMethods(Class c) { } /** - * Returns a list of overridable methods in a class. This includes - * non-static, non-private, non-final methods. + * Returns a list of overridable methods in a class. This includes non-static, non-private, non-final methods. * * @param c * @return */ private static List getOverridableMethods(Class c) { List methodList = new ArrayList<>(); - for (Method m : c.getDeclaredMethods()) { - if ((m.getModifiers() & Modifier.PRIVATE) == 0 + for(Method m : c.getDeclaredMethods()) { + if((m.getModifiers() & Modifier.PRIVATE) == 0 && (m.getModifiers() & Modifier.STATIC) == 0 && (m.getModifiers() & Modifier.FINAL) == 0 && !m.isSynthetic()) { diff --git a/src/main/java/com/laytonsmith/PureUtilities/Common/Annotations/ForceImplementation.java b/src/main/java/com/laytonsmith/PureUtilities/Common/Annotations/ForceImplementation.java new file mode 100644 index 0000000000..35f73c6392 --- /dev/null +++ b/src/main/java/com/laytonsmith/PureUtilities/Common/Annotations/ForceImplementation.java @@ -0,0 +1,16 @@ +package com.laytonsmith.PureUtilities.Common.Annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation, when tagging a constructor/method, indicates that ALL subclasses must include a constructor/method + * with the same parameter signature, excluding abstract subclasses. This check is enforced at compile time. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.CONSTRUCTOR, ElementType.METHOD}) +public @interface ForceImplementation { + +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/Common/Annotations/InterfaceRunnerFor.java b/src/main/java/com/laytonsmith/PureUtilities/Common/Annotations/InterfaceRunnerFor.java new file mode 100644 index 0000000000..5988474630 --- /dev/null +++ b/src/main/java/com/laytonsmith/PureUtilities/Common/Annotations/InterfaceRunnerFor.java @@ -0,0 +1,21 @@ +package com.laytonsmith.PureUtilities.Common.Annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation ties the class to the "super interface" that this class will implement certain methods for. Ideally, + * we would just have the interfaces implement the methods required by @typeof, but that won't work in Java < 8, + * which we need to support. Instead, we have a real class that extends {@link MScriptInterfaceRunner} and uses this + * annotation to link it to the parent interface. Methods that are forced in all subclasses using + * {@link ForceImplementation} can use this to provide the implementations for interfaces. This also works for abstract + * classes. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface InterfaceRunnerFor { + + public Class value(); +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/Common/ArrayUtils.java b/src/main/java/com/laytonsmith/PureUtilities/Common/ArrayUtils.java index 6f5d905555..e199e61ba9 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Common/ArrayUtils.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Common/ArrayUtils.java @@ -1,730 +1,928 @@ package com.laytonsmith.PureUtilities.Common; import java.lang.reflect.Array; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.util.Arrays; import java.util.List; /** - * - * + * Provides various utility methods for working with arrays. */ +@SuppressWarnings({"UnnecessaryUnboxing", "unchecked"}) public class ArrayUtils { - + /** - * Instantiating a new 0 length array is *usually* inefficient, unless you - * are doing reference comparisons later. If you are generating it simply to use - * as a "default" value for an array, consider using this instead to increase performance. + * Instantiating a new 0 length array is *usually* inefficient, unless you are doing reference comparisons later. If + * you are generating it simply to use as a "default" value for an array, consider using this instead to increase + * performance. */ public static final char[] EMPTY_CHAR_ARRAY = new char[0]; /** - * Instantiating a new 0 length array is *usually* inefficient, unless you - * are doing reference comparisons later. If you are generating it simply to use - * as a "default" value for an array, consider using this instead to increase performance. + * Instantiating a new 0 length array is *usually* inefficient, unless you are doing reference comparisons later. If + * you are generating it simply to use as a "default" value for an array, consider using this instead to increase + * performance. */ public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; /** - * Instantiating a new 0 length array is *usually* inefficient, unless you - * are doing reference comparisons later. If you are generating it simply to use - * as a "default" value for an array, consider using this instead to increase performance. + * Instantiating a new 0 length array is *usually* inefficient, unless you are doing reference comparisons later. If + * you are generating it simply to use as a "default" value for an array, consider using this instead to increase + * performance. */ public static final short[] EMPTY_SHORT_ARRAY = new short[0]; /** - * Instantiating a new 0 length array is *usually* inefficient, unless you - * are doing reference comparisons later. If you are generating it simply to use - * as a "default" value for an array, consider using this instead to increase performance. + * Instantiating a new 0 length array is *usually* inefficient, unless you are doing reference comparisons later. If + * you are generating it simply to use as a "default" value for an array, consider using this instead to increase + * performance. */ public static final int[] EMPTY_INT_ARRAY = new int[0]; /** - * Instantiating a new 0 length array is *usually* inefficient, unless you - * are doing reference comparisons later. If you are generating it simply to use - * as a "default" value for an array, consider using this instead to increase performance. + * Instantiating a new 0 length array is *usually* inefficient, unless you are doing reference comparisons later. If + * you are generating it simply to use as a "default" value for an array, consider using this instead to increase + * performance. */ public static final long[] EMPTY_LONG_ARRAY = new long[0]; /** - * Instantiating a new 0 length array is *usually* inefficient, unless you - * are doing reference comparisons later. If you are generating it simply to use - * as a "default" value for an array, consider using this instead to increase performance. + * Instantiating a new 0 length array is *usually* inefficient, unless you are doing reference comparisons later. If + * you are generating it simply to use as a "default" value for an array, consider using this instead to increase + * performance. */ public static final float[] EMPTY_FLOAT_ARRAY = new float[0]; /** - * Instantiating a new 0 length array is *usually* inefficient, unless you - * are doing reference comparisons later. If you are generating it simply to use - * as a "default" value for an array, consider using this instead to increase performance. + * Instantiating a new 0 length array is *usually* inefficient, unless you are doing reference comparisons later. If + * you are generating it simply to use as a "default" value for an array, consider using this instead to increase + * performance. */ public static final double[] EMPTY_DOUBLE_ARRAY = new double[0]; /** - * Instantiating a new 0 length array is *usually* inefficient, unless you - * are doing reference comparisons later. If you are generating it simply to use - * as a "default" value for an array, consider using this instead to increase performance. + * Instantiating a new 0 length array is *usually* inefficient, unless you are doing reference comparisons later. If + * you are generating it simply to use as a "default" value for an array, consider using this instead to increase + * performance. */ public static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[0]; - + /** - * Instantiating a new 0 length array is *usually* inefficient, unless you - * are doing reference comparisons later. If you are generating it simply to use - * as a "default" value for an array, consider using this instead to increase performance. + * Instantiating a new 0 length array is *usually* inefficient, unless you are doing reference comparisons later. If + * you are generating it simply to use as a "default" value for an array, consider using this instead to increase + * performance. */ public static final Character[] EMPTY_CHAR_OBJ_ARRAY = new Character[0]; /** - * Instantiating a new 0 length array is *usually* inefficient, unless you - * are doing reference comparisons later. If you are generating it simply to use - * as a "default" value for an array, consider using this instead to increase performance. + * Instantiating a new 0 length array is *usually* inefficient, unless you are doing reference comparisons later. If + * you are generating it simply to use as a "default" value for an array, consider using this instead to increase + * performance. */ public static final Byte[] EMPTY_BYTE_OBJ_ARRAY = new Byte[0]; /** - * Instantiating a new 0 length array is *usually* inefficient, unless you - * are doing reference comparisons later. If you are generating it simply to use - * as a "default" value for an array, consider using this instead to increase performance. + * Instantiating a new 0 length array is *usually* inefficient, unless you are doing reference comparisons later. If + * you are generating it simply to use as a "default" value for an array, consider using this instead to increase + * performance. */ public static final Short[] EMPTY_SHORT_OBJ_ARRAY = new Short[0]; /** - * Instantiating a new 0 length array is *usually* inefficient, unless you - * are doing reference comparisons later. If you are generating it simply to use - * as a "default" value for an array, consider using this instead to increase performance. + * Instantiating a new 0 length array is *usually* inefficient, unless you are doing reference comparisons later. If + * you are generating it simply to use as a "default" value for an array, consider using this instead to increase + * performance. */ public static final Integer[] EMPTY_INT_OBJ_ARRAY = new Integer[0]; /** - * Instantiating a new 0 length array is *usually* inefficient, unless you - * are doing reference comparisons later. If you are generating it simply to use - * as a "default" value for an array, consider using this instead to increase performance. + * Instantiating a new 0 length array is *usually* inefficient, unless you are doing reference comparisons later. If + * you are generating it simply to use as a "default" value for an array, consider using this instead to increase + * performance. */ public static final Long[] EMPTY_LONG_OBJ_ARRAY = new Long[0]; /** - * Instantiating a new 0 length array is *usually* inefficient, unless you - * are doing reference comparisons later. If you are generating it simply to use - * as a "default" value for an array, consider using this instead to increase performance. + * Instantiating a new 0 length array is *usually* inefficient, unless you are doing reference comparisons later. If + * you are generating it simply to use as a "default" value for an array, consider using this instead to increase + * performance. */ public static final Float[] EMPTY_FLOAT_OBJ_ARRAY = new Float[0]; /** - * Instantiating a new 0 length array is *usually* inefficient, unless you - * are doing reference comparisons later. If you are generating it simply to use - * as a "default" value for an array, consider using this instead to increase performance. + * Instantiating a new 0 length array is *usually* inefficient, unless you are doing reference comparisons later. If + * you are generating it simply to use as a "default" value for an array, consider using this instead to increase + * performance. */ public static final Double[] EMPTY_DOUBLE_OBJ_ARRAY = new Double[0]; /** - * Instantiating a new 0 length array is *usually* inefficient, unless you - * are doing reference comparisons later. If you are generating it simply to use - * as a "default" value for an array, consider using this instead to increase performance. + * Instantiating a new 0 length array is *usually* inefficient, unless you are doing reference comparisons later. If + * you are generating it simply to use as a "default" value for an array, consider using this instead to increase + * performance. */ public static final Boolean[] EMPTY_BOOLEAN_OBJ_ARRAY = new Boolean[0]; - - - /*************************************************************************** - * Slices - ***************************************************************************/ - - /** - * Slices an array, where the array [0, 1, 2] sliced from 0 to 2 would return the whole array. - * That is, start and finish are inclusive. Finish may be less than start, in which case the slice will also - * be backwards, so slicing from 1 to 0 would return [1, 0]. If start and finish are equal, an array with one - * result is returned. + /** + * Instantiating a new 0 length array is *usually* inefficient, unless you are doing reference comparisons later. If + * you are generating it simply to use as a "default" value for an array, consider using this instead to increase + * performance. + */ + public static final String[] EMPTY_STRING_ARRAY = new String[0]; + + /** + * ************************************************************************* + * Slices ************************************************************************* + */ + /** + * Slices an array, where the array [0, 1, 2] sliced from 0 to 2 would return the whole array. That is, start and + * finish are inclusive. Finish may be less than start, in which case the slice will also be backwards, so slicing + * from 1 to 0 would return [1, 0]. If start and finish are equal, an array with one result is returned. + * * @param The array type * @param array The array to be sliced. Note that the original array remains unchanged. * @param start The starting node. * @param finish The ending node (inclusive). - * @return + * @return */ - public static T [] slice(T[] array, int start, int finish){ + @SuppressWarnings("unchecked") + public static T[] slice(T[] array, int start, int finish) { int size = Math.abs(start - finish) + 1; - Object [] newArray = new Object[size]; - if(start <= finish){ + Object newArray = Array.newInstance(array.getClass().getComponentType(), size); + if(start <= finish) { int counter = 0; - for(int i = start; i <= finish; i++){ - newArray[counter++] = array[i]; + for(int i = start; i <= finish; i++) { + Array.set(newArray, counter++, array[i]); } } else { int counter = 0; - for(int i = start; i >= finish; i--){ - newArray[counter++] = array[i]; + for(int i = start; i >= finish; i--) { + Array.set(newArray, counter++, array[i]); } } - return (T[])newArray; + return (T[]) newArray; } - + /** - * Slices an array, where the array [0, 1, 2] sliced from 0 to 2 would return the whole array. - * That is, start and finish are inclusive. Finish may be less than start, in which case the slice will also - * be backwards, so slicing from 1 to 0 would return [1, 0]. If start and finish are equal, an array with one - * result is returned. + * Slices an array, where the array [0, 1, 2] sliced from 0 to 2 would return the whole array. That is, start and + * finish are inclusive. Finish may be less than start, in which case the slice will also be backwards, so slicing + * from 1 to 0 would return [1, 0]. If start and finish are equal, an array with one result is returned. + * * @param array The array to be sliced. Note that the original array remains unchanged. * @param start The starting node. * @param finish The ending node (inclusive). - * @return + * @return */ - public static char [] slice(char[] array, int start, int finish){ + public static char[] slice(char[] array, int start, int finish) { int size = Math.abs(start - finish) + 1; - char [] newArray = new char[size]; - if(start <= finish){ + char[] newArray = new char[size]; + if(start <= finish) { int counter = 0; - for(int i = start; i <= finish; i++){ + for(int i = start; i <= finish; i++) { newArray[counter++] = array[i]; } } else { int counter = 0; - for(int i = start; i >= finish; i--){ + for(int i = start; i >= finish; i--) { newArray[counter++] = array[i]; } } return newArray; } + /** - * Slices an array, where the array [0, 1, 2] sliced from 0 to 2 would return the whole array. - * That is, start and finish are inclusive. Finish may be less than start, in which case the slice will also - * be backwards, so slicing from 1 to 0 would return [1, 0]. If start and finish are equal, an array with one - * result is returned. + * Slices an array, where the array [0, 1, 2] sliced from 0 to 2 would return the whole array. That is, start and + * finish are inclusive. Finish may be less than start, in which case the slice will also be backwards, so slicing + * from 1 to 0 would return [1, 0]. If start and finish are equal, an array with one result is returned. + * * @param array The array to be sliced. Note that the original array remains unchanged. * @param start The starting node. * @param finish The ending node (inclusive). - * @return + * @return */ - public static byte [] slice(byte[] array, int start, int finish){ + public static byte[] slice(byte[] array, int start, int finish) { int size = Math.abs(start - finish) + 1; - byte [] newArray = new byte[size]; - if(start <= finish){ + byte[] newArray = new byte[size]; + if(start <= finish) { int counter = 0; - for(int i = start; i <= finish; i++){ + for(int i = start; i <= finish; i++) { newArray[counter++] = array[i]; } } else { int counter = 0; - for(int i = start; i >= finish; i--){ + for(int i = start; i >= finish; i--) { newArray[counter++] = array[i]; } } return newArray; } + /** - * Slices an array, where the array [0, 1, 2] sliced from 0 to 2 would return the whole array. - * That is, start and finish are inclusive. Finish may be less than start, in which case the slice will also - * be backwards, so slicing from 1 to 0 would return [1, 0]. If start and finish are equal, an array with one - * result is returned. + * Slices an array, where the array [0, 1, 2] sliced from 0 to 2 would return the whole array. That is, start and + * finish are inclusive. Finish may be less than start, in which case the slice will also be backwards, so slicing + * from 1 to 0 would return [1, 0]. If start and finish are equal, an array with one result is returned. + * * @param array The array to be sliced. Note that the original array remains unchanged. * @param start The starting node. * @param finish The ending node (inclusive). - * @return + * @return */ - public static short [] slice(short[] array, int start, int finish){ + public static short[] slice(short[] array, int start, int finish) { int size = Math.abs(start - finish) + 1; - short [] newArray = new short[size]; - if(start <= finish){ + short[] newArray = new short[size]; + if(start <= finish) { int counter = 0; - for(int i = start; i <= finish; i++){ + for(int i = start; i <= finish; i++) { newArray[counter++] = array[i]; } } else { int counter = 0; - for(int i = start; i >= finish; i--){ + for(int i = start; i >= finish; i--) { newArray[counter++] = array[i]; } } return newArray; } + /** - * Slices an array, where the array [0, 1, 2] sliced from 0 to 2 would return the whole array. - * That is, start and finish are inclusive. Finish may be less than start, in which case the slice will also - * be backwards, so slicing from 1 to 0 would return [1, 0]. If start and finish are equal, an array with one - * result is returned. + * Slices an array, where the array [0, 1, 2] sliced from 0 to 2 would return the whole array. That is, start and + * finish are inclusive. Finish may be less than start, in which case the slice will also be backwards, so slicing + * from 1 to 0 would return [1, 0]. If start and finish are equal, an array with one result is returned. + * * @param array The array to be sliced. Note that the original array remains unchanged. * @param start The starting node. * @param finish The ending node (inclusive). - * @return + * @return */ - public static int [] slice(int[] array, int start, int finish){ + public static int[] slice(int[] array, int start, int finish) { int size = Math.abs(start - finish) + 1; - int [] newArray = new int[size]; - if(start <= finish){ + int[] newArray = new int[size]; + if(start <= finish) { int counter = 0; - for(int i = start; i <= finish; i++){ + for(int i = start; i <= finish; i++) { newArray[counter++] = array[i]; } } else { int counter = 0; - for(int i = start; i >= finish; i--){ + for(int i = start; i >= finish; i--) { newArray[counter++] = array[i]; } } return newArray; } + /** - * Slices an array, where the array [0, 1, 2] sliced from 0 to 2 would return the whole array. - * That is, start and finish are inclusive. Finish may be less than start, in which case the slice will also - * be backwards, so slicing from 1 to 0 would return [1, 0]. If start and finish are equal, an array with one - * result is returned. + * Slices an array, where the array [0, 1, 2] sliced from 0 to 2 would return the whole array. That is, start and + * finish are inclusive. Finish may be less than start, in which case the slice will also be backwards, so slicing + * from 1 to 0 would return [1, 0]. If start and finish are equal, an array with one result is returned. + * * @param array The array to be sliced. Note that the original array remains unchanged. * @param start The starting node. * @param finish The ending node (inclusive). - * @return + * @return */ - public static long [] slice(long[] array, int start, int finish){ + public static long[] slice(long[] array, int start, int finish) { int size = Math.abs(start - finish) + 1; - long [] newArray = new long[size]; - if(start <= finish){ + long[] newArray = new long[size]; + if(start <= finish) { int counter = 0; - for(int i = start; i <= finish; i++){ + for(int i = start; i <= finish; i++) { newArray[counter++] = array[i]; } } else { int counter = 0; - for(int i = start; i >= finish; i--){ + for(int i = start; i >= finish; i--) { newArray[counter++] = array[i]; } } return newArray; } + /** - * Slices an array, where the array [0, 1, 2] sliced from 0 to 2 would return the whole array. - * That is, start and finish are inclusive. Finish may be less than start, in which case the slice will also - * be backwards, so slicing from 1 to 0 would return [1, 0]. If start and finish are equal, an array with one - * result is returned. + * Slices an array, where the array [0, 1, 2] sliced from 0 to 2 would return the whole array. That is, start and + * finish are inclusive. Finish may be less than start, in which case the slice will also be backwards, so slicing + * from 1 to 0 would return [1, 0]. If start and finish are equal, an array with one result is returned. + * * @param array The array to be sliced. Note that the original array remains unchanged. * @param start The starting node. * @param finish The ending node (inclusive). - * @return + * @return */ - public static float [] slice(float[] array, int start, int finish){ + public static float[] slice(float[] array, int start, int finish) { int size = Math.abs(start - finish) + 1; - float [] newArray = new float[size]; - if(start <= finish){ + float[] newArray = new float[size]; + if(start <= finish) { int counter = 0; - for(int i = start; i <= finish; i++){ + for(int i = start; i <= finish; i++) { newArray[counter++] = array[i]; } } else { int counter = 0; - for(int i = start; i >= finish; i--){ + for(int i = start; i >= finish; i--) { newArray[counter++] = array[i]; } } return newArray; } + /** - * Slices an array, where the array [0, 1, 2] sliced from 0 to 2 would return the whole array. - * That is, start and finish are inclusive. Finish may be less than start, in which case the slice will also - * be backwards, so slicing from 1 to 0 would return [1, 0]. If start and finish are equal, an array with one - * result is returned. + * Slices an array, where the array [0, 1, 2] sliced from 0 to 2 would return the whole array. That is, start and + * finish are inclusive. Finish may be less than start, in which case the slice will also be backwards, so slicing + * from 1 to 0 would return [1, 0]. If start and finish are equal, an array with one result is returned. + * * @param array The array to be sliced. Note that the original array remains unchanged. * @param start The starting node. * @param finish The ending node (inclusive). - * @return + * @return */ - public static double [] slice(double[] array, int start, int finish){ + public static double[] slice(double[] array, int start, int finish) { int size = Math.abs(start - finish) + 1; - double [] newArray = new double[size]; - if(start <= finish){ + double[] newArray = new double[size]; + if(start <= finish) { int counter = 0; - for(int i = start; i <= finish; i++){ + for(int i = start; i <= finish; i++) { newArray[counter++] = array[i]; } } else { int counter = 0; - for(int i = start; i >= finish; i--){ + for(int i = start; i >= finish; i--) { newArray[counter++] = array[i]; } } return newArray; } + /** - * Slices an array, where the array [0, 1, 2] sliced from 0 to 2 would return the whole array. - * That is, start and finish are inclusive. Finish may be less than start, in which case the slice will also - * be backwards, so slicing from 1 to 0 would return [1, 0]. If start and finish are equal, an array with one - * result is returned. + * Slices an array, where the array [0, 1, 2] sliced from 0 to 2 would return the whole array. That is, start and + * finish are inclusive. Finish may be less than start, in which case the slice will also be backwards, so slicing + * from 1 to 0 would return [1, 0]. If start and finish are equal, an array with one result is returned. + * * @param array The array to be sliced. Note that the original array remains unchanged. * @param start The starting node. * @param finish The ending node (inclusive). - * @return + * @return */ - public static boolean [] slice(boolean[] array, int start, int finish){ + public static boolean[] slice(boolean[] array, int start, int finish) { int size = Math.abs(start - finish) + 1; - boolean [] newArray = new boolean[size]; - if(start <= finish){ + boolean[] newArray = new boolean[size]; + if(start <= finish) { int counter = 0; - for(int i = start; i <= finish; i++){ + for(int i = start; i <= finish; i++) { newArray[counter++] = array[i]; } } else { int counter = 0; - for(int i = start; i >= finish; i--){ + for(int i = start; i >= finish; i--) { newArray[counter++] = array[i]; } } return newArray; } - - /*************************************************************************** - * Unboxes - ***************************************************************************/ - + + /** + * ************************************************************************* + * Unboxes ************************************************************************* + */ /** - * "Unboxes" an array, that is, unboxes all the primitives in this - * array, and returns a primitive array. + * "Unboxes" an array, that is, unboxes all the primitives in this array, and returns a primitive array. + * * @param array The "boxed" array - * @return The "unboxed" array + * @return The "unboxed" array */ - public static char[] unbox(Character[] array){ - if(array == null){ + @SuppressWarnings("UnnecessaryUnboxing") + public static char[] unbox(Character[] array) { + if(array == null) { return null; - } else if(array.length == 0){ + } else if(array.length == 0) { return EMPTY_CHAR_ARRAY; } - final char [] newArray = new char[array.length]; - for(int i = 0; i < array.length; i++){ + final char[] newArray = new char[array.length]; + for(int i = 0; i < array.length; i++) { newArray[i] = array[i].charValue(); } return newArray; } - + /** - * "Unboxes" an array, that is, unboxes all the primitives in this - * array, and returns a primitive array. + * "Unboxes" an array, that is, unboxes all the primitives in this array, and returns a primitive array. + * * @param array The "boxed" array - * @return The "unboxed" array + * @return The "unboxed" array */ - public static byte[] unbox(Byte[] array){ - if(array == null){ + @SuppressWarnings("UnnecessaryUnboxing") + public static byte[] unbox(Byte[] array) { + if(array == null) { return null; - } else if(array.length == 0){ + } else if(array.length == 0) { return EMPTY_BYTE_ARRAY; } - final byte [] newArray = new byte[array.length]; - for(int i = 0; i < array.length; i++){ + final byte[] newArray = new byte[array.length]; + for(int i = 0; i < array.length; i++) { newArray[i] = array[i].byteValue(); } return newArray; } - + /** - * "Unboxes" an array, that is, unboxes all the primitives in this - * array, and returns a primitive array. + * "Unboxes" an array, that is, unboxes all the primitives in this array, and returns a primitive array. + * * @param array The "boxed" array - * @return The "unboxed" array + * @return The "unboxed" array */ - public static short[] unbox(Short[] array){ - if(array == null){ + @SuppressWarnings("UnnecessaryUnboxing") + public static short[] unbox(Short[] array) { + if(array == null) { return null; - } else if(array.length == 0){ + } else if(array.length == 0) { return EMPTY_SHORT_ARRAY; } - final short [] newArray = new short[array.length]; - for(int i = 0; i < array.length; i++){ + final short[] newArray = new short[array.length]; + for(int i = 0; i < array.length; i++) { newArray[i] = array[i].shortValue(); } return newArray; } - + /** - * "Unboxes" an array, that is, unboxes all the primitives in this - * array, and returns a primitive array. + * "Unboxes" an array, that is, unboxes all the primitives in this array, and returns a primitive array. + * * @param array The "boxed" array - * @return The "unboxed" array + * @return The "unboxed" array */ - public static int[] unbox(Integer[] array){ - if(array == null){ + @SuppressWarnings("UnnecessaryUnboxing") + public static int[] unbox(Integer[] array) { + if(array == null) { return null; - } else if(array.length == 0){ + } else if(array.length == 0) { return EMPTY_INT_ARRAY; } - final int [] newArray = new int[array.length]; - for(int i = 0; i < array.length; i++){ + final int[] newArray = new int[array.length]; + for(int i = 0; i < array.length; i++) { newArray[i] = array[i].intValue(); } return newArray; } - + /** - * "Unboxes" an array, that is, unboxes all the primitives in this - * array, and returns a primitive array. + * "Unboxes" an array, that is, unboxes all the primitives in this array, and returns a primitive array. + * * @param array The "boxed" array - * @return The "unboxed" array + * @return The "unboxed" array */ - public static long[] unbox(Long[] array){ - if(array == null){ + @SuppressWarnings("UnnecessaryUnboxing") + public static long[] unbox(Long[] array) { + if(array == null) { return null; - } else if(array.length == 0){ + } else if(array.length == 0) { return EMPTY_LONG_ARRAY; } - final long [] newArray = new long[array.length]; - for(int i = 0; i < array.length; i++){ + final long[] newArray = new long[array.length]; + for(int i = 0; i < array.length; i++) { newArray[i] = array[i].longValue(); } return newArray; } - + /** - * "Unboxes" an array, that is, unboxes all the primitives in this - * array, and returns a primitive array. + * "Unboxes" an array, that is, unboxes all the primitives in this array, and returns a primitive array. + * * @param array The "boxed" array - * @return The "unboxed" array + * @return The "unboxed" array */ - public static float[] unbox(Float[] array){ - if(array == null){ + @SuppressWarnings("UnnecessaryUnboxing") + public static float[] unbox(Float[] array) { + if(array == null) { return null; - } else if(array.length == 0){ + } else if(array.length == 0) { return EMPTY_FLOAT_ARRAY; } - final float [] newArray = new float[array.length]; - for(int i = 0; i < array.length; i++){ + final float[] newArray = new float[array.length]; + for(int i = 0; i < array.length; i++) { newArray[i] = array[i].floatValue(); } return newArray; } - + /** - * "Unboxes" an array, that is, unboxes all the primitives in this - * array, and returns a primitive array. + * "Unboxes" an array, that is, unboxes all the primitives in this array, and returns a primitive array. + * * @param array The "boxed" array * @return The "unboxed" array */ - public static double[] unbox(Double[] array){ - if(array == null){ + @SuppressWarnings("UnnecessaryUnboxing") + public static double[] unbox(Double[] array) { + if(array == null) { return null; - } else if(array.length == 0){ + } else if(array.length == 0) { return EMPTY_DOUBLE_ARRAY; } - final double [] newArray = new double[array.length]; - for(int i = 0; i < array.length; i++){ + final double[] newArray = new double[array.length]; + for(int i = 0; i < array.length; i++) { newArray[i] = array[i].doubleValue(); } return newArray; } - + /** - * "Unboxes" an array, that is, unboxes all the primitives in this - * array, and returns a primitive array. + * "Unboxes" an array, that is, unboxes all the primitives in this array, and returns a primitive array. + * * @param array The "boxed" array * @return The "unboxed" array */ - public static boolean[] unbox(Boolean[] array){ - if(array == null){ + @SuppressWarnings("UnnecessaryUnboxing") + public static boolean[] unbox(Boolean[] array) { + if(array == null) { return null; - } else if(array.length == 0){ + } else if(array.length == 0) { return EMPTY_BOOLEAN_ARRAY; } - final boolean [] newArray = new boolean[array.length]; - for(int i = 0; i < array.length; i++){ + final boolean[] newArray = new boolean[array.length]; + for(int i = 0; i < array.length; i++) { newArray[i] = array[i].booleanValue(); } return newArray; } - - /*************************************************************************** - * Boxes - ***************************************************************************/ - + /** - * "Boxes" an array, that is, boxes all the primitives in the given array, - * and returns a new "boxed" array. + * ************************************************************************* + * Boxes ************************************************************************* + */ + /** + * "Boxes" an array, that is, boxes all the primitives in the given array, and returns a new "boxed" array. + * * @param array The primitive array * @return The "boxed" array */ - public static Character[] box(char[] array){ - if(array == null){ + public static Character[] box(char[] array) { + if(array == null) { return null; - } else if(array.length == 0){ + } else if(array.length == 0) { return EMPTY_CHAR_OBJ_ARRAY; } final Character[] newArray = new Character[array.length]; - for(int i = 0; i < array.length; i++){ + for(int i = 0; i < array.length; i++) { newArray[i] = array[i]; } return newArray; } - + /** - * "Boxes" an array, that is, boxes all the primitives in the given array, - * and returns a new "boxed" array. + * "Boxes" an array, that is, boxes all the primitives in the given array, and returns a new "boxed" array. + * * @param array The primitive array * @return The "boxed" array */ - public static Byte[] box(byte[] array){ - if(array == null){ + public static Byte[] box(byte[] array) { + if(array == null) { return null; - } else if(array.length == 0){ + } else if(array.length == 0) { return EMPTY_BYTE_OBJ_ARRAY; } final Byte[] newArray = new Byte[array.length]; - for(int i = 0; i < array.length; i++){ + for(int i = 0; i < array.length; i++) { newArray[i] = array[i]; } return newArray; } - + /** - * "Boxes" an array, that is, boxes all the primitives in the given array, - * and returns a new "boxed" array. + * "Boxes" an array, that is, boxes all the primitives in the given array, and returns a new "boxed" array. + * * @param array The primitive array * @return The "boxed" array */ - public static Short[] box(short[] array){ - if(array == null){ + public static Short[] box(short[] array) { + if(array == null) { return null; - } else if(array.length == 0){ + } else if(array.length == 0) { return EMPTY_SHORT_OBJ_ARRAY; } final Short[] newArray = new Short[array.length]; - for(int i = 0; i < array.length; i++){ + for(int i = 0; i < array.length; i++) { newArray[i] = array[i]; } return newArray; } - + /** - * "Boxes" an array, that is, boxes all the primitives in the given array, - * and returns a new "boxed" array. + * "Boxes" an array, that is, boxes all the primitives in the given array, and returns a new "boxed" array. + * * @param array The primitive array * @return The "boxed" array */ - public static Integer[] box(int[] array){ - if(array == null){ + public static Integer[] box(int[] array) { + if(array == null) { return null; - } else if(array.length == 0){ + } else if(array.length == 0) { return EMPTY_INT_OBJ_ARRAY; } final Integer[] newArray = new Integer[array.length]; - for(int i = 0; i < array.length; i++){ + for(int i = 0; i < array.length; i++) { newArray[i] = array[i]; } return newArray; } - + /** - * "Boxes" an array, that is, boxes all the primitives in the given array, - * and returns a new "boxed" array. + * "Boxes" an array, that is, boxes all the primitives in the given array, and returns a new "boxed" array. + * * @param array The primitive array * @return The "boxed" array */ - public static Long[] box(long[] array){ - if(array == null){ + public static Long[] box(long[] array) { + if(array == null) { return null; - } else if(array.length == 0){ + } else if(array.length == 0) { return EMPTY_LONG_OBJ_ARRAY; } final Long[] newArray = new Long[array.length]; - for(int i = 0; i < array.length; i++){ + for(int i = 0; i < array.length; i++) { newArray[i] = array[i]; } return newArray; } - + /** - * "Boxes" an array, that is, boxes all the primitives in the given array, - * and returns a new "boxed" array. + * "Boxes" an array, that is, boxes all the primitives in the given array, and returns a new "boxed" array. + * * @param array The primitive array * @return The "boxed" array */ - public static Float[] box(float[] array){ - if(array == null){ + public static Float[] box(float[] array) { + if(array == null) { return null; - } else if(array.length == 0){ + } else if(array.length == 0) { return EMPTY_FLOAT_OBJ_ARRAY; } final Float[] newArray = new Float[array.length]; - for(int i = 0; i < array.length; i++){ + for(int i = 0; i < array.length; i++) { newArray[i] = array[i]; } return newArray; } - + /** - * "Boxes" an array, that is, boxes all the primitives in the given array, - * and returns a new "boxed" array. + * "Boxes" an array, that is, boxes all the primitives in the given array, and returns a new "boxed" array. + * * @param array The primitive array * @return The "boxed" array */ - public static Double[] box(double[] array){ - if(array == null){ + public static Double[] box(double[] array) { + if(array == null) { return null; - } else if(array.length == 0){ + } else if(array.length == 0) { return EMPTY_DOUBLE_OBJ_ARRAY; } final Double[] newArray = new Double[array.length]; - for(int i = 0; i < array.length; i++){ + for(int i = 0; i < array.length; i++) { newArray[i] = array[i]; } return newArray; } - + /** - * "Boxes" an array, that is, boxes all the primitives in the given array, - * and returns a new "boxed" array. + * "Boxes" an array, that is, boxes all the primitives in the given array, and returns a new "boxed" array. + * * @param array The primitive array * @return The "boxed" array */ - public static Boolean[] box(boolean[] array){ - if(array == null){ + public static Boolean[] box(boolean[] array) { + if(array == null) { return null; - } else if(array.length == 0){ + } else if(array.length == 0) { return EMPTY_BOOLEAN_OBJ_ARRAY; } final Boolean[] newArray = new Boolean[array.length]; - for(int i = 0; i < array.length; i++){ + for(int i = 0; i < array.length; i++) { newArray[i] = array[i]; } return newArray; } - - /*************************************************************************** - * Misc - ***************************************************************************/ - + + /** + * ************************************************************************* + * Misc ************************************************************************* + */ /** * Returns a new array, based on the runtime type of the list. + * * @param * @param list - * @return + * @return */ - public static T[] asArray(Class clazz, List list){ + @SuppressWarnings("unchecked") + public static T[] asArray(Class clazz, List list) { T[] obj = (T[]) Array.newInstance(clazz, list.size()); - for(int i = 0; i < list.size(); i++){ - obj[i] = (T)list.get(i); + for(int i = 0; i < list.size(); i++) { + obj[i] = list.get(i); } return obj; - } - + } + /** - * Returns a new array, where each item has been cast to the - * specified class, and the returned array is an array type - * based on that class. + * Returns a new array, where each item has been cast to the specified class, and the returned array is an array + * type based on that class. + * * @param - * @param array Despite being an Object, instead of an Object[], this will throw a ClassCastException - * if it is not an array type. - * @param toClass - * @return - */ - public static T cast(Object array, Class toArrayClass){ - if(!array.getClass().isArray()){ + * @param array Despite being an Object, instead of an Object[], this will throw a ClassCastException if it is not + * an array type. + * @param toArrayClass Should be an array type, i.e. {@code String[].class} + * @return + */ + @SuppressWarnings("unchecked") + public static T cast(Object array, Class toArrayClass) { + if(!array.getClass().isArray()) { throw new ClassCastException(); } Object obj; - Class toClass = toArrayClass.getComponentType(); + Class toClass = toArrayClass.getComponentType(); obj = toArrayClass.cast(Array.newInstance(toClass, Array.getLength(array))); - for(int i = 0; i < Array.getLength(array); i++){ + for(int i = 0; i < Array.getLength(array); i++) { doSet(obj, i, Array.get(array, i)); } - return (T)obj; - } - - private static void doSet(Object array, int index, Object o){ - Class componentType = array.getClass().getComponentType(); - if(componentType.isPrimitive()){ - if(componentType == char.class){ - Array.setChar(array, index, ((Character)o).charValue()); - } else if(componentType == byte.class){ - Array.setByte(array, index, ((Number)o).byteValue()); - } else if(componentType == short.class){ - Array.setShort(array, index, ((Number)o).shortValue()); - } else if(componentType == int.class){ - Array.setInt(array, index, ((Number)o).intValue()); - } else if(componentType == long.class){ - Array.setLong(array, index, ((Number)o).longValue()); - } else if(componentType == float.class){ - Array.setFloat(array, index, ((Number)o).floatValue()); - } else if(componentType == double.class){ - Array.setDouble(array, index, ((Number)o).doubleValue()); - } else if(componentType == boolean.class){ - Array.setBoolean(array, index, ((Boolean)o).booleanValue()); + return (T) obj; + } + + @SuppressWarnings("UnnecessaryUnboxing") + private static void doSet(Object array, int index, Object o) { + Class componentType = array.getClass().getComponentType(); + if(componentType.isPrimitive()) { + if(componentType == char.class) { + Array.setChar(array, index, ((Character) o).charValue()); + } else if(componentType == byte.class) { + Array.setByte(array, index, ((Number) o).byteValue()); + } else if(componentType == short.class) { + Array.setShort(array, index, ((Number) o).shortValue()); + } else if(componentType == int.class) { + Array.setInt(array, index, ((Number) o).intValue()); + } else if(componentType == long.class) { + Array.setLong(array, index, ((Number) o).longValue()); + } else if(componentType == float.class) { + Array.setFloat(array, index, ((Number) o).floatValue()); + } else if(componentType == double.class) { + Array.setDouble(array, index, ((Number) o).doubleValue()); + } else if(componentType == boolean.class) { + Array.setBoolean(array, index, ((Boolean) o).booleanValue()); } } else { Array.set(array, index, o); } } + + /** + * Converts a char array to a byte array, assuming UTF-8 encoding. + * + * {@link #charToBytes(char[], java.lang.String)} for the documentation on this method other than the encoding used. + * + * @param chars The char array to convert. + * @return + */ + public static byte[] charToBytes(char[] chars) { + return charToBytes(chars, "UTF-8"); + } + + /** + * Converts a char array to a byte array, assuming the given encoding. This is done in a secure manner, and + * potentially sensitive data is cleared from memory after the encoding is done. + * + * @param chars The char array to convert. + * @param encoding The encoding to use. + * @return + */ + @SuppressWarnings("UnusedAssignment") + public static byte[] charToBytes(char[] chars, String encoding) { + CharBuffer charBuffer = CharBuffer.wrap(chars); + ByteBuffer byteBuffer = Charset.forName(encoding).encode(charBuffer); + byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), + byteBuffer.position(), byteBuffer.limit()); + Arrays.fill(charBuffer.array(), '\u0000'); // clear sensitive data + Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data + charBuffer = null; // faster GC + byteBuffer = null; // faster GC + System.gc(); + return bytes; + } + + /** + * Converts a byte array to a char array, assuming UTF-8 encoding. + * + * {@link #bytesToChar(byte[], java.lang.String)} for the documentation on this method other than the encoding used. + * + * @param bytes The byte array to convert. + * @return + */ + public static char[] bytesToChar(byte[] bytes) { + return bytesToChar(bytes, "UTF-8"); + } + + /** + * Converts a byte array to a char array, assuming the given encoding. This is done in a secure manner, and + * potentially sensitive data is cleared from memory after the encoding is done. + * + * @param bytes The byte array to convert + * @param encoding The encoding to use + * @return + */ + @SuppressWarnings("UnusedAssignment") + public static char[] bytesToChar(byte[] bytes, String encoding) { + ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + CharBuffer charBuffer = Charset.forName(encoding).decode(byteBuffer); + char[] chars = Arrays.copyOfRange(charBuffer.array(), + charBuffer.position(), charBuffer.limit()); + Arrays.fill(byteBuffer.array(), (byte) 0); + Arrays.fill(charBuffer.array(), '\u0000'); + charBuffer = null; + byteBuffer = null; + System.gc(); + return chars; + } + + /** + * Fills an array with the given value. + * @param array The array to fill. + * @param with The value to fill the array with. + * @param The type of the array. + */ + public static void fill(T[] array, T with) { + for(int i = 0; i < array.length; i++) { + array[i] = with; + } + } + + /** + * Fills an array with the given value. + * @param array The array to fill. + * @param with The value to fill the array with. + */ + public static void fill(char[] array, char with) { + for(int i = 0; i < array.length; i++) { + array[i] = with; + } + } + + /** + * Fills an array with the given value. + * @param array The array to fill. + * @param with The value to fill the array with. + */ + public static void fill(byte[] array, byte with) { + for(int i = 0; i < array.length; i++) { + array[i] = with; + } + } + + /** + * Fills an array with the given value. + * @param array The array to fill. + * @param with The value to fill the array with. + */ + public static void fill(short[] array, short with) { + for(int i = 0; i < array.length; i++) { + array[i] = with; + } + } + + /** + * Fills an array with the given value. + * @param array The array to fill. + * @param with The value to fill the array with. + */ + public static void fill(int[] array, int with) { + for(int i = 0; i < array.length; i++) { + array[i] = with; + } + } + + /** + * Fills an array with the given value. + * @param array The array to fill. + * @param with The value to fill the array with. + */ + public static void fill(long[] array, long with) { + for(int i = 0; i < array.length; i++) { + array[i] = with; + } + } + + /** + * Fills an array with the given value. + * @param array The array to fill. + * @param with The value to fill the array with. + */ + public static void fill(float[] array, float with) { + for(int i = 0; i < array.length; i++) { + array[i] = with; + } + } + + /** + * Fills an array with the given value. + * @param array The array to fill. + * @param with The value to fill the array with. + */ + public static void fill(double[] array, double with) { + for(int i = 0; i < array.length; i++) { + array[i] = with; + } + } + + /** + * Fills an array with the given value. + * @param array The array to fill. + * @param with The value to fill the array with. + */ + public static void fill(boolean[] array, boolean with) { + for(int i = 0; i < array.length; i++) { + array[i] = with; + } + } + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/Common/AutoFlushObjectOutputStream.java b/src/main/java/com/laytonsmith/PureUtilities/Common/AutoFlushObjectOutputStream.java index 34b48050d7..7fb74d3c88 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Common/AutoFlushObjectOutputStream.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Common/AutoFlushObjectOutputStream.java @@ -6,8 +6,7 @@ import java.io.OutputStream; /** - * This class extends ObjectOutputStream, but automatically flushes - * after each write operation. + * This class extends ObjectOutputStream, but automatically flushes after each write operation. */ public class AutoFlushObjectOutputStream extends ObjectOutputStream { @@ -15,7 +14,7 @@ public AutoFlushObjectOutputStream() throws IOException { super(); } - public AutoFlushObjectOutputStream(OutputStream outputStream) throws IOException{ + public AutoFlushObjectOutputStream(OutputStream outputStream) throws IOException { super(outputStream); } diff --git a/src/main/java/com/laytonsmith/PureUtilities/Common/ByteArrayUtils.java b/src/main/java/com/laytonsmith/PureUtilities/Common/ByteArrayUtils.java new file mode 100644 index 0000000000..cd34d2c7ef --- /dev/null +++ b/src/main/java/com/laytonsmith/PureUtilities/Common/ByteArrayUtils.java @@ -0,0 +1,71 @@ +package com.laytonsmith.PureUtilities.Common; + +/** + * + * @author Cailin + */ +public class ByteArrayUtils { + + private final boolean useUpper; + private static final String MIDDLE_UPPER = " X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 XA XB XC XD XE XF "; + private static final String MIDDLE_LOWER = " x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xa xb xc xd xe xf "; + + /** + * Creates a new ByteArrayUtils object with default options + */ + public ByteArrayUtils() { + useUpper = true; + } + + public ByteArrayUtils(boolean useUpperCase) { + this.useUpper = useUpperCase; + } + + public String baToHexTable(byte[] ba) { + StringBuilder b = new StringBuilder(); + StringBuilder c1 = new StringBuilder(); + StringBuilder c2 = new StringBuilder(); + b.append("Address |").append(useUpper ? MIDDLE_UPPER : MIDDLE_LOWER).append("| ASCII |\n"); + for(int i = 0; i < ba.length + (16 - (ba.length % 16)); i++) { + if(i % 16 == 0) { + // First line, the address + b.append(String.format("0x%07" + (useUpper ? "X" : "x"), i / 16)).append(useUpper ? "X " : "x "); + } + if(i < ba.length) { + byte by = ba[i]; + c1.append(toHex(by)).append(" "); + char w = '.'; + if(by != 0) { + w = (char) by; + } + if(Character.isISOControl(by)) { + w = '.'; + } + c2.append(w); + } else { + c1.append(".. "); + c2.append("."); + } + if(i % 16 == 15) { + // End of line, construct line + b.append("| ").append(c1.toString()).append("| ") + .append(c2.toString()).append(" |\n"); + c1 = new StringBuilder(); + c2 = new StringBuilder(); + } + } + return b.toString(); + } + + private static final char[] UPPER_HEX_ARRAY = "0123456789ABCDEF".toCharArray(); + private static final char[] LOWER_HEX_ARRAY = "0123456789abcdef".toCharArray(); + + private String toHex(byte b) { + int v = b & 0xFF; + if(useUpper) { + return new String(new char[]{UPPER_HEX_ARRAY[v >>> 4], UPPER_HEX_ARRAY[v & 0x0F]}); + } else { + return new String(new char[]{LOWER_HEX_ARRAY[v >>> 4], LOWER_HEX_ARRAY[v & 0x0F]}); + } + } +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/Common/ClassUtils.java b/src/main/java/com/laytonsmith/PureUtilities/Common/ClassUtils.java index f3d70a1d6e..677be111af 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Common/ClassUtils.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Common/ClassUtils.java @@ -1,39 +1,39 @@ - package com.laytonsmith.PureUtilities.Common; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; /** - * Provides wrapper methods around some common methods that Class and some of - * the java.reflect package class left out. + * Provides wrapper methods around some common methods that Class and some of the java.reflect package class left out. */ public class ClassUtils { + private static final Pattern ARRAY_COUNT_PATTERN = Pattern.compile("\\[\\]"); - + /** - * Returns the Class object, given the in-code class name. This takes into - * account inner classes not being handled the same normally, as well as ... - * for varargs, and [] for arrays. For instance, java.lang.String[] would - * return the class object for String[].class. Primitives are handled - * correctly as well. This works like Class.forName in all other regards, - * however. + * Returns the Class object, given the in-code class name. This takes into account inner classes not being handled + * the same normally, as well as ... for varargs, and [] for arrays. For instance, java.lang.String[] would return + * the class object for String[].class. Primitives are handled correctly as well. This works like Class.forName in + * all other regards, however. * * @param className The canonical class name. * @return The class object. * @throws ClassNotFoundException If the class can't be found. */ - public static Class forCanonicalName(String className) throws ClassNotFoundException{ + public static Class forCanonicalName(String className) throws ClassNotFoundException { return forCanonicalName(className, false, false, null); } - + /** - * Returns the Class object, given the in-code class name. This takes into - * account inner classes not being handled the same normally, as well as ... - * for varargs, and [] for arrays. For instance, java.lang.String[] would - * return the class object for String[].class. Primitives are handled - * correctly as well. This works like Class.forName in all other regards, - * however. + * Returns the Class object, given the in-code class name. This takes into account inner classes not being handled + * the same normally, as well as ... for varargs, and [] for arrays. For instance, java.lang.String[] would return + * the class object for String[].class. Primitives are handled correctly as well. This works like Class.forName in + * all other regards, however. * * @param className The canonical class name * @param initialize whether the class must be initialized @@ -41,81 +41,90 @@ public static Class forCanonicalName(String className) throws ClassNotFoundExcep * @return The class object. * @throws ClassNotFoundException If the class can't be found. */ - public static Class forCanonicalName(String className, boolean initialize, ClassLoader classLoader) throws ClassNotFoundException{ + public static Class forCanonicalName(String className, boolean initialize, ClassLoader classLoader) throws ClassNotFoundException { return forCanonicalName(className, true, initialize, classLoader); } - + + private static final Map CANONICAL_CLASS_CACHE = new ConcurrentHashMap<>(); /** * Private version, which accepts the useInitializer parameter. + * * @param className * @param useInitializer * @param initialize * @param classLoader * @return - * @throws ClassNotFoundException + * @throws ClassNotFoundException */ - private static Class forCanonicalName(String className, boolean useInitializer, boolean initialize, ClassLoader classLoader) throws ClassNotFoundException { + private static Class forCanonicalName(String className, boolean useInitializer, boolean initialize, + ClassLoader classLoader) throws ClassNotFoundException { + if(CANONICAL_CLASS_CACHE.containsKey(className)) { + return CANONICAL_CLASS_CACHE.get(className); + } + if("void".equals(className)) { + return void.class; + } className = StringUtils.replaceLast(className, "\\.\\.\\.", "[]"); //Of course primitives all need to be dealt with specially. int arrays = 0; Matcher m = ARRAY_COUNT_PATTERN.matcher(className); - while(m.find()){ + while(m.find()) { arrays++; } String simpleName = className.replaceAll("\\[\\]", ""); String primitiveID = null; Class primitiveClass = null; - if(null != simpleName)switch (simpleName) { - case "boolean": - primitiveID = "Z"; - primitiveClass = boolean.class; - break; - case "byte": - primitiveID = "B"; - primitiveClass = byte.class; - break; - case "short": - primitiveID = "S"; - primitiveClass = short.class; - break; - case "int": - primitiveID = "I"; - primitiveClass = int.class; - break; - case "long": - primitiveID = "J"; - primitiveClass = long.class; - break; - case "float": - primitiveID = "F"; - primitiveClass = float.class; - break; - case "double": - primitiveID = "D"; - primitiveClass = double.class; - break; - case "char": - primitiveID = "C"; - primitiveClass = char.class; - break; + if(null != simpleName) { + switch(simpleName) { + case "boolean": + primitiveID = "Z"; + primitiveClass = boolean.class; + break; + case "byte": + primitiveID = "B"; + primitiveClass = byte.class; + break; + case "short": + primitiveID = "S"; + primitiveClass = short.class; + break; + case "int": + primitiveID = "I"; + primitiveClass = int.class; + break; + case "long": + primitiveID = "J"; + primitiveClass = long.class; + break; + case "float": + primitiveID = "F"; + primitiveClass = float.class; + break; + case "double": + primitiveID = "D"; + primitiveClass = double.class; + break; + case "char": + primitiveID = "C"; + primitiveClass = char.class; + break; + } } - if(primitiveClass != null){ - if(arrays > 0){ + if(primitiveClass != null) { + if(arrays > 0) { //This will be dealt with below className = StringUtils.stringMultiply(arrays, "[") + primitiveID; } else { //Class.forName doesn't know how to deal with this, so short circuit. return primitiveClass; } - } else { - if(arrays > 0){ - //Ok, we need to get it from the canonical name - className = StringUtils.stringMultiply(arrays, "[") + "L" + simpleName + ";"; - } + } else if(arrays > 0) { + //Ok, we need to get it from the canonical name + className = StringUtils.stringMultiply(arrays, "[") + "L" + simpleName + ";"; } Class c = null; try { - if(useInitializer){ + if(useInitializer) { c = Class.forName(className, initialize, classLoader); } else { c = Class.forName(className); @@ -123,10 +132,10 @@ private static Class forCanonicalName(String className, boolean useInitializer, } catch (ClassNotFoundException ex) { //Ok, try replacing the last . with $ as this may be an inner class String name = className; - while (name.contains(".")) { + while(name.contains(".")) { name = StringUtils.replaceLast(name, "\\.", "$"); try { - if(useInitializer){ + if(useInitializer) { c = Class.forName(name, initialize, classLoader); } else { c = Class.forName(name); @@ -137,100 +146,163 @@ private static Class forCanonicalName(String className, boolean useInitializer, //No? Try again then. } } - if (c == null) { + if(c == null) { //We really couldn't find it. throw ex; } } + CANONICAL_CLASS_CACHE.put(className, c); return c; } - + /** - * Returns the name of the class, as the JVM would output it. For instance, - * for an int, "I" is returned, for an array of Objects, "[Ljava/lang/Object;" is - * returned. + * Returns the name of the class, as the JVM would output it. For instance, for an int, "I" is returned, for an + * array of Objects, "[Ljava/lang/Object;" is returned. If the input is null, null is returned. + * * @param clazz - * @return + * @return */ - public static String getJVMName(Class clazz){ + public static String getJVMName(Class clazz) { + if(clazz == null) { + return null; + } //For arrays, .getName() is fine. - if(clazz.isArray()){ - return clazz.getName().replace(".", "/"); + if(clazz.isArray()) { + return clazz.getName().replace('.', '/'); } - if(clazz == boolean.class){ + if(clazz == boolean.class) { return "Z"; - } else if(clazz == byte.class){ + } else if(clazz == byte.class) { return "B"; - } else if(clazz == short.class){ + } else if(clazz == short.class) { return "S"; - } else if(clazz == int.class){ + } else if(clazz == int.class) { return "I"; - } else if(clazz == long.class){ + } else if(clazz == long.class) { return "J"; - } else if(clazz == float.class){ + } else if(clazz == float.class) { return "F"; - } else if(clazz == double.class){ + } else if(clazz == double.class) { return "D"; - } else if(clazz == char.class){ + } else if(clazz == char.class) { return "C"; } else { - return "L" + clazz.getName().replace(".", "/") + ";"; + return "L" + clazz.getName().replace('.', '/') + ";"; + } + } + + /** + * Generically and dynamically returns the array class type for the given class type. The dynamic equivalent of + * sending {@code String.class} and getting {@code String[].class}. Works with array types as well. + * @param clazz The class to convert to an array type. + * @return The array type of the input class. + */ + public static Class getArrayClassFromType(Class clazz) { + Objects.requireNonNull(clazz); + try { + return Class.forName("[" + getJVMName(clazz).replace('/', '.')); + } catch (ClassNotFoundException ex) { + // This cannot naturally happen, as we are simply creating an array type for a real type that has + // clearly already been loaded. + throw new NoClassDefFoundError(ex.getMessage()); } } - + /** - * Returns the common name of a class, as it would be typed out in source code. - * In general, this returns the same as Class.getName, but for arrays, it outputs - * [[Ljava.lang.Object; which would be better written as + * Returns the common name of a class, as it would be typed out in source code. In general, this returns the same as + * Class.getName, but for arrays, it outputs [[Ljava.lang.Object; which would be better written as * java.lang.Object[][]. + * * @param c - * @return + * @return */ - public static String getCommonName(Class c){ - if(!c.isArray()){ + public static String getCommonName(Class c) { + if(!c.isArray()) { //This is fine for non arrays. return c.getName(); } int arrayCount = c.getName().lastIndexOf("[") + 1; Class cc = c.getComponentType(); - while(cc.isArray()){ + while(cc.isArray()) { cc = cc.getComponentType(); } return cc.getName() + StringUtils.stringMultiply(arrayCount, "[]"); } - + /** - * Converts the binary name to the common name. For instance, - * for [Ljava/lang/Object;, java.lang.Object[] would be returned. The classes - * don't necessarily need to exist for this method to work. + * Converts the binary name to the common name. For instance, for [Ljava/lang/Object;, java.lang.Object[] would be + * returned. The classes don't necessarily need to exist for this method to work. + * * @param classname - * @return + * @return */ - public static String getCommonNameFromJVMName(String classname){ + public static String getCommonNameFromJVMName(String classname) { int arrayCount = classname.lastIndexOf("[") + 1; classname = classname.substring(arrayCount); //ZBSIJDFC - if("Z".equals(classname)){ + if("Z".equals(classname)) { classname = "boolean"; - } else if("B".equals(classname)){ + } else if("B".equals(classname)) { classname = "byte"; - } else if("S".equals(classname)){ + } else if("S".equals(classname)) { classname = "short"; - } else if("I".equals(classname)){ + } else if("I".equals(classname)) { classname = "int"; - } else if("J".equals(classname)){ + } else if("J".equals(classname)) { classname = "long"; - } else if("D".equals(classname)){ + } else if("D".equals(classname)) { classname = "double"; - } else if("F".equals(classname)){ + } else if("F".equals(classname)) { classname = "float"; - } else if("C".equals(classname)){ + } else if("C".equals(classname)) { classname = "char"; - } else if("V".equals(classname)){ + } else if("V".equals(classname)) { return "void"; //special case } else { - classname = classname.substring(1, classname.length() - 1).replace("/", ".").replace("$", "."); + classname = classname.substring(1, classname.length() - 1).replace('/', '.'); } return classname + StringUtils.stringMultiply(arrayCount, "[]"); } + + /** + * Returns a list of all classes that the specified class can be validly cast to. This includes all super classes, + * as well as all interfaces (and superclasses of those interfaces, etc) and java.lang.Object, as well as the class + * itself. + * + * @param c The class to search for. + * @return + */ + public static Set> getAllCastableClasses(Class c) { + Set> ret = new HashSet<>(); + getAllCastableClassesWithBlacklist(c, ret); + return ret; + } + + /** + * Private version of {@link #getAllCastableClasses(java.lang.Class)} + * + * @param c + * @param blacklist + * @return + */ + private static Set> getAllCastableClassesWithBlacklist(Class c, Set> blacklist) { + if(blacklist.contains(c)) { + return blacklist; + } + while(true) { + blacklist.add(c); + Class su = c.getSuperclass(); + if(su == null) { + return blacklist; + } + blacklist.add(su); + blacklist.addAll(getAllCastableClassesWithBlacklist(su, blacklist)); + for(Class iface : c.getInterfaces()) { + blacklist.add(iface); + blacklist.addAll(getAllCastableClassesWithBlacklist(iface, blacklist)); + } + c = su; + } + } + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/Common/DateUtils.java b/src/main/java/com/laytonsmith/PureUtilities/Common/DateUtils.java index 12b5d1f231..faf16a4804 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Common/DateUtils.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Common/DateUtils.java @@ -4,47 +4,49 @@ /** * - * + * */ public final class DateUtils { - - private DateUtils(){} - - /** - * Convenience notation for ParseCalendarNotation(name, null) - */ - public static String ParseCalendarNotation(String name) { - return ParseCalendarNotation(name, null); - } - /** - * Parses a calendar notation. The following patterns are replaced with the following: - * - * - * - * - * - * - * - *
%YYear
%MMonth
%DDay
%hHour
%mMinute
%sSecond
- * - * A generally standard format for human readable logs is: %Y-%M-%D %h:%m.%s - * @param name - * @param c - * @return - */ - public static String ParseCalendarNotation(String name, Calendar c) { - if (c == null) { - c = Calendar.getInstance(); - } - String year = String.format("%04d", c.get(Calendar.YEAR)); - String month = String.format("%02d", 1 + c.get(Calendar.MONTH)); //January is 0 - String day = String.format("%02d", c.get(Calendar.DAY_OF_MONTH)); - String hour = String.format("%02d", c.get(Calendar.HOUR)); - String minute = String.format("%02d", c.get(Calendar.MINUTE)); - String second = String.format("%02d", c.get(Calendar.SECOND)); - return name.replaceAll("%Y", year).replaceAll("%M", month) - .replaceAll("%D", day).replaceAll("%h", hour) - .replaceAll("%m", minute).replaceAll("%s", second); - } + private DateUtils() { + } + + /** + * Convenience notation for ParseCalendarNotation(name, null) + */ + public static String ParseCalendarNotation(String name) { + return ParseCalendarNotation(name, null); + } + + /** + * Parses a calendar notation. The following patterns are replaced with the following: + * + * + * + * + * + * + * + *
%YYear
%MMonth
%DDay
%hHour
%mMinute
%sSecond
+ * + * A generally standard format for human readable logs is: %Y-%M-%D %h:%m.%s + * + * @param name + * @param c + * @return + */ + public static String ParseCalendarNotation(String name, Calendar c) { + if(c == null) { + c = Calendar.getInstance(); + } + String year = String.format("%04d", c.get(Calendar.YEAR)); + String month = String.format("%02d", 1 + c.get(Calendar.MONTH)); //January is 0 + String day = String.format("%02d", c.get(Calendar.DAY_OF_MONTH)); + String hour = String.format("%02d", c.get(Calendar.HOUR)); + String minute = String.format("%02d", c.get(Calendar.MINUTE)); + String second = String.format("%02d", c.get(Calendar.SECOND)); + return name.replace("%Y", year).replace("%M", month) + .replace("%D", day).replace("%h", hour) + .replace("%m", minute).replace("%s", second); + } } diff --git a/src/main/java/com/laytonsmith/PureUtilities/Common/FileUtil.java b/src/main/java/com/laytonsmith/PureUtilities/Common/FileUtil.java index 92026aa40f..eb69cc3853 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Common/FileUtil.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Common/FileUtil.java @@ -1,5 +1,6 @@ package com.laytonsmith.PureUtilities.Common; +import com.laytonsmith.PureUtilities.GCUtil; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.ByteArrayInputStream; @@ -9,65 +10,90 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; import java.util.HashMap; import java.util.Map; import org.apache.commons.io.FileUtils; import org.mozilla.intl.chardet.nsDetector; -import org.mozilla.intl.chardet.nsICharsetDetectionObserver; import org.mozilla.intl.chardet.nsPSMDetector; /** - * + * */ -public class FileUtil { +public final class FileUtil { private FileUtil() { } public static final int OVERWRITE = 0; public static final int APPEND = 1; - - private static final Map fileLocks = new HashMap<>(); - private static final Map fileLockCounter = new HashMap<>(); + + private static final Map FILE_LOCKS = new HashMap<>(); + private static final Map FILE_LOCK_COUNTER = new HashMap<>(); + /** - * A more complicated mechanism is required to ensure global access across the JVM - * is synchronized, so file system accesses do not throw OverlappingFileLockExceptions. - * Though process safe, file locks are not thread safe -.- + * A more complicated mechanism is required to ensure global access across the JVM is synchronized, so file system + * accesses do not throw OverlappingFileLockExceptions. Though process safe, file locks are not thread safe -.- + * * @param file * @return - * @throws IOException + * @throws IOException */ - private static synchronized Object getLock(File file) throws IOException{ + private static synchronized Object getLock(File file) throws IOException { String canonical = file.getAbsoluteFile().getCanonicalPath(); - if(!fileLocks.containsKey(canonical)){ - fileLocks.put(canonical, new Object()); - fileLockCounter.put(canonical, 0); + if(!FILE_LOCKS.containsKey(canonical)) { + FILE_LOCKS.put(canonical, new Object()); + FILE_LOCK_COUNTER.put(canonical, 0); } - fileLockCounter.put(canonical, fileLockCounter.get(canonical) + 1); - return fileLocks.get(canonical); + FILE_LOCK_COUNTER.put(canonical, FILE_LOCK_COUNTER.get(canonical) + 1); + return FILE_LOCKS.get(canonical); } - - private static synchronized void freeLock(File file) throws IOException{ + + private static synchronized void freeLock(File file) throws IOException { String canonical = file.getAbsoluteFile().getCanonicalPath(); - fileLockCounter.put(canonical, fileLockCounter.get(canonical) - 1); - if(fileLockCounter.get(canonical) == 0){ - fileLockCounter.remove(canonical); - fileLocks.remove(canonical); + FILE_LOCK_COUNTER.put(canonical, FILE_LOCK_COUNTER.get(canonical) - 1); + if(FILE_LOCK_COUNTER.get(canonical) == 0) { + FILE_LOCK_COUNTER.remove(canonical); + FILE_LOCKS.remove(canonical); } } public static String read(File f) throws IOException { return org.apache.commons.io.FileUtils.readFileToString(f, "UTF-8"); -// try { -// return read(f, "UTF-8"); -// } catch (UnsupportedEncodingException ex) { -// throw new Error(ex); -// } } - public static String read(File file, String charset) throws IOException{ - return StreamUtils.GetString(readAsStream(file), charset); + /** + * Fully reads data from the given file channel, and returns it as a UTF-8 string. If the file is too large to + * fit in memory, an IOException is thrown. + * @param channel + * @return + * @throws IOException + */ + public static String read(FileChannel channel) throws IOException { + return new String(readData(channel), "UTF-8"); + } + + public static String read(File file, String charset) throws IOException { + return StreamUtils.GetString(readAsStream(file), charset); } - + + /** + * Fully reads data from the given file channel, and returns a byte array. If the file is too large to fit + * in memory, an IOException is thrown. + * @param channel + * @return + * @throws IOException + */ + public static byte[] readData(FileChannel channel) throws IOException { + if(channel.size() > Integer.MAX_VALUE) { + throw new IOException("File too large to read into memory. Use a stream reader."); + } + ByteBuffer buffer = ByteBuffer.allocate((int) channel.size()); + channel.read(buffer); + return buffer.array(); + } + /** * Returns the contents of this file as a string * @@ -79,26 +105,26 @@ public static InputStream readAsStream(File file) throws IOException { try { byte[] bytes = org.apache.commons.io.FileUtils.readFileToByteArray(file); return new ByteArrayInputStream(bytes); - } catch(IOException ex){ + } catch (IOException ex) { //Apache IO has an interesting feature/bug where the error message "Unexpected readed size" is thrown. //If this is the case, we're going to try using a normal java file connection. Other IOExceptions //are just going be rethrown. - if(ex.getMessage().startsWith("Unexpected readed size.")){ + if(ex.getMessage().startsWith("Unexpected readed size.")) { FileInputStream fis = new FileInputStream(file); - try{ - byte [] bytes = StreamUtils.GetBytes(fis); + try { + byte[] bytes = StreamUtils.GetBytes(fis); return new ByteArrayInputStream(bytes); } finally { //JVM bug with files fis.close(); fis = null; - System.gc(); + GCUtil.BlockUntilGC(); } } else { throw ex; } } -// try{ +// try { // synchronized (getLock(file)) { // RandomAccessFile raf = new RandomAccessFile(file, "rw"); // FileLock lock = null; @@ -108,125 +134,130 @@ public static InputStream readAsStream(File file) throws IOException { // raf.getChannel().read(buffer); // return new ByteArrayInputStream(buffer.array()); // } finally { -// if (lock != null) { +// if(lock != null) { // lock.release(); // } // raf.close(); // } // } // } finally { -// freeLock(file); +// freeLock(file); // } -// FileInputStream fis = new FileInputStream(f); -// try{ +// FileInputStream fis = new FileInputStream(f); +// try { // return StreamUtils.GetString(fis, charset); -// } finally { -// fis.close(); -// fis = null; -// System.gc(); -// } +// } finally { +// fis.close(); +// fis = null; +// System.gc(); +// } } - + /** * Works the same as write(String, File, int, false). + * * @param data * @param file * @param mode - * @throws IOException + * @throws IOException */ - public static void write(String data, File file, int mode) throws IOException{ + public static void write(String data, File file, int mode) throws IOException { write(data, file, mode, false); } - public static void write(String data, File file, int mode, boolean create) throws IOException{ + public static void write(String data, File file, int mode, boolean create) throws IOException { write(data.getBytes("UTF-8"), file, mode, create); } + /** - * Writes out a String to the given file, either appending or - * overwriting, depending on the selected mode. If create is true, - * will attempt to create the file and parent directories if need be. + * Writes out string data as UTF-8 to the given file, either appending or overwriting, depending on the selected + * mode. If create + * is true, will attempt to create the file and parent directories if need be. + * + * @param data The string to write to the file + * @param file The File to write to + * @param mode The mode in which to write the file + * @param create If true, will create the parent directories + * @throws IOException If the File cannot be written to + */ + public static void write(String data, File file, FileWriteMode mode, boolean create) throws IOException { + write(data.getBytes("UTF-8"), file, mode, create); + } + + /** + * Writes out byte data to the given file, either appending or overwriting, depending on the selected mode. If create + * is true, will attempt to create the file and parent directories if need be. + * + * @param data The data to write to the file + * @param file The File to write to + * @param mode The mode in which to write the file + * @param create If true, will create the parent directories + * @throws IOException If the File cannot be written to + */ + public static void write(byte[] data, File file, FileWriteMode mode, boolean create) throws IOException { + if(mode == FileWriteMode.SAFE_WRITE) { + if(file.exists()) { + throw new IOException("Cannot create file, SAFE_WRITE set, and file already exists [" + file + "]"); + } + mode = FileWriteMode.OVERWRITE; + } + if(mode == FileWriteMode.OVERWRITE) { + write(data, file, OVERWRITE, create); + } else if(mode == FileWriteMode.APPEND) { + write(data, file, APPEND, create); + } else { + throw new Error("Unaccounted for FileWriteMode"); + } + } + + /** + * Writes out byte data to the given file, either appending or overwriting, depending on the selected mode. If create + * is true, will attempt to create the file and parent directories if need be. * * @param data The string to write to the file * @param file The File to write to * @param mode Either OVERWRITE or APPEND + * @param create If true, will create the parent directories * @throws IOException If the File f cannot be written to */ public static void write(byte[] data, File file, int mode, boolean create) throws IOException { boolean append; - if (mode == OVERWRITE) { - append = false; - } else { - append = true; - } - if(create && !file.exists()){ - if(file.getAbsoluteFile().getParentFile() != null){ + append = mode != OVERWRITE; + if(create && !file.exists()) { + if(file.getAbsoluteFile().getParentFile() != null) { file.getAbsoluteFile().getParentFile().mkdirs(); } file.getAbsoluteFile().createNewFile(); } FileUtils.writeByteArrayToFile(file, data, append); -// try{ -// synchronized (getLock(file)) { -// int sleepTime = 0; -// int sleepTimes = 0; -// loop: while(true){ -// try { -// Thread.sleep(sleepTime); -// sleepTime += 10; -// sleepTimes++; -// } catch (InterruptedException ex) { -// // -// } -// RandomAccessFile raf = new RandomAccessFile(file, "rw"); -// FileLock lock = null; -// try { -// lock = raf.getChannel().lock(); -// //Clear out the file -// if (!append) { -// raf.getChannel().truncate(0); -// } else { -// raf.seek(raf.length()); -// } -// //Write out the data -// MappedByteBuffer buf = raf.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, data.length); -// buf.put(data); -// buf.force(); -// //We assume it worked at this point, so let's break; -// break loop; -// //raf.getChannel().write(ByteBuffer.wrap(data)); -// } catch(IOException e){ -// //If we get this dumb message, we're on windows. We'll try again here shortly, -// //but we don't want to bother the user with this exception if we can help it. -// //http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6354433 -// if(!"The requested operation cannot be performed on a file with a user-mapped section open" -// .equals(e.getMessage())){ -// throw e; -// } -// if(sleepTimes > 10){ -// //Eh. Gotta give up some time. -// throw e; -// } -// } finally { -// if (lock != null) { -// lock.release(); -// } -// raf.close(); -// raf = null; -// System.gc(); -// } -// } -// } -// } finally { -// freeLock(file); -// } -// FileWriter fw = new FileWriter(f, append); -// fw.write(s); -// fw.close(); } /** - * This function writes out a String to a file, overwriting it if it - * already exists + * Writes the given string to the existing file channel. UTF-8 encoding is assumed. + * @param fileChannel + * @param data + * @throws IOException + */ + public static void write(FileChannel fileChannel, String data) throws IOException { + try { + write(fileChannel, data.getBytes("UTF-8")); + } catch (UnsupportedEncodingException ex) { + throw new Error(ex); + } + } + + /** + * Writes the given data to the existing file channel. + * @param fileChannel + * @param data + * @throws IOException + */ + public static void write(FileChannel fileChannel, byte[] data) throws IOException { + fileChannel.write(ByteBuffer.wrap(data)); + } + + /** + * This function writes out a String to a file, overwriting it if it already exists * * @param s The string to write to the file * @param f The File to write to @@ -235,20 +266,18 @@ public static void write(byte[] data, File file, int mode, boolean create) throw public static void write(String s, File f) throws IOException { write(s, f, OVERWRITE); } - + /** * Shorthand for write(s, f, OVERWRITE, create) */ - public static void write(String s, File f, boolean create) throws IOException{ + public static void write(String s, File f, boolean create) throws IOException { write(s, f, OVERWRITE, create); } /** - * Copies a file from one location to another. If overwrite is null, - * prompts the user on the console if the file already exists. If - * overwrite is false, the operation throws an exception if the file - * already exists. If overwrite is true, the file is overwritten without - * prompting if it already exists. + * Copies a file from one location to another. If overwrite is null, prompts the user on the console if the file + * already exists. If overwrite is false, the operation throws an exception if the file already exists. If overwrite + * is true, the file is overwritten without prompting if it already exists. * * @param fromFile * @param toFile @@ -256,62 +285,63 @@ public static void write(String s, File f, boolean create) throws IOException{ * @throws IOException */ public static void copy(File fromFile, File toFile, Boolean overwrite) - throws IOException { + throws IOException { - if (!fromFile.exists()) { + if(!fromFile.exists()) { throw new IOException("FileCopy: " + "no such source file: " - + fromFile.getName()); + + fromFile.getName()); } - if (!fromFile.isFile()) { + if(!fromFile.isFile()) { throw new IOException("FileCopy: " + "can't copy directory: " - + fromFile.getName()); + + fromFile.getName()); } - if (!fromFile.canRead()) { + if(!fromFile.canRead()) { throw new IOException("FileCopy: " + "source file is unreadable: " - + fromFile.getName()); + + fromFile.getName()); } - if (toFile.isDirectory()) { + if(toFile.isDirectory()) { toFile = new File(toFile, fromFile.getName()); } - if (toFile.exists()) { - if (!toFile.canWrite()) { + if(toFile.exists()) { + if(!toFile.canWrite()) { throw new IOException("FileCopy: " - + "destination file is unwriteable: " + toFile.getName()); + + "destination file is unwriteable: " + toFile.getName()); } String response = null; - if (overwrite == null) { - System.out.print("Overwrite existing file " + toFile.getName() - + "? (Y/N): "); - System.out.flush(); + if(overwrite == null) { + StreamUtils.GetSystemOut().print("Overwrite existing file " + toFile.getName() + + "? (Y/N): "); + StreamUtils.GetSystemOut().flush(); BufferedReader in = new BufferedReader(new InputStreamReader( - System.in)); + System.in)); response = in.readLine(); } - if ((overwrite != null && overwrite == false) || (!response.equals("Y") && !response.equals("y"))) { + if((overwrite != null && overwrite == false) + || (response != null && !response.equals("Y") && !response.equals("y"))) { throw new IOException("FileCopy: " - + "existing file was not overwritten."); + + "existing file was not overwritten."); } //overwrite being true falls through } else { String parent = toFile.getParent(); - if (parent == null) { + if(parent == null) { parent = System.getProperty("user.dir"); } File dir = new File(parent); - if (!dir.exists()) { + if(!dir.exists()) { throw new IOException("FileCopy: " - + "destination directory doesn't exist: " + parent); + + "destination directory doesn't exist: " + parent); } - if (dir.isFile()) { + if(dir.isFile()) { throw new IOException("FileCopy: " - + "destination is not a directory: " + parent); + + "destination is not a directory: " + parent); } - if (!dir.canWrite()) { + if(!dir.canWrite()) { throw new IOException("FileCopy: " - + "destination directory is unwriteable: " + parent); + + "destination directory is unwriteable: " + parent); } } @@ -324,18 +354,18 @@ public static void copy(File fromFile, File toFile, Boolean overwrite) // byte[] buffer = new byte[4096]; // int bytesRead; // -// while ((bytesRead = from.read(buffer)) != -1) { +// while((bytesRead = from.read(buffer)) != -1) { // to.write(buffer, 0, bytesRead); // write // } // } finally { -// if (from != null) { +// if(from != null) { // try { // from.close(); // } catch (IOException e) { // ; // } // } -// if (to != null) { +// if(to != null) { // try { // to.close(); // } catch (IOException e) { @@ -346,8 +376,7 @@ public static void copy(File fromFile, File toFile, Boolean overwrite) } /** - * Moves a file from one location to another. Assuming no exception is - * thrown, always returns true. + * Moves a file from one location to another. Assuming no exception is thrown, always returns true. * * @param from * @param to @@ -355,7 +384,7 @@ public static void copy(File fromFile, File toFile, Boolean overwrite) public static boolean move(File from, File to) throws IOException { FileUtils.moveFile(from, to); return true; -// try{ +// try { // synchronized(getLock(to)){ // return from.renameTo(to); // } @@ -365,9 +394,8 @@ public static boolean move(File from, File to) throws IOException { } /** - * Recursively deletes a file/folder structure. True is returned if ALL - * files were deleted. If it returns false, some or none of the files - * may have been deleted. + * Recursively deletes a file/folder structure. True is returned if ALL files were deleted. If it returns false, + * some or none of the files may have been deleted. * * @param file * @return @@ -378,15 +406,15 @@ public static boolean recursiveDelete(File file) { //collection happens, the system will still //have file locks on the file, even if the Streams //were properly closed. - System.gc(); - if (file.isDirectory()) { + GCUtil.BlockUntilGC(); + if(file.isDirectory()) { boolean ret = true; - for (File f : file.listFiles()) { - if (!recursiveDelete(f)) { + for(File f : file.listFiles()) { + if(!recursiveDelete(f)) { ret = false; } } - if (!file.delete()) { + if(!file.delete()) { ret = false; } return ret; @@ -394,46 +422,87 @@ public static boolean recursiveDelete(File file) { return file.delete(); } } - + + public static interface FileHandler { + /** + * Handles a file. + * @param f + * @throws java.io.IOException If the some operation on the file errored. + */ + void handle(File f) throws IOException; + } /** - * Returns the most likely character encoding for this file. The default is - * "ASCII" and is probably the most common. + * Recursively iterates through all files in this directory and all subdirectories. It takes a callback, which is + * sent each file, in turn. The parent directory (the one passed in) is also sent to the handler. The callback + * may throw an IOException, which stops further processing, and is rethrown. + * @param file The file to start with + * @param handler The handler to use to process the file + * @throws java.io.IOException If the underlying handler throws an IOException. If the handler does not throw + * an IOException, then this function will never otherwise do so. (SecurityExceptions may be thrown, however.) + */ + public static void recursiveFind(File file, FileHandler handler) throws IOException { + handler.handle(file); + if(file.isDirectory()) { + for(File f : file.listFiles()) { + recursiveFind(f, handler); + } + } + } + + /** + * Recursively deletes a file/folder structure on exit. This will not delete the file immediately, but it is not + * an error to delete the file first. + * + * @param file + */ + public static void recursiveDeleteOnExit(File file) { + System.gc(); + if(file.isDirectory()) { + for(File f : file.listFiles()) { + recursiveDelete(f); + } + file.deleteOnExit(); + } else { + file.deleteOnExit(); + } + } + + /** + * Returns the most likely character encoding for this file. The default is "ASCII" and is probably the most common. + * Note that ASCII and UTF-8 are the same format when the character set is just the 8 byte characters. + * * @param file * @return - * @throws IOException + * @throws IOException */ public static String getFileCharset(File file) throws IOException { int lang = nsPSMDetector.ALL; nsDetector det = new nsDetector(lang); final MutableObject result = new MutableObject("ASCII"); - det.Init(new nsICharsetDetectionObserver() { - - @Override - public void Notify(String charset) { - result.setObject(charset); - } + det.Init((String charset) -> { + result.setObject(charset); }); - + BufferedInputStream imp = null; - try{ + try { imp = new BufferedInputStream(new FileInputStream(file)); byte[] buf = new byte[1024]; int len; boolean done = false; boolean isAscii = true; - - while((len=imp.read(buf, 0, buf.length)) != -1){ - if(isAscii){ + + while((len = imp.read(buf, 0, buf.length)) != -1) { + if(isAscii) { isAscii = det.isAscii(buf, len); } - if(!isAscii && !done){ + if(!isAscii && !done) { done = det.DoIt(buf, len, false); } } det.DataEnd(); - return (String)result.getObject(); + return (String) result.getObject(); } finally { - if(imp != null){ + if(imp != null) { imp.close(); } } diff --git a/src/main/java/com/laytonsmith/PureUtilities/Common/FileWriteMode.java b/src/main/java/com/laytonsmith/PureUtilities/Common/FileWriteMode.java new file mode 100644 index 0000000000..dfabc779ff --- /dev/null +++ b/src/main/java/com/laytonsmith/PureUtilities/Common/FileWriteMode.java @@ -0,0 +1,19 @@ +package com.laytonsmith.PureUtilities.Common; + +/** + * + */ +public enum FileWriteMode { + /** + * Replaces any existing file with the new contents (or simply creates a new file if it doesn't already exist). + */ + OVERWRITE, + /** + * Appends to the existing file, or if one does not exist, simply creates the file with the new content. + */ + APPEND, + /** + * Only writes the file if it doesn't already exist. + */ + SAFE_WRITE +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/Common/GNUErrorMessageFormat.java b/src/main/java/com/laytonsmith/PureUtilities/Common/GNUErrorMessageFormat.java new file mode 100644 index 0000000000..b5dff8d9ce --- /dev/null +++ b/src/main/java/com/laytonsmith/PureUtilities/Common/GNUErrorMessageFormat.java @@ -0,0 +1,239 @@ +package com.laytonsmith.PureUtilities.Common; + +import java.util.Objects; + +/** + * This class represents a GNU error format message. It supports all of the following message formats: + * + *

    + *
  • sourcefile:lineno:column: message
  • + *
  • sourcefile:lineno: message
  • + *
  • sourcefile:lineno.column: message
  • + *
  • sourcefile:line1.column1-line2.column2: message
  • + *
  • sourcefile:line1.column1-column2: message
  • + *
  • sourcefile:line1-line2: message
  • + *
+ * + * Currently, explicitly not supported are the following otherwise valid GNU error message formats: + *
    + *
  • file1:line1.column1-file2:line2.column2: message
  • + *
  • program:sourcefile:lineno: message
  • + *
  • program: message
  • + *
  • program:sourcefile:lineno:column: message
  • + *
+ * + * Once the object is created, the error message is parsed, and the constituent parts are more easily readable from the + * object's methods. + * + * @author cailin + */ +public class GNUErrorMessageFormat { + + public static enum MessageType { + ERROR, WARNING, INFO, UNKNOWN + } + + private final String messageLine; + + private String file; + private int fromLine = -1; + private int fromColumn = 0; + private int toLine = -1; + private int toColumn = 0; + private MessageType probableMessageType = MessageType.UNKNOWN; + private String message; + private boolean parsed = false; + + /** + * Constructs a new GNUErrorMessageFormat object from the given error message line. + * + * @param messageLine the message to parse + */ + public GNUErrorMessageFormat(String messageLine) { + this.messageLine = messageLine; + } + + /** + * Actually parses the message. This is called automatically by all the other methods in this class, but this allows + * you to check immediately to ensure that the error is correctly parseable. + * + * @throws IllegalArgumentException if the message is not parseable. + * @return This object, for easier chaining + */ + public GNUErrorMessageFormat parse() throws IllegalArgumentException { + if(parsed) { + return this; + } + parsed = true; + String[] errorParts = messageLine.split(" ", 2); + message = errorParts[1].trim(); + errorParts = errorParts[0].split(":", -1); + if("".equals(errorParts[errorParts.length - 1])) { + errorParts = ArrayUtils.slice(errorParts, 0, errorParts.length - 2); + } + if(errorParts.length > 3) { + throw new IllegalArgumentException("Not a supported error message format"); + } + // These are all the formats we need to support + // sourcefile:lineno:column: message + // sourcefile:lineno: message + // sourcefile:lineno.column: message + // sourcefile:line1.column1-line2.column2: message + // sourcefile:line1.column1-column2: message + // sourcefile:line1-line2: message + switch(errorParts.length) { + case 2: + file = errorParts[0]; + String middle = errorParts[1]; + if(middle.matches("\\d+")) { + fromLine = Integer.parseInt(middle); + } else if(middle.matches("\\d+\\.\\d+")) { + String[] s = middle.split("\\."); + fromLine = Integer.parseInt(s[0]); + fromColumn = toColumn = Integer.parseInt(s[1]); + } else if(middle.matches("\\d+\\.\\d+-\\d+\\.\\d+")) { + String[] s = middle.split("-"); + String s0[] = s[0].split("\\."); + String s1[] = s[1].split("\\."); + fromLine = Integer.parseInt(s0[0]); + fromColumn = Integer.parseInt(s0[1]); + toLine = Integer.parseInt(s1[0]); + toColumn = Integer.parseInt(s1[1]); + } else if(middle.matches("\\d+\\.\\d+-\\d+")) { + String[] s = middle.split("\\."); + String[] c = s[1].split("-"); + fromLine = toLine = Integer.parseInt(s[0]); + fromColumn = Integer.parseInt(c[0]); + toColumn = Integer.parseInt(c[1]); + } else if(middle.matches("\\d+-\\d+")) { + String[] s = middle.split("-"); + fromLine = Integer.parseInt(s[0]); + toLine = Integer.parseInt(s[1]); + } else { + throw new IllegalArgumentException("Could not parse message"); + } + break; + case 3: + file = errorParts[0]; + fromLine = Integer.parseInt(errorParts[1]); + fromColumn = toColumn = Integer.parseInt(errorParts[2]); + break; + } + // If it contains more than one, then the most severe takes priority anyways + if(StringUtils.containsIgnoreCase(message, "error")) { + probableMessageType = MessageType.ERROR; + } else if(StringUtils.containsIgnoreCase(message, "warning")) { + probableMessageType = MessageType.WARNING; + } else if(StringUtils.containsIgnoreCase(message, "info")) { + probableMessageType = MessageType.INFO; + } + if(toLine == -1) { + toLine = fromLine; + } + if(toColumn == -1) { + toColumn = fromColumn; + } + return this; + } + + /** + * The file identifier. + * + * @return + */ + public String file() { + parse(); + return file; + } + + /** + * The beginning line number of the error. This value is 1 indexed. + * + * @return + */ + public int fromLine() { + parse(); + return fromLine; + } + + /** + * The beginning column number of the error + * + * @return + */ + public int fromColumn() { + parse(); + return fromColumn; + } + + /** + * The ending line of the error. This value is 1 indexed. + * + * @return + */ + public int toLine() { + parse(); + return toLine; + } + + /** + * The ending column number of the error. + * + * @return + */ + public int toColumn() { + parse(); + return toColumn; + } + + /** + * Returns the probable message type. This is based on whether or not the message contains certain keywords. It may + * guess wrong, however, you can ignore this parameter if you wish to do custom analyzation of the message. + * + * @return + */ + public MessageType messageType() { + parse(); + return probableMessageType; + } + + /** + * Returns the message itself. + * + * @return + */ + public String message() { + parse(); + return message; + } + + /** + * Returns the original error message line that was used to construct this object. + * + * @return + */ + public String getOriginalErrorLine() { + return messageLine; + } + + @Override + public boolean equals(Object obj) { + if(obj instanceof GNUErrorMessageFormat) { + return this.messageLine.equals(((GNUErrorMessageFormat) obj).messageLine); + } + return false; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 17 * hash + Objects.hashCode(this.messageLine); + return hash; + } + + @Override + public String toString() { + return file + ":" + fromLine + "." + fromColumn + "-" + toLine + "." + toColumn + ": " + message; + } + +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/Common/HTMLUtils.java b/src/main/java/com/laytonsmith/PureUtilities/Common/HTMLUtils.java index c5708c5fde..40de497f7d 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Common/HTMLUtils.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Common/HTMLUtils.java @@ -1,39 +1,51 @@ package com.laytonsmith.PureUtilities.Common; /** - * + * */ public class HTMLUtils { - + /** - * Given a raw string, escapes html special characters. For instance, turning - * < into &lt; + * Given a raw string, escapes html special characters. For instance, turning < into &lt; * - * - * @param raw - * @return + * @ + * + * + * param raw + * @return */ - public static String escapeHTML(String raw){ + public static String escapeHTML(String raw) { return raw.replace("&", "&") .replace("<", "<") .replace(">", ">") .replace("\"", """) .replace("'", "'"); } - + /** - * Given escaped html characters, unescapes them. For instance, turning - * &lt; into < + * Given escaped html characters, unescapes them. For instance, turning &lt; into < * - * - * @param html - * @return + * @param ht + * + * ml + * + * @return */ - public static String unescapeHTML(String html){ + public static String unescapeHTML(String html) { return html.replace("'", "'") .replace(""", "\"") .replace(">", ">") .replace("<", "<") + .replace(" ", " ") + .replace("(", "(") + .replace(")", ")") + .replace("{", "{") + .replace("}", "}") + .replace("[", "[") + .replace("]", "]") + .replace("=", "=") + .replace("#", "#") + .replace("*", "*") .replace("&", "&"); } } diff --git a/src/main/java/com/laytonsmith/PureUtilities/Common/LogUtils.java b/src/main/java/com/laytonsmith/PureUtilities/Common/LogUtils.java index b90894cd56..065ee3f44f 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Common/LogUtils.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Common/LogUtils.java @@ -8,69 +8,74 @@ /** * - * + * */ public final class LogUtils { - - private LogUtils(){} - - /** - * Get the system's line endings - * @return - */ - public static String LF() { - return System.getProperty("line.separator"); - } - - /** - * Logs a message to the given file, appending the current timestamp. - * @param filename - * @param message - * @throws IOException - */ - public static void LoggerMessage(String filename, String message) throws IOException{ - String timestamp = DateUtils.ParseCalendarNotation("%Y-%M-%D %h:%m.%s - "); - QuickAppend(GetLog(filename), timestamp + message + LF()); - } - - /** - * Logs a message to the given file, and prints out to the given logger as well. - * @param filename - * @param message - * @param logger - * @throws IOException - */ - public static synchronized void LoggerMessage(String filename, String message, Logger logger) throws IOException { - if (logger != null) { - logger.log(Level.INFO, message); - } - LoggerMessage(filename, message); - } - - /** - * Logs a message to the given file, and prints the message to the given PrintStream, for instance, System.out. - * @param filename - * @param message - * @param out - * @throws IOException - */ - public static synchronized void LoggerMessage(String filename, String message, PrintStream out) throws IOException{ - if(out != null){ - out.println(message); - } - LoggerMessage(filename, message); - } - public static void QuickAppend(FileWriter f, String message) throws IOException { - f.append(message); - f.flush(); - } - - private static FileWriter GetLog(String filename) throws IOException{ - return new FileWriter(filename, true); - } - - public static void Log(String filename, String message) throws IOException { - QuickAppend(GetLog(filename), message + LF()); - } + private LogUtils() { + } + + /** + * Get the system's line endings + * + * @return + */ + public static String LF() { + return System.getProperty("line.separator"); + } + + /** + * Logs a message to the given file, appending the current timestamp. + * + * @param filename + * @param message + * @throws IOException + */ + public static void LoggerMessage(String filename, String message) throws IOException { + String timestamp = DateUtils.ParseCalendarNotation("%Y-%M-%D %h:%m.%s - "); + QuickAppend(GetLog(filename), timestamp + message + LF()); + } + + /** + * Logs a message to the given file, and prints out to the given logger as well. + * + * @param filename + * @param message + * @param logger + * @throws IOException + */ + public static synchronized void LoggerMessage(String filename, String message, Logger logger) throws IOException { + if(logger != null) { + logger.log(Level.INFO, message); + } + LoggerMessage(filename, message); + } + + /** + * Logs a message to the given file, and prints the message to the given PrintStream, for instance, System.out. + * + * @param filename + * @param message + * @param out + * @throws IOException + */ + public static synchronized void LoggerMessage(String filename, String message, PrintStream out) throws IOException { + if(out != null) { + out.println(message); + } + LoggerMessage(filename, message); + } + + public static void QuickAppend(FileWriter f, String message) throws IOException { + f.append(message); + f.flush(); + } + + private static FileWriter GetLog(String filename) throws IOException { + return new FileWriter(filename, true); + } + + public static void Log(String filename, String message) throws IOException { + QuickAppend(GetLog(filename), message + LF()); + } } diff --git a/src/main/java/com/laytonsmith/PureUtilities/Common/LogicUtils.java b/src/main/java/com/laytonsmith/PureUtilities/Common/LogicUtils.java index 3a70fcca94..3c29963f59 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Common/LogicUtils.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Common/LogicUtils.java @@ -2,57 +2,61 @@ /** * Provides various functions for making some logic statements easier to read. - * + * */ public class LogicUtils { - - public static Compare get(Object obj){ + + public static Compare get(Object obj) { return new Compare(obj); } - public static class Compare{ + + public static class Compare { + Object obj; - Compare(Object obj){ + + Compare(Object obj) { this.obj = obj; } - + /** - * Returns true if the element is equals to ANY value in the list. - * LogicUtils.get(a).equalsAny(b, c, d) is equivalent to a == b || a == c || a == d + * Returns true if the element is equals to ANY value in the list. LogicUtils.get(a).equalsAny(b, c, d) is + * equivalent to a == b || a == c || a == d + * * @param o - * @return + * @return */ - public boolean equalsAny(Object ... o){ - if(obj == null){ - for(Object oo : o){ - if(oo == null){ + public boolean equalsAny(Object... o) { + if(obj == null) { + for(Object oo : o) { + if(oo == null) { return true; } } return false; } - for(Object oo : o){ - if(obj.equals(oo)){ + for(Object oo : o) { + if(obj.equals(oo)) { return true; } } return false; } - + /** - * Returns true if the element is equal to NONE of the values on the list. - * LogicUtils.get(a).equalsNone(b, c, d) is equivalent to a != b && a != c && a != d + * Returns true if the element is equal to NONE of the values on the list. LogicUtils.get(a).equalsNone(b, c, d) + * is equivalent to a != b && a != c && a != d */ - public boolean equalsNone(Object ... o){ - if(obj == null){ - for(Object oo : o){ - if(oo == null){ + public boolean equalsNone(Object... o) { + if(obj == null) { + for(Object oo : o) { + if(oo == null) { return false; } } return true; } - for(Object oo : o){ - if(obj.equals(oo)){ + for(Object oo : o) { + if(obj.equals(oo)) { return false; } } @@ -61,29 +65,30 @@ public boolean equalsNone(Object ... o){ /** * Returns true if the elements are equal + * * @param obj - * @return + * @return */ @Override public boolean equals(Object o) { - if(!(o instanceof Compare)){ + if(!(o instanceof Compare)) { return false; } - if(obj == null){ + if(obj == null) { return obj == o; } else { return obj.equals(o); } - } + } @Override public int hashCode() { - if(obj == null){ + if(obj == null) { return 0; } else { return obj.hashCode(); } } - + } } diff --git a/src/main/java/com/laytonsmith/PureUtilities/Common/Misc.java b/src/main/java/com/laytonsmith/PureUtilities/Common/Misc.java deleted file mode 100644 index 742aa8cf5e..0000000000 --- a/src/main/java/com/laytonsmith/PureUtilities/Common/Misc.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.laytonsmith.PureUtilities.Common; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.io.Writer; - -/** - * Completely standalone generic functions live here. - * - */ -public final class Misc { - - private Misc(){} - - public static String GetStacktrace(Throwable t){ - final Writer result = new StringWriter(); - final PrintWriter printWriter = new PrintWriter(result); - t.printStackTrace(printWriter); - return result.toString(); - } -} diff --git a/src/main/java/com/laytonsmith/PureUtilities/Common/MutableObject.java b/src/main/java/com/laytonsmith/PureUtilities/Common/MutableObject.java index 8371844e51..7a7abd3900 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Common/MutableObject.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Common/MutableObject.java @@ -1,49 +1,52 @@ package com.laytonsmith.PureUtilities.Common; /** - * This class wraps an object, which is mutable. Useful for places where - * you must be able to manipulate a final variable, for instance, in anonymous - * classes. The common Object methods are forwarded to the underlying object, + * This class wraps an object, which is mutable. Useful for places where you must be able to manipulate a final + * variable, for instance, in anonymous classes. The common Object methods are forwarded to the underlying object, * unless it is null, in which case various defaults are returned. */ public final class MutableObject { + private T obj = null; /** * Constructs a new MutableObject, which is null. */ - public MutableObject(){ + public MutableObject() { } /** * Constructs a new MutableObject, wrapping the specified object. + * * @param obj */ - public MutableObject(T obj){ + public MutableObject(T obj) { setObject(obj); } /** * Sets the underlying object. + * * @param obj */ - public void setObject(T obj){ + public void setObject(T obj) { this.obj = obj; } /** * Gets the underlying object. + * * @return */ - public T getObject(){ + public T getObject() { return obj; } @Override @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") public boolean equals(Object obj) { - if(this.obj == null){ + if(this.obj == null) { return obj == null; } else { return this.obj.equals(obj); @@ -52,7 +55,7 @@ public boolean equals(Object obj) { @Override public int hashCode() { - if(this.obj == null){ + if(this.obj == null) { return 0; } else { return this.obj.hashCode(); @@ -61,7 +64,7 @@ public int hashCode() { @Override public String toString() { - if(this.obj == null){ + if(this.obj == null) { return "null"; } else { return this.obj.toString(); diff --git a/src/main/java/com/laytonsmith/PureUtilities/Common/OSUtils.java b/src/main/java/com/laytonsmith/PureUtilities/Common/OSUtils.java index dcf788a3c5..13d5d3b20e 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Common/OSUtils.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Common/OSUtils.java @@ -1,38 +1,273 @@ package com.laytonsmith.PureUtilities.Common; +import com.laytonsmith.PureUtilities.CommandExecutor; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + /** * This class contains utilities that help with OS specific tasks. - * + * */ public class OSUtils { - public static enum OS{ + + /** + * The bit depth of the OS. + */ + public static enum BitDepth { + /** + * The OS is a 32 bit architecture. + */ + B32, + /** + * The OS is a 64 bit architecture. + */ + B64 + } + + public static enum OS { WINDOWS, MAC, LINUX, SOLARIS, - UNKNOWN + UNKNOWN; + + /** + * Returns true if this is {@link #WINDOWS} + * @return + */ + public boolean isWindows() { + return this == WINDOWS; + } + + /** + * Returns true if this is {@link #MAC} + * @return + */ + public boolean isMac() { + return this == MAC; + } + + /** + * Returns true if this is {@link #LINUX} + * @return + */ + public boolean isLinux() { + return this == LINUX; + } + + /** + * Returns true if this is {@link #SOLARIS} + * @return + */ + public boolean isSolaris() { + return this == SOLARIS; + } + + /** + * Returns true if this is {@link #UNKNOWN} + * @return + */ + public boolean isUnknown() { + return this == UNKNOWN; + } + + /** + * Returns true if this is a strict UNIX implementation, that is, {@link #MAC} or {@link #SOLARIS} + * + * @return + */ + public boolean isUnix() { + return this == MAC || this == SOLARIS; + } + + /** + * Returns true if this {@link #isUnix()} returns true, or if this is {@link #LINUX}. This is a less strict + * category than {@link #isUnix()}, because for most purposes, Linux is UNIX compatible, but this is not + * strictly the case. Depending on what you're doing, you may need to differentiate between strict UNIX OSes + * or not, so if this matters, you'll need to do a more granular check. + * @return + */ + public boolean isUnixLike() { + return isUnix() || this == LINUX; + } } - + /** * Returns the OS that is currently running - * @return + * + * @return */ - public static OS GetOS(){ + public static OS GetOS() { String os = System.getProperty("os.name").toLowerCase(); - if(os.contains("win")){ + if(os.contains("win")) { return OS.WINDOWS; - } else if(os.contains("mac")){ + } else if(os.contains("mac")) { return OS.MAC; - } else if(os.contains("nix") || os.contains("nux")){ + } else if(os.contains("nix") || os.contains("nux")) { return OS.LINUX; - } else if(os.contains("sunos")){ + } else if(os.contains("sunos")) { return OS.SOLARIS; } else { return OS.UNKNOWN; } } - - public static String GetLineEnding(){ + + public static String GetLineEnding() { return System.getProperty("line.separator"); } + + /** + * Returns the process id of the currently running Java process. In Java versions < 9, this actually just a best + * effort, and it is possible in some implementations of Java, this will cause an Exception. However, if Java 9 or + * greater is running, this will be an authoritative response. + * @return + * @throws UnsupportedOperationException If the java implementation does not have correct support for this + * operation. + */ + @SuppressWarnings("checkstyle:localvariablename") + public static long GetMyPid() throws UnsupportedOperationException { + return ProcessHandle.current().pid(); + } + + public static class ProcessInfo { + private final long pid; + private final String command; + + ProcessInfo(long pid, String command) { + this.pid = pid; + this.command = command; + } + + /** + * Returns the process id of the given process. + * @return + */ + public long getPid() { + return pid; + } + + /** + * Returns the process name, i.e. "nginx" on linux or "cmd.exe" on windows. + * @return + */ + public String getCommand() { + return command; + } + + @Override + public String toString() { + return pid + ": " + command; + } + + } + + /** + * Returns a list of processes running on this system. + * @return + */ + public static List GetRunningProcesses() { + try { + if(GetOS().isWindows()) { + return GetRunningProcessesWindows(); + } else if(GetOS().isUnixLike()) { + return GetRunningProcessesUnix(); + } else { + throw new UnsupportedOperationException("Unsupported OS"); + } + } catch (IOException | InterruptedException ex) { + throw new RuntimeException(ex); + } + } + + public static BitDepth GetOSBitDepth() { + if(GetOS().isWindows()) { + // Windows lies if we're running 32 bit java on a 64 bit architecture, but this really isn't + // what we need to know, so "thanks". + String arch = System.getenv("PROCESSOR_ARCHITECTURE"); + String wow64Arch = System.getenv("PROCESSOR_ARCHITEW6432"); + + return arch != null && arch.endsWith("64") + || wow64Arch != null && wow64Arch.endsWith("64") + ? BitDepth.B64 : BitDepth.B32; + } else { + return System.getProperty("os.arch").endsWith("64") ? BitDepth.B64 : BitDepth.B32; + } + } + + private static List GetRunningProcessesWindows() throws InterruptedException, IOException { + List list = new ArrayList<>(); + String cmd = CommandExecutor.Execute("tasklist.exe /fo list"); + String imageName = null; + String pid = null; + for(String line : cmd.split("\n|\r\n|\n\r")) { + line = line.trim(); + if(line.isEmpty()) { + if(imageName != null) { + list.add(new ProcessInfo(Long.parseLong(pid), imageName)); + } + continue; + } + if(line.startsWith("Image Name")) { + imageName = line.replaceAll("Image Name:\\s*(.*)", "$1"); + continue; + } + if(line.startsWith("PID")) { + pid = line.replaceAll("PID:\\s*(.*)", "$1"); + } + } + return list; + } + + private static List GetRunningProcessesUnix() throws InterruptedException, IOException { + List list = new ArrayList<>(); + String cmd = CommandExecutor.Execute("ps -ea"); + boolean first = true; + for(String line : cmd.split("\n|\r\n|\n\r")) { + if(first) { + // First line is header + first = false; + continue; + } + line = line.trim(); + String[] params = line.split("\\s+", 4); + list.add(new ProcessInfo(Long.parseLong(params[0]), params[3])); + } + return list; + } + + private static Properties GetLsbRelease() throws IOException { + if(!GetOS().isLinux()) { + throw new UnsupportedOperationException("Only Linux distributions are supported in this method."); + } + File lsbRelease = new File("/etc/lsb-release"); + if(!lsbRelease.exists()) { + return null; + } + Properties props = new Properties(); + props.load(new FileInputStream(lsbRelease)); + return props; + } + + /** + * Does a best effort to get the linux distro currently running. Null is returned if this can't be determined. + * If {@link #GetOS()} would not return LINUX, then this throws an UnsupportedOperationException. + * @return + */ + public static String GetLinuxDistro() throws IOException { + return GetLsbRelease().getProperty("DISTRIB_ID"); + } + + /** + * Does a best effort to get the linux version currently running. Null is returned if this can't be determined. + * If {@link #GetOS()} would not return LINUX, then this throws an UnsupportedOperationException. + * @return + */ + public static String GetLinuxVersion() throws IOException { + return GetLsbRelease().getProperty("DISTRIB_RELEASE"); + } + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/Common/RSAEncrypt.java b/src/main/java/com/laytonsmith/PureUtilities/Common/RSAEncrypt.java index 6d6b00a7a0..4c8dd5f76e 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Common/RSAEncrypt.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Common/RSAEncrypt.java @@ -23,6 +23,7 @@ * Given a public/private key pair, this class uses RSA to encrypt/decrypt data. */ public class RSAEncrypt { + /** * The RSA algorithm key. */ @@ -48,8 +49,7 @@ public static RSAEncrypt generateKey(String label) { } /** - * Given a public key and a label, produces an ssh compatible rsa public key - * string. + * Given a public key and a label, produces an ssh compatible rsa public key string. * * @param key * @param label @@ -88,8 +88,8 @@ private static String toString(PrivateKey key) { StringBuilder privBuilder = new StringBuilder(); privBuilder.append("-----BEGIN RSA PRIVATE KEY-----"); - for (int i = 0; i < privateKey.length(); i++) { - if (i % 64 == 0) { + for(int i = 0; i < privateKey.length(); i++) { + if(i % 64 == 0) { privBuilder.append(StringUtils.nl()); } privBuilder.append(privateKey.charAt(i)); @@ -104,17 +104,15 @@ private static String toString(PrivateKey key) { private String label; /** - * Creates a new RSAEncrypt object, based on the ssh compatible - * private/public key pair. Only one key needs to be provided. If so, only - * those methods for the key provided will work. + * Creates a new RSAEncrypt object, based on the ssh compatible private/public key pair. Only one key needs to be + * provided. If so, only those methods for the key provided will work. * * @param privateKey * @param publicKey - * @throws IllegalArgumentException If the keys are not the correct type. - * They must be ssh compatible. + * @throws IllegalArgumentException If the keys are not the correct type. They must be ssh compatible. */ public RSAEncrypt(String privateKey, String publicKey) throws IllegalArgumentException { - if (privateKey != null) { + if(privateKey != null) { //private key processing //replace all newlines with nothing privateKey = privateKey.replaceAll("\r", ""); @@ -131,13 +129,13 @@ public RSAEncrypt(String privateKey, String publicKey) throws IllegalArgumentExc } } - if (publicKey != null) { + if(publicKey != null) { //public key processing String[] split = publicKey.split(" "); - if (split.length != 3) { + if(split.length != 3) { throw new IllegalArgumentException("Invalid public key passed in."); } - if (!"ssh-rsa".equals(split[0])) { + if(!"ssh-rsa".equals(split[0])) { throw new IllegalArgumentException("Invalid public key type. Expecting ssh-rsa, but found \"" + split[0] + "\""); } this.label = split[2]; @@ -152,8 +150,9 @@ public RSAEncrypt(String privateKey, String publicKey) throws IllegalArgumentExc } /** - * Encrypts the data with the public key, which can be decrypted with the - * private key. This is only valid if the public key was provided. + * Encrypts the data with the public key, which can be decrypted with the private key. This is only valid if the + * public key was provided. + * * @param data * @return */ @@ -161,49 +160,53 @@ public byte[] encryptWithPublic(byte[] data) { Objects.requireNonNull(publicKey); return crypt(data, publicKey, Cipher.ENCRYPT_MODE); } - + /** - * Encrypts the data with the private key, which can be decrypted with - * the public key. This is only valid if the private key was provided. + * Encrypts the data with the private key, which can be decrypted with the public key. This is only valid if the + * private key was provided. + * * @param data * @return - * @throws InvalidKeyException + * @throws InvalidKeyException */ public byte[] encryptWithPrivate(byte[] data) throws InvalidKeyException { Objects.requireNonNull(privateKey); return crypt(data, privateKey, Cipher.ENCRYPT_MODE); } - + /** - * Decrypts the data with the public key, which will have been encrypted - * with the private key. This is only valid if the public key was provided. + * Decrypts the data with the public key, which will have been encrypted with the private key. This is only valid if + * the public key was provided. + * * @param data - * @return + * @return */ public byte[] decryptWithPublic(byte[] data) { Objects.requireNonNull(publicKey); return crypt(data, publicKey, Cipher.DECRYPT_MODE); } - + /** - * Decrypts the data with the private key, which will have been encrypted - * with the public key. This is only valid if the private key was provided. + * Decrypts the data with the private key, which will have been encrypted with the public key. This is only valid if + * the private key was provided. + * * @param data - * @return + * @return */ - public byte[] decryptWithPrivate(byte[] data){ + public byte[] decryptWithPrivate(byte[] data) { Objects.requireNonNull(privateKey); return crypt(data, privateKey, Cipher.DECRYPT_MODE); } - + /** * Utility method that actually does the de/encrypting. + * * @param data * @param key * @param cryptMode - * @return + * @return */ - private byte[] crypt(byte [] data, Key key, int cryptMode){ + private byte[] crypt(byte[] data, Key key, int cryptMode) { byte[] cipherValue = null; Cipher cipher; try { @@ -216,7 +219,6 @@ private byte[] crypt(byte [] data, Key key, int cryptMode){ return cipherValue; } - /** * Returns the private key string. * diff --git a/src/main/java/com/laytonsmith/PureUtilities/Common/Range.java b/src/main/java/com/laytonsmith/PureUtilities/Common/Range.java index d014d25399..44a233d2a0 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Common/Range.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Common/Range.java @@ -1,4 +1,3 @@ - package com.laytonsmith.PureUtilities.Common; import java.util.AbstractList; @@ -13,114 +12,114 @@ public class Range { private final int rightBound; private final boolean leftInclusive; private final boolean rightInclusive; - + /** - * Creates a new Range object. The left bound doesn't have to be - * less than the right bound, but if it is greater, then the - * range is considered descending, assuming leftBound is not equal - * to rightBound. Otherwise, it is ascending. + * Creates a new Range object. The left bound doesn't have to be less than the right bound, but if it is greater, + * then the range is considered descending, assuming leftBound is not equal to rightBound. Otherwise, it is + * ascending. + * * @param leftBound The left boundary number * @param rightBound The right boundary number * @param leftInclusive If the left bound should be inclusive * @param rightInclusive If the right bound should be inclusive */ - public Range(int leftBound, int rightBound, boolean leftInclusive, boolean rightInclusive){ + public Range(int leftBound, int rightBound, boolean leftInclusive, boolean rightInclusive) { this.leftBound = leftBound; this.rightBound = rightBound; this.leftInclusive = leftInclusive; this.rightInclusive = rightInclusive; } - + /** * Creates a new Range object with both left and right bounds inclusive. + * * @param leftBound - * @param rightBound + * @param rightBound */ - public Range(int leftBound, int rightBound){ + public Range(int leftBound, int rightBound) { this(leftBound, rightBound, true, true); } - + /** - * Returns true if the left bound equals the right bound. If this returns - * true, both {@link #isAscending()} and {@link #isDecending()} will return - * false. - * @return + * Returns true if the left bound equals the right bound. If this returns true, both {@link #isAscending()} and + * {@link #isDecending()} will return false. + * + * @return */ - public boolean isEqual(){ + public boolean isEqual() { return leftBound == rightBound; } - + /** - * Returns true if the left bound is less than the right bound. If this - * returns true, both {@link #isEqual()} and {@link #isDecending()} will - * return false. - * @return + * Returns true if the left bound is less than the right bound. If this returns true, both {@link #isEqual()} and + * {@link #isDecending()} will return false. + * + * @return */ - public boolean isAscending(){ + public boolean isAscending() { return leftBound < rightBound; } - + /** - * Returns true if the right bound is less than the left bound. If this - * returns true, both {@link #isEqual()} and {@link #isAscending()} will - * return false. - * @return + * Returns true if the right bound is less than the left bound. If this returns true, both {@link #isEqual()} and + * {@link #isAscending()} will return false. + * + * @return */ - public boolean isDecending(){ + public boolean isDecending() { return rightBound < leftBound; } - + /** - * Returns a range of integers, starting with the left bound, counting up (or down if descending) - * by 1, and returning a List of values. The returned List is optimized to prevent - * a large memory footprint by generating the returned values via - * algorithm, instead of actually storing each value. However, this means that the - * List is read only. You can create a new List with a copy constructor to make - * a new mutable list. - * @return + * Returns a range of integers, starting with the left bound, counting up (or down if descending) by 1, and + * returning a List of values. The returned List is optimized to prevent a large memory footprint by generating the + * returned values via algorithm, instead of actually storing each value. However, this means that the List is read + * only. You can create a new List with a copy constructor to make a new mutable list. + * + * @return */ - public List getRange(){ + public List getRange() { //Calculate the size once - double _size = Math.abs(leftBound - rightBound); - if(!leftInclusive){ - _size--; + double size = Math.abs(leftBound - rightBound); + if(!leftInclusive) { + size--; } - if(rightInclusive){ - _size++; + if(rightInclusive) { + size++; } - final int size = (int)_size; + final int finalSize = (int) size; return new AbstractList() { @Override public Integer get(int index) { - if(isAscending()){ - return leftBound + index + (leftInclusive?0:1); + if(isAscending()) { + return leftBound + index + (leftInclusive ? 0 : 1); } else { - return leftBound - index - (leftInclusive?0:1); + return leftBound - index - (leftInclusive ? 0 : 1); } } @Override public int size() { - return size; + return finalSize; } }; } - + /** - * Returns the minimum value in this range, regardless of whether - * it is descending or ascending. - * @return + * Returns the minimum value in this range, regardless of whether it is descending or ascending. + * + * @return */ - public int getMin(){ - if(isAscending()){ - if(leftInclusive){ + public int getMin() { + if(isAscending()) { + if(leftInclusive) { return leftBound; } else { return leftBound + 1; } - } else if(isDecending()){ - if(rightInclusive){ + } else if(isDecending()) { + if(rightInclusive) { return rightBound; } else { return rightBound + 1; @@ -129,21 +128,21 @@ public int getMin(){ return leftBound; } } - + /** - * Returns the maximum value in this range, regardless of whether - * it is descending or ascending. - * @return + * Returns the maximum value in this range, regardless of whether it is descending or ascending. + * + * @return */ - public int getMax(){ - if(isDecending()){ - if(leftInclusive){ + public int getMax() { + if(isDecending()) { + if(leftInclusive) { return leftBound; } else { return leftBound - 1; } - } else if(isAscending()){ - if(rightInclusive){ + } else if(isAscending()) { + if(rightInclusive) { return rightBound; } else { return rightBound - 1; @@ -152,20 +151,20 @@ public int getMax(){ return leftBound; } } - + /** * Returns true if the specified value is included in this range. + * * @param value The value to test * @return true, if the value is in this range */ - public boolean contains(int value){ + public boolean contains(int value) { return value >= getMin() && value <= getMax(); } - - + @Override - public String toString(){ - return (leftInclusive?"[":"(") + leftBound + ", " + rightBound + (rightInclusive?"]":")"); + public String toString() { + return (leftInclusive ? "[" : "(") + leftBound + ", " + rightBound + (rightInclusive ? "]" : ")"); } - + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/Common/ReflectionUtils.java b/src/main/java/com/laytonsmith/PureUtilities/Common/ReflectionUtils.java index 8393bb241d..c9a54d453e 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Common/ReflectionUtils.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Common/ReflectionUtils.java @@ -7,15 +7,17 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; /** * - * + * */ -public class ReflectionUtils { +public final class ReflectionUtils { private ReflectionUtils() { } @@ -27,8 +29,7 @@ public ReflectionException(Throwable cause) { } /** - * Returns the underlying checked exception that was thrown by the - * reflective operation. + * Returns the underlying checked exception that was thrown by the reflective operation. * * @return */ @@ -39,53 +40,42 @@ public Throwable getCause() { } /** - * Constructs a new instance of the specified object, assuming it has a no - * arg constructor. It will bypass access restrictions if possible. + * Constructs a new instance of the specified object, assuming it has a no arg constructor. It will bypass access + * restrictions if possible. * * @param The class type returned, specified by the class type requested * @param clazz * @return - * @throws com.laytonsmith.PureUtilities.ReflectionUtils.ReflectionException + * @throws ReflectionException */ public static T newInstance(Class clazz) throws ReflectionException { return newInstance(clazz, new Class[]{}, new Object[]{}); } /** - * Constructs a new instance of the specified object, using the constructor - * that matches the argument types, and passes in the arguments specified. - * It will bypass access restrictions if possible. + * Constructs a new instance of the specified object, using the constructor that matches the argument types, and + * passes in the arguments specified. It will bypass access restrictions if possible. * * @param The class type returned, specified by the class type requested * @param clazz * @param argTypes * @param args * @return - * @throws com.laytonsmith.PureUtilities.ReflectionUtils.ReflectionException + * @throws ReflectionException */ public static T newInstance(Class clazz, Class[] argTypes, Object[] args) throws ReflectionException { try { Constructor c = clazz.getDeclaredConstructor(argTypes); c.setAccessible(true); return c.newInstance(args); - } catch (InstantiationException ex) { - throw new ReflectionException(ex); - } catch (IllegalAccessException ex) { - throw new ReflectionException(ex); - } catch (IllegalArgumentException ex) { - throw new ReflectionException(ex); - } catch (InvocationTargetException ex) { - throw new ReflectionException(ex); - } catch (NoSuchMethodException ex) { - throw new ReflectionException(ex); - } catch (SecurityException ex) { + } catch(InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException ex) { throw new ReflectionException(ex); } } /** - * Gets the value from a static class member, disregarding the access - * restrictions. + * Gets the value from a static class member, disregarding the access restrictions. * * @param clazz * @param variableName @@ -96,16 +86,14 @@ public static Object get(Class clazz, String variableName) throws ReflectionExce } /** - * Gets a member from a class, disregarding the access restrictions. If - * accessing a static variable, the instance may be null. If variableName - * contains a dot, then it recursively digs down and grabs that value. So, - * given the following class definitions: + * Gets a member from a class, disregarding the access restrictions. If accessing a static variable, the instance + * may be null. If variableName contains a dot, then it recursively digs down and grabs that value. So, given the + * following class definitions: *
 	 * class A { B bObj; }
 	 * class B { C cObj; }
 	 * class C { String obj; }
-	 * 
Then if clazz were A.class, and variableName were "bObj.cObj.obj", - * then C's String obj would be returned. + * Then if clazz were A.class, and variableName were "bObj.cObj.obj", then C's String obj would be returned. * * @param clazz * @param instance @@ -114,11 +102,11 @@ public static Object get(Class clazz, String variableName) throws ReflectionExce */ public static Object get(Class clazz, Object instance, String variableName) throws ReflectionException { try { - if (variableName.contains(".")) { + if(variableName.contains(".")) { String split[] = variableName.split("\\."); Object myInstance = instance; Class myClazz = clazz; - for (String var : split) { + for(String var : split) { myInstance = get(myClazz, myInstance, var); myClazz = myInstance.getClass(); } @@ -128,20 +116,13 @@ public static Object get(Class clazz, Object instance, String variableName) thro f.setAccessible(true); return f.get(instance); } - } catch (IllegalArgumentException ex) { - throw new ReflectionException(ex); - } catch (IllegalAccessException ex) { - throw new ReflectionException(ex); - } catch (NoSuchFieldException ex) { - throw new ReflectionException(ex); - } catch (SecurityException ex) { + } catch(IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException ex) { throw new ReflectionException(ex); } } /** - * Sets the value of a member in a static class, disregarding access - * restrictions and the final modifier. + * Sets the value of a member in a static class, disregarding access restrictions and the final modifier. * * @param clazz * @param variableName @@ -152,8 +133,8 @@ public static void set(Class clazz, String variableName, Object value) throws Re } /** - * Sets the value of a member in a specific instance of an object, - * disregarding access restrictions and the final modifier. + * Sets the value of a member in a specific instance of an object, disregarding access restrictions and the final + * modifier. * * @param clazz * @param instance @@ -163,13 +144,13 @@ public static void set(Class clazz, String variableName, Object value) throws Re public static void set(Class clazz, Object instance, String variableName, Object value) throws ReflectionException { try { - if (variableName.contains(".")) { + if(variableName.contains(".")) { String split[] = variableName.split("\\."); Object myInstance = instance; Class myClazz = clazz; int count = 0; - for (String var : split) { - if (count == split.length - 1) { + for(String var : split) { + if(count == split.length - 1) { //Only the last one needs to be set break; } @@ -183,224 +164,395 @@ public static void set(Class clazz, Object instance, String variableName, Object Field f = clazz.getDeclaredField(variableName); f.setAccessible(true); - //This is the really evil stuff here, this is what removes the final modifier. - Field modifiersField = Field.class.getDeclaredField("modifiers"); - modifiersField.setAccessible(true); - modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL); + if(Modifier.isFinal(f.getModifiers())) { + //This is the really evil stuff here, this is what removes the final modifier. + Field modifiersField = Field.class.getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL); + } f.set(instance, value); } - } catch (IllegalArgumentException ex) { - throw new ReflectionException(ex); - } catch (IllegalAccessException ex) { - throw new ReflectionException(ex); - } catch (NoSuchFieldException ex) { - throw new ReflectionException(ex); - } catch (SecurityException ex) { + } catch(IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException ex) { throw new ReflectionException(ex); } } /** - * Invokes a no argument method, disregarding access restrictions, and - * returns the result. + * Invokes a no argument method, disregarding access restrictions, and returns the result. Note that internally this + * uses {@link Class#getDeclaredMethod} which does not walk the class hierarchy, meaning that the clazz parameter + * must be of the class that declares the method, perhaps a supertype of the instance type. * - * @param clazz - * @param instance - * @param methodName - * @return + * @param clazz The class which declares the method intending on being called. + * @param instance The instance of the object to call the method on. + * @param methodName The name of the method. + * @return The invocation result, null if void. */ - public static Object invokeMethod(Class clazz, Object instance, String methodName) throws ReflectionException { + public static T invokeMethod(Class clazz, Object instance, String methodName) throws ReflectionException { return invokeMethod(clazz, instance, methodName, new Class[]{}, new Object[]{}); } /** - * Grabs the method from the instance object automatically. If multiple - * methods match the given name, the most appropriate one is selected based - * on the argument types. {@code instance} may not be null. + * Grabs the method from the instance object automatically. If multiple methods match the given name, the most + * appropriate one is selected based on the argument types. {@code instance} may not be null. * - * @param instance - * @param methodName - * @throws com.laytonsmith.PureUtilities.ReflectionUtils.ReflectionException + * @param instance The instance of the object to call the method on. + * @param methodName The name of the method. + * @param params + * @return The invocation result, null if void. + * @throws ReflectionException */ - public static Object invokeMethod(Object instance, String methodName, Object... params) throws ReflectionException { + @SuppressWarnings({"ThrowableInstanceNotThrown", "ThrowableInstanceNeverThrown"}) + public static T invokeMethod(Object instance, String methodName, Object... params) throws ReflectionException { Class c = instance.getClass(); Class[] argTypes; { - List cl = new ArrayList(); - for (Object o : params) { - if (o != null) { + List cl = new ArrayList<>(); + for(Object o : params) { + if(o != null) { cl.add(o.getClass()); } else { - //If it's null, we'll just have to assume Object - cl.add(Object.class); + //If it's null, we'll just add null, and check it below + cl.add(null); } } argTypes = cl.toArray(new Class[cl.size()]); } - while (c != null) { + while(c != null) { method: - for (Method m : c.getDeclaredMethods()) { - if (methodName.equals(m.getName())) { + for(Method m : c.getDeclaredMethods()) { + if(methodName.equals(m.getName())) { try { - if (m.getParameterTypes().length == argTypes.length) { + if(m.getParameterTypes().length == argTypes.length) { Class[] args = m.getParameterTypes(); //Check to see that these arguments are subclasses //of the method's parameters. If so, this is our method, //otherwise, not. - for (int i = 0; i < argTypes.length; i++) { - if (!args[i].isAssignableFrom(argTypes[i])) { + for(int i = 0; i < argTypes.length; i++) { + // Null types match everything, so if argTypes[i] is null, then we + // don't care what the actual method type is. + if(argTypes[i] != null && !args[i].isAssignableFrom(argTypes[i])) { continue method; } } - return m.invoke(instance, params); + m.setAccessible(true); + return (T) m.invoke(instance, params); } - } catch (IllegalAccessException ex) { - throw new ReflectionException(ex); - } catch (IllegalArgumentException ex) { - throw new ReflectionException(ex); - } catch (InvocationTargetException ex) { + } catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { throw new ReflectionException(ex); } } } c = c.getSuperclass(); } - throw new ReflectionException(new NoSuchMethodException(methodName + " was not found in any of the searched classes.")); + throw new ReflectionException(new NoSuchMethodException(methodName + + " was not found in any of the searched classes.")); } /** - * Grabs the method from the instance object automatically. {@code instance} - * may not be null. + * Grabs the method from the instance object automatically. {@code instance} may not be null. This walks the + * superclass hierarchy if necessary to find the correct method. This only works for argument-less methods. * - * @param instance - * @param methodName - * @throws com.laytonsmith.PureUtilities.ReflectionUtils.ReflectionException + * @param instance The instance to call the method on. + * @param methodName The method to call. + * @return The invocation result, null if void. + * @throws ReflectionException */ - public static Object invokeMethod(Object instance, String methodName) throws ReflectionException { + @SuppressWarnings({"ThrowableInstanceNotThrown", "ThrowableInstanceNeverThrown"}) + public static T invokeMethod(Object instance, String methodName) throws ReflectionException { Class c = instance.getClass(); - while (c != null) { - for (Method m : c.getDeclaredMethods()) { - if (methodName.equals(m.getName())) { + while(c != null) { + for(Method m : c.getDeclaredMethods()) { + if(methodName.equals(m.getName()) && m.getParameterCount() == 0) { try { - return m.invoke(instance); - } catch (IllegalAccessException ex) { - throw new ReflectionException(ex); - } catch (IllegalArgumentException ex) { - throw new ReflectionException(ex); - } catch (InvocationTargetException ex) { + return (T) m.invoke(instance); + } catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { throw new ReflectionException(ex); } } } c = c.getSuperclass(); } - throw new ReflectionException(new NoSuchMethodException(methodName + " was not found in any of the searched classes.")); + throw new ReflectionException(new NoSuchMethodException(methodName + + " was not found in any of the searched classes.")); } /** - * Invokes a method with the parameters specified, disregarding access - * restrictions, and returns the result. + * Invokes a method with the parameters specified, disregarding access restrictions, and returns the result. Note + * that internally this uses {@link Class#getDeclaredMethod} which does not walk the class hierarchy, meaning that + * the clazz parameter must be of the class that declares the method, perhaps a supertype of the instance type. * - * @param clazz - * @param instance - * @param methodName - * @param argTypes - * @param args - * @return + * @param clazz The class which declares the method intending on being called. + * @param instance The instance of the object to call the method on. + * @param methodName The name of the method. + * @param argTypes The argument types. + * @param args The arguments. + * @return The invocation result, null if void. */ - public static Object invokeMethod(Class clazz, Object instance, String methodName, Class[] argTypes, Object[] args) throws ReflectionException { + public static T invokeMethod(Class clazz, Object instance, String methodName, Class[] argTypes, Object[] args) + throws ReflectionException { try { Method m = clazz.getDeclaredMethod(methodName, argTypes); m.setAccessible(true); - return m.invoke(instance, args); - } catch (InvocationTargetException ex) { - throw new ReflectionException(ex); - } catch (NoSuchMethodException ex) { - throw new ReflectionException(ex); - } catch (IllegalArgumentException ex) { - throw new ReflectionException(ex); - } catch (IllegalAccessException ex) { - throw new ReflectionException(ex); - } catch (SecurityException ex) { + return (T) m.invoke(instance, args); + } catch(InvocationTargetException | NoSuchMethodException | IllegalArgumentException + | IllegalAccessException | SecurityException ex) { throw new ReflectionException(ex); } } /** * Shorthand for {@link #PrintObjectTrace(instance, instanceOnly, null)} + * + * @param instance + * @param instanceOnly */ public static void PrintObjectTrace(Object instance, boolean instanceOnly) { PrintObjectTrace(instance, instanceOnly, null); } /** - * Meant mostly as a debug tool, takes an object and prints out the object's - * non-static field information at this current point in time, to the - * specified PrintStream. This method will not throw any SecurityExceptions - * if a value cannot be reflectively accessed, but instead will print an - * error message for that single value. - * - * @param instance The object to explore. If this is null, "The object is - * null" is printed, and the method exits. - * @param instanceOnly If true, only the object's class members will be - * printed, otherwise, the method will recurse up the object's inheritance - * hierarchy, and prints everything. + * Meant mostly as a debug tool, takes an object and prints out the object's non-static field information at this + * current point in time, to the specified PrintStream. This method will not throw any SecurityExceptions if a value + * cannot be reflectively accessed, but instead will print an error message for that single value. + * + * @param instance The object to explore. If this is null, "The object is null" is printed, and the method exits. + * @param instanceOnly If true, only the object's class members will be printed, otherwise, the method will recurse + * up the object's inheritance hierarchy, and prints everything. * @param output The print stream to output to, or System.out if null. */ public static void PrintObjectTrace(Object instance, boolean instanceOnly, PrintStream output) { - if (output == null) { - output = System.out; + if(output == null) { + output = StreamUtils.GetSystemOut(); } - if (instance == null) { + if(instance == null) { output.println("The object is null"); return; } Class iClass = instance.getClass(); do { - for (Field f : iClass.getDeclaredFields()) { - if ((f.getModifiers() & Modifier.STATIC) > 0) { + for(Field f : iClass.getDeclaredFields()) { + if((f.getModifiers() & Modifier.STATIC) > 0) { continue; } String value = "null"; try { f.setAccessible(true); Object o = ReflectionUtils.get(iClass, instance, f.getName()); - if (o != null) { + if(o != null) { value = o.toString(); } - } catch (SecurityException e) { + } catch(SecurityException e) { value = "Could not access value due to a SecurityException"; } output.println("(" + f.getType() + ") " + f.getName() + ": " + value); } - } while (!instanceOnly && (iClass = iClass.getSuperclass()) != null); + } while(!instanceOnly && (iClass = iClass.getSuperclass()) != null); } - + /** * Gets a set of all classes that this class extends or implements. In other words, - * {@link Class#isAssignableFrom(java.lang.Class)} will return true for - * all returned classes. + * {@link Class#isAssignableFrom(java.lang.Class)} will return true for all returned classes. + * * @param c - * @return + * @return */ - public static Set getAllExtensions(Class c){ + public static Set getAllExtensions(Class c) { Set cs = new HashSet<>(); Class cc = c.getSuperclass(); - while(cc != null){ + while(cc != null) { cs.add(cc); - for(Class ccc : cc.getInterfaces()){ + for(Class ccc : cc.getInterfaces()) { cs.addAll(getAllExtensions(ccc)); } cc = cc.getSuperclass(); - + } - for(Class i : c.getInterfaces()){ + for(Class i : c.getInterfaces()) { cs.addAll(getAllExtensions(i)); cs.add(i); } return cs; } + + /** + * Checks to see if a method with the given signature exists. + * + * @param c The class to check + * @param methodName The method name + * @param returnType The return type of the method, or if it is otherwise castable to this. Sending null or + * Object.class implies that the return type doesn't matter. + * @param params The signature of the method + * @return True, if the method with this signature exists. + * @throws ReflectionException If a SecurityException is thrown by the underlying code, this will be thrown. + */ + @SuppressWarnings("unchecked") + public static boolean hasMethod(Class c, String methodName, Class returnType, Class... params) + throws ReflectionException { + Method m; + try { + m = c.getMethod(methodName, params); + } catch(NoSuchMethodException ex) { + return false; + } catch(SecurityException ex) { + throw new ReflectionException(ex); + } + if(returnType != null) { + return returnType.isAssignableFrom(m.getReturnType()); + } + return true; + } + + /** + * Returns sun.misc.Unsafe. It is an object, which requires reflective calls made onto it. This is to prevent any + * warnings from the JVM. + * + * @return + */ + private static Object getUnsafe() { + Object unsafe; + try { + unsafe = ReflectionUtils.get(Class.forName("sun.misc.Unsafe"), "theUnsafe"); + } catch(ClassNotFoundException ex) { + throw new RuntimeException(ex); + } + return unsafe; + } + + /** + * Instantiates a class without calling its constructor. In general, the object will be in an unknown state. This + * method should not generally be relied on, and only used in limited cases. + * + * @param cls The class to instantiate + * @return The newly instantiated object. + * @throws RuntimeException If the underlying code throws an InstantiationException, it is wrapped and re-thrown in + * a RuntimeException. + */ + @SuppressWarnings("unchecked") + public static T instantiateUnsafe(Class cls) throws RuntimeException { + return (T) ReflectionUtils.invokeMethod(getUnsafe(), "allocateInstance", cls); + } + + /** + * Throws the given Throwable without requiring calling classes to check it. + * + * @param t The Throwable to throw, checked or otherwise. + */ + public static void throwUncheckedException(Throwable t) { + ReflectionUtils.invokeMethod(getUnsafe(), "throwException", t); + } + + /** + * Returns the {@code Class} object associated with the class or interface with the given string name. Invoking this + * method is equivalent to: + * + *
{@code Class.forName(className, true, currentLoader)} + *
+ * + * where {@code currentLoader} denotes the defining class loader of the current class. + * + *

+ * For example, the following code fragment returns the runtime {@code Class} descriptor for the class named + * {@code java.lang.Thread}: + * + *

{@code Class t = Class.forName("java.lang.Thread")} + *
+ *

+ * A call to {@code forName("X")} causes the class named {@code X} to be initialized. + * + * @param className the fully qualified name of the desired class. + * @return the {@code Class} object for the class with the specified name. + * @throws ReflectionException wraps a LinkageError if the linkage fails, ExceptionInInitializerError if the + * initialization provoked by this method fails, or ClassNotFoundException if the class is not found. + */ + public static Class forName(String className) { + try { + return Class.forName(className); + } catch(ClassNotFoundException ex) { + throw new ReflectionException(ex); + } + } + + /** + * Returns the {@code Class} object associated with the class or interface with the given string name, using the + * given class loader. Given the fully qualified name for a class or interface (in the same format returned by + * {@code getName}) this method attempts to locate and load the class or interface. The specified class loader is + * used to load the class or interface. If the parameter {@code loader} is null, the class is loaded through the + * bootstrap class loader. The class is initialized only if the {@code initialize} parameter is {@code true} and if + * it has not been initialized earlier. + * + *

+ * If {@code name} denotes a primitive type or void, an attempt will be made to locate a user-defined class in the + * unnamed package whose name is {@code name}. Therefore, this method cannot be used to obtain any of the + * {@code Class} objects representing primitive types or void. + * + *

+ * If {@code name} denotes an array class, the component type of the array class is loaded but not initialized. + * + *

+ * For example, in an instance method the expression: + * + *

{@code Class.forName("Foo")} + *
+ * + * is equivalent to: + * + *
{@code Class.forName("Foo", true, this.getClass().getClassLoader())} + *
+ * + * Note that this method throws errors related to loading, linking or initializing as specified in Sections { + * + * @jls 12.2}, { + * @jls 12.3}, and { + * @jls 12.4} of The Java Language Specification. Note that this method does not check whether the + * requested class is accessible to its caller. + * + * @param name fully qualified name of the desired class + * + * @param initialize if {@code true} the class will be initialized (which implies linking). See Section { + * @jls 12.4} of The Java Language Specification. + * @param loader class loader from which the class must be loaded + * @return class object representing the desired class + * + * @throws ReflectionException wraps a LinkageError if the linkage fails, ExceptionInInitializerError if the + * initialization provoked by this method fails, SecurityException if a security manager is present, + * and the {@code loader} is {@code null}, and the + * caller's class loader is not {@code null}, and the caller does not have the + * {@link RuntimePermission}{@code ("getClassLoader")}, or ClassNotFoundException if the class is not found. + * + * @see java.lang.Class#forName(String, boolean, ClassLoader) + * @see java.lang.ClassLoader + */ + public static Class forName(String name, boolean initialize, ClassLoader loader) { + try { + return Class.forName(name, initialize, loader); + } catch(ClassNotFoundException ex) { + throw new ReflectionException(ex); + } + } + + // Exceptions are expensive, so cache this. + private static Map classExistsMap = new HashMap<>(); + + /** + * Checks if a class exists, according to Class.forName(). This is cached. + * @param name The class name. + * @return True if the class exists. + */ + public static boolean classExists(String name) { + if(classExistsMap.containsKey(name)) { + return classExistsMap.get(name); + } + try { + Class.forName(name); + classExistsMap.put(name, Boolean.TRUE); + return true; + } catch(ClassNotFoundException ex) { + classExistsMap.put(name, Boolean.FALSE); + return false; + } + } + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/Common/StackTraceUtils.java b/src/main/java/com/laytonsmith/PureUtilities/Common/StackTraceUtils.java index c529084ae8..5900a76aba 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Common/StackTraceUtils.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Common/StackTraceUtils.java @@ -6,24 +6,100 @@ /** * - * + * */ -public class StackTraceUtils { - - private StackTraceUtils(){} - - public static String GetStacktrace(Throwable t){ - final Writer result = new StringWriter(); +public final class StackTraceUtils { + + private StackTraceUtils() { + } + + /** + * Gets the stack trace of an exception, as if it were being printed to the console. + * @param t The Throwable instance. May have a causedBy reason. + * @return + */ + public static String GetStacktrace(Throwable t) { + final Writer result = new StringWriter(); final PrintWriter printWriter = new PrintWriter(result); boolean first = true; Throwable tt = t; do { - if(!first){ + if(!first) { printWriter.append("Caused by: "); } first = false; tt.printStackTrace(printWriter); } while((tt = tt.getCause()) != null); - return result.toString(); - } + return result.toString(); + } + + /** + * Returns the name of the class that called the method that is using this code. I.e., if + * the method a in class A calls the method b in class B, and the method b in class B + * calls getCallingClass(), then a reference to class A would be returned. + * @return + */ + public static Class getCallingClass() { + try { + // This is the class that called us. Calls may bounce around that class, + // but we ultimately want to return that class that called the original + // method within this class. + StackTraceElement[] st = Thread.currentThread().getStackTrace(); + String doNotReturn = st[2].getClassName(); + for(int i = 3; i < st.length; i++) { + if(!st[i].getClassName().equals(doNotReturn)) { + return Class.forName(st[i].getClassName()); + } + } + // The only way this can get here is if this were the bootstrap class. + // I doubt the JVM is calling us first thing, so just throw an Error. + throw new Error(); + } catch (ClassNotFoundException ex) { + throw new RuntimeException(ex); + } + } + + /** + * Returns the name of the current method. This method is lambda aware, that is, if a code inside a lambda + * calls this method, it continues walking up the chain until it finds the actual full method that's calling + * this method. + * @return + */ + public static String currentMethod() { + // We unfortunately need to duplicate code here, since we can't add to the stack. + StackTraceElement[] st = Thread.currentThread().getStackTrace(); + String found = null; + int trace = 1; + while(found == null) { + trace++; + String m = st[trace].getMethodName(); + // If this is a lambda, we want to go up one more. + if(!m.startsWith("lambda$")) { + found = m; + } + } + return found; + } + + /** + * Returns the name of the current method. This method is lambda aware, that is, if a code inside a lambda + * calls this method, it continues walking up the chain until it finds the actual full method that's calling + * this method, unless includeLambdas is false. + * @param includeLambdas + * @return + */ + public static String currentMethod(boolean includeLambdas) { + StackTraceElement[] st = Thread.currentThread().getStackTrace(); + String found = null; + int trace = 1; + while(found == null) { + trace++; + String m = st[trace].getMethodName(); + // If this is a lambda, we want to go up one more. + if(includeLambdas || !m.startsWith("lambda$")) { + found = m; + } + } + return found; + } } diff --git a/src/main/java/com/laytonsmith/PureUtilities/Common/StreamUtils.java b/src/main/java/com/laytonsmith/PureUtilities/Common/StreamUtils.java index b4cc9193bc..f717f55b64 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Common/StreamUtils.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Common/StreamUtils.java @@ -2,125 +2,195 @@ import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; +import java.io.PrintStream; import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.List; +import java.nio.charset.StandardCharsets; /** - * Streams are hard sometimes. This class abstracts most of the functionality - * that is commonly used. + * Streams are hard sometimes. This class abstracts most of the functionality that is commonly used. * */ public class StreamUtils { /** * Copies from one stream to another + * * @param out * @param in - * @throws IOException + * @throws IOException */ public static void Copy(InputStream in, OutputStream out) throws IOException { byte[] buffer = new byte[1024]; int len = in.read(buffer); - while (len != -1) { + while(len != -1) { out.write(buffer, 0, len); len = in.read(buffer); } } - + /** - * Given an input stream, a UTF-8 encoded string is returned, which - * is a reasonable assumption for most textual data. This assumes - * that the stream is finite, i.e. not a streaming socket, for instance, and - * reads until the stream reaches the end. + * Given an input stream, a UTF-8 encoded string is returned, which is a reasonable assumption for most textual + * data. This assumes that the stream is finite, i.e. not a streaming socket, for instance, and reads until the + * stream reaches the end. + * * @param out - * @return + * @return */ - public static String GetString(InputStream in){ + public static String GetString(InputStream in) { try { return GetString(in, "UTF-8"); - } catch (UnsupportedEncodingException ex) { + } catch(UnsupportedEncodingException ex) { throw new Error(ex); } } - + /** - * Gets a string from an input stream, assuming the given encoding. This assumes - * that the stream is finite, i.e. not a streaming socket, for instance, and - * reads until the stream reaches the end. + * Gets a string from an input stream, assuming the given encoding. This assumes that the stream is finite, i.e. not + * a streaming socket, for instance, and reads until the stream reaches the end. + * * @param in * @param encoding * @return - * @throws UnsupportedEncodingException + * @throws UnsupportedEncodingException */ - public static String GetString(InputStream in, String encoding) throws UnsupportedEncodingException{ - if(encoding == null){ + public static String GetString(InputStream in, String encoding) throws UnsupportedEncodingException { + if(encoding == null) { encoding = "UTF-8"; } - if(in == null){ - throw new NullPointerException(); + if(in == null) { + throw new NullPointerException("InputStream is null"); } InputStreamReader input; input = new InputStreamReader(new BufferedInputStream(in), encoding); - final int CHARS_PER_PAGE = 5000; //counting spaces - final char[] buffer = new char[CHARS_PER_PAGE]; - StringBuilder output = new StringBuilder(CHARS_PER_PAGE); + final int charsPerPage = 5000; //counting spaces + final char[] buffer = new char[charsPerPage]; + StringBuilder output = new StringBuilder(charsPerPage); try { - for(int read = input.read(buffer, 0, buffer.length); - read != -1; - read = input.read(buffer, 0, buffer.length)) { - output.append(buffer, 0, read); - } - } catch (IOException ignore) { } + for(int read = input.read(buffer, 0, buffer.length); + read != -1; + read = input.read(buffer, 0, buffer.length)) { + output.append(buffer, 0, read); + } + } catch(IOException ignore) { + } return output.toString(); } - + /** - * Fully reads in a stream, as efficiently as possible, and returns a - * byte array. + * Fully reads in a stream, as efficiently as possible, and returns a byte array. The input stream is not closed + * afterwards. + * * @param in * @return - * @throws IOException + * @throws IOException */ public static byte[] GetBytes(InputStream in) throws IOException { BufferedInputStream bis = new BufferedInputStream(in); - List bytes = new ArrayList(); - int i; - while((i = bis.read()) != -1){ - bytes.add(((byte)i)); + try(ByteArrayOutputStream out = new ByteArrayOutputStream()) { + int i; + byte[] buffer = new byte[8 * 1024]; + while((i = bis.read(buffer)) != -1) { + out.write(buffer, 0, i); + } + return out.toByteArray(); } - return ArrayUtils.unbox(bytes.toArray(new Byte[bytes.size()])); } - + /** - * Assuming a UTF-8 encoded string is provided, returns an InputStream - * for that String. + * Assuming a UTF-8 encoded string is provided, returns an InputStream for that String. + * * @param contents - * @return + * @return */ - public static InputStream GetInputStream(String contents){ + public static InputStream GetInputStream(String contents) { try { return GetInputStream(contents, "UTF-8"); - } catch (UnsupportedEncodingException ex) { + } catch(UnsupportedEncodingException ex) { throw new Error(ex); } } - + /** - * Returns an InputStream for a given string, using the given - * encoding. + * Returns an InputStream for a given string, using the given encoding. + * * @param contents * @param encoding * @return - * @throws UnsupportedEncodingException + * @throws UnsupportedEncodingException */ - public static InputStream GetInputStream(String contents, String encoding) throws UnsupportedEncodingException{ + public static InputStream GetInputStream(String contents, String encoding) throws UnsupportedEncodingException { return new ByteArrayInputStream(contents.getBytes(encoding)); } + + /** + * Returns an InputStream for a given byte array (ByteArrayInputStream). + * + * @param bytes The bytes to wrap in an InputStream + * @return An InputStream wrapping the bytes + */ + public static InputStream GetInputStream(byte[] bytes) { + return new ByteArrayInputStream(bytes); + } + + /** + * Returns System.out, but wrapped in a UTF-8 capable output stream. This is required, because higher order + * characters cannot print by default in Java. + * + * @return A new PrintStream object, based on System.out + */ + public static PrintStream GetSystemOut() { + return new PrintStream(System.out, true, StandardCharsets.UTF_8); + } + + /** + * Returns System.err, but wrapped in a UTF-8 capable output stream. This is required, because higher order + * characters cannot print by default in Java. + * + * @return A new PrintStream object, based on System.err + */ + public static PrintStream GetSystemErr() { + return new PrintStream(System.err, true, StandardCharsets.UTF_8); + } + + /** + * Gets a resource string with the specified encoding, relative to the class that is calling this method. + * + * @param name The name of the resource. The name should follow the same naming conventions used by + * {@link Class#getResource(java.lang.String)}. + * @param encoding The encoding to use on the resource. + * @return A string depiction of the specified resource. + * @throws java.io.UnsupportedEncodingException If the encoding is not supported. + * @throws java.lang.IllegalArgumentException If the resource was not found. + */ + public static final String GetResource(String name, String encoding) throws UnsupportedEncodingException, + IllegalArgumentException { + InputStream is = StackTraceUtils.getCallingClass().getResourceAsStream(name); + if(is == null) { + throw new IllegalArgumentException("Could not find resource " + name); + } + return GetString(is, encoding); + } + + /** + * Gets a resource as a UTF-8 encoded string, relative to the class that is calling this method. + * + * @param name The name of the resource. The name should follow the same naming conventions used by + * {@link Class#getResource(java.lang.String)}. + * @return A string depiction of the specified resource. + * @throws java.lang.IllegalArgumentException If the resource was not found. + */ + public static final String GetResource(String name) throws IllegalArgumentException { + try { + return GetResource(name, "UTF-8"); + } catch(UnsupportedEncodingException ex) { + throw new Error(ex); + } + } } diff --git a/src/main/java/com/laytonsmith/PureUtilities/Common/StringUtils.java b/src/main/java/com/laytonsmith/PureUtilities/Common/StringUtils.java index ae3fcd6d20..1f17720ce8 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Common/StringUtils.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Common/StringUtils.java @@ -19,15 +19,11 @@ private StringUtils() { } /** - * Joins a map together (using StringBuilder's { - * - * @see StringBuilder#append(Object)} method to "toString" the Object) + * Joins a map together (using StringBuilder's {@link StringBuilder#append(Object)} method to "toString" the Object) * * @param map The map to concatenate - * @param entryGlue The glue to use between the key and value of each pair - * in the map - * @param elementGlue The glue to use between each key-value element pairs - * in the map + * @param entryGlue The glue to use between the key and value of each pair in the map + * @param elementGlue The glue to use between each key-value element pairs in the map * @return The concatenated string */ public static String Join(Map map, String entryGlue, String elementGlue) { @@ -35,20 +31,12 @@ public static String Join(Map map, String entryGlue, String elementGlue) { } /** - * Joins a map together (using StringBuilder's { - * - * @see StringBuilder#append(Object)} method to "toString" the Object) + * Joins a map together (using StringBuilder's {@link StringBuilder#append(Object)} method to "toString" the Object) * * @param map The map to concatenate - * @param entryGlue The glue to use between the key and value of each pair - * in the map - * @param elementGlue The glue to use between each key-value element pairs - * in the map + * @param entryGlue The glue to use between the key and value of each pair in the map + * @param elementGlue The glue to use between each key-value element pairs in the map * @param lastElementGlue The glue for the last two elements - * @param glueForTwoItems If only two items are in the map, then this glue - * is used instead. If it is null, then lastElementGlue is used instead. - * @param empty If the map is completely empty, this string is simply - * returned. If null, an empty string is used. * @return The concatenated string */ public static String Join(Map map, String entryGlue, String elementGlue, String lastElementGlue) { @@ -56,18 +44,14 @@ public static String Join(Map map, String entryGlue, String elementGlue, String } /** - * Joins a map together (using StringBuilder's { - * - * @see StringBuilder#append(Object)} method to "toString" the Object) + * Joins a map together (using StringBuilder's {@link StringBuilder#append(Object)} method to "toString" the Object) * * @param map The map to concatenate - * @param entryGlue The glue to use between the key and value of each pair - * in the map - * @param elementGlue The glue to use between each key-value element pairs - * in the map + * @param entryGlue The glue to use between the key and value of each pair in the map + * @param elementGlue The glue to use between each key-value element pairs in the map * @param lastElementGlue The glue for the last two elements - * @param glueForTwoItems If only two items are in the map, then this glue - * is used instead. If it is null, then lastElementGlue is used instead. + * @param elementGlueForTwoItems If only two items are in the map, then this glue is used instead. If it is null, then + * lastElementGlue is used instead. * @return The concatenated string */ public static String Join(Map map, String entryGlue, String elementGlue, String lastElementGlue, String elementGlueForTwoItems) { @@ -75,26 +59,21 @@ public static String Join(Map map, String entryGlue, String elementGlue, String } /** - * Joins a map together (using StringBuilder's { - * - * @see StringBuilder#append(Object)} method to "toString" the Object) + * Joins a map together (using StringBuilder's {@link StringBuilder#append(Object)} method to "toString" the Object) * * @param map The map to concatenate - * @param entryGlue The glue to use between the key and value of each pair - * in the map - * @param elementGlue The glue to use between each key-value element pairs - * in the map + * @param entryGlue The glue to use between the key and value of each pair in the map + * @param elementGlue The glue to use between each key-value element pairs in the map * @param lastElementGlue The glue for the last two elements - * @param glueForTwoItems If only two items are in the map, then this glue - * is used instead. If it is null, then lastElementGlue is used instead. - * @param empty If the map is completely empty, this string is simply - * returned. If null, an empty string is used. + * @param elementGlueForTwoItems If only two items are in the map, then this glue is used instead. If it is null, then + * lastElementGlue is used instead. + * @param empty If the map is completely empty, this string is simply returned. If null, an empty string is used. * @return The concatenated string */ public static String Join(Map map, String entryGlue, String elementGlue, String lastElementGlue, String elementGlueForTwoItems, String empty) { //Just create a list of glued together entries, then send it to the other Join method - List list = new ArrayList(); - for (Object key : map.keySet()) { + List list = new ArrayList<>(); + for(Object key : map.keySet()) { StringBuilder b = new StringBuilder(); b.append(key).append(entryGlue).append(map.get(key)); list.add(b.toString()); @@ -103,94 +82,99 @@ public static String Join(Map map, String entryGlue, String elementGlue, String } /** - * Joins a set together (using StringBuilder's { - * - * @see StringBuilder#append(Object)} method to "toString" the Object) using - * the specified string for glue. - * @param list The set to concatenate + * Joins a set together (using StringBuilder's {@link StringBuilder#append(Object)} method to "toString" the Object) + * using the specified string for glue. + * @param The set type + * @param set The set to concatenate * @param glue The glue to use * @return The concatenated string */ - public static String Join(Set set, String glue) { + public static String Join(Set set, String glue) { return Join(set, glue, null, null, null); } /** - * Joins a set together (using StringBuilder's { - * - * @see StringBuilder#append(Object)} method to "toString" the Object) using - * the specified string for glue. If lastGlue is null, it is the same as - * glue, but otherwise it is used to glue just the last two items together, - * which is useful for sets that are being read by a human, to have a proper - * conjunction at the end. - * @param list The set to concatenate + * Joins a set together, rendering each item with the custom renderer. + * @param The set type + * @param set The set to concatenate + * @param glue The glue to use + * @param renderer The item renderer. This renders each item in the set, one at a time. If null, toString will be + * used by default on each item. + * @return + */ + public static String Join(Set set, String glue, Renderer renderer) { + return Join(set, glue, null, null, null, renderer); + } + + /** + * Joins a set together (using StringBuilder's {@link StringBuilder#append(Object)} method to "toString" the Object) + * using the specified string for glue. If + * lastGlue is null, it is the same as glue, but otherwise it is used to glue just the last two items together, + * which is useful for sets that are being read by a human, to have a proper conjunction at the end. + * @param The set type + * @param set The set to concatenate * @param glue The glue to use * @param lastGlue The glue for the last two elements * @return The concatenated string */ - public static String Join(Set set, String glue, String lastGlue) { + public static String Join(Set set, String glue, String lastGlue) { return Join(set, glue, lastGlue, null, null); } /** - * Joins a set together (using StringBuilder's { - * - * @see StringBuilder#append(Object)} method to "toString" the Object) using - * the specified string for glue. If lastGlue is null, it is the same as - * glue, but otherwise it is used to glue just the last two items together, - * which is useful for sets that are being read by a human, to have a proper - * conjunction at the end. - * @param list The set to concatenate + * Joins a set together (using StringBuilder's {@link StringBuilder#append(Object)} method to "toString" the Object) + * using the specified string for glue. + * If lastGlue is null, it is the same as glue, but otherwise it is used to glue just the last two items together, + * which is useful for sets that are being read by a human, to have a proper conjunction at the end. + * @param The set type + * @param set The set to concatenate * @param glue The glue to use * @param lastGlue The glue for the last two elements - * @param glueForTwoItems If only two items are in the set, then this glue - * is used instead. If it is null, then lastGlue is used instead. + * @param glueForTwoItems If only two items are in the set, then this glue is used instead. If it is null, then + * lastGlue is used instead. * @return The concatenated string */ - public static String Join(Set set, String glue, String lastGlue, String glueForTwoItems) { + public static String Join(Set set, String glue, String lastGlue, String glueForTwoItems) { return Join(set, glue, lastGlue, glueForTwoItems, null); } /** - * Joins a set together (using StringBuilder's { - * - * @see StringBuilder#append(Object)} method to "toString" the Object) using - * the specified string for glue. If lastGlue is null, it is the same as - * glue, but otherwise it is used to glue just the last two items together, - * which is useful for sets that are being read by a human, to have a proper - * conjunction at the end. - * @param list The set to concatenate + * Joins a set together (using StringBuilder's {@link StringBuilder#append(Object)} method to "toString" the Object) + * using the specified string for glue. + * If lastGlue is null, it is the same as glue, but otherwise it is used to glue just the last two items together, + * which is useful for sets that are being read by a human, to have a proper conjunction at the end. + * @param The set type + * @param set The set to concatenate * @param glue The glue to use * @param lastGlue The glue for the last two elements - * @param glueForTwoItems If only two items are in the set, then this glue - * is used instead. If it is null, then lastGlue is used instead. - * @param empty If the set is completely empty, this string is simply - * returned. If null, an empty string is used. + * @param glueForTwoItems If only two items are in the set, then this glue is used instead. If it is null, then + * lastGlue is used instead. + * @param empty If the set is completely empty, this string is simply returned. If null, an empty string is used. * @return The concatenated string */ - public static String Join(Set set, String glue, String lastGlue, String glueForTwoItems, String empty) { + public static String Join(Set set, String glue, String lastGlue, String glueForTwoItems, String empty) { return Join(set, glue, lastGlue, glueForTwoItems, empty, null); } /** - * Joins a set together (using StringBuilder's { - * - * @see StringBuilder#append(Object)} method to "toString" the Object) using - * the specified string for glue. If lastGlue is null, it is the same as - * glue, but otherwise it is used to glue just the last two items together, - * which is useful for sets that are being read by a human, to have a proper - * conjunction at the end. - * @param list The set to concatenate + * Joins a set together (using StringBuilder's {@link StringBuilder#append(Object)} method to "toString" the Object) + * using the specified string for glue. + * If lastGlue is null, it is the same as glue, but otherwise it is used to glue just the last two items together, + * which is useful for sets that are being read by a human, to have a proper conjunction at the end. + * @param + * @param set The set to concatenate * @param glue The glue to use * @param lastGlue The glue for the last two elements - * @param glueForTwoItems If only two items are in the set, then this glue - * is used instead. If it is null, then lastGlue is used instead. - * @param empty If the set is completely empty, this string is simply - * returned. If null, an empty string is used. + * @param glueForTwoItems If only two items are in the set, then this glue is used instead. If it is null, then + * lastGlue is used instead. + * @param empty If the set is completely empty, this string is simply returned. If null, an empty string is used. + * @param renderer The item renderer. This renders each item in the set, one at a time. If null, toString will be + * used by default on each item. * @return The concatenated string */ - public static String Join(Set set, String glue, String lastGlue, String glueForTwoItems, String empty, Renderer renderer) { - final List list = new ArrayList(set); + public static String Join(Set set, String glue, String lastGlue, String glueForTwoItems, String empty, + Renderer renderer) { + final List list = new ArrayList<>(set); return doJoin(new ItemGetter() { @Override @@ -211,99 +195,88 @@ public boolean isEmpty() { } /** - * Joins an array together (using StringBuilder's { - * - * @see StringBuilder#append(Object)} method to "toString" the Object) using - * the specified string for glue. + * Joins an array together (using StringBuilder's {@link StringBuilder#append(Object)} method + * to "toString" the Object) using the specified string for glue. + * @param The array type * @param list The array to concatenate * @param glue The glue to use * @return The concatenated string */ - public static String Join(Object[] list, String glue) { + public static String Join(T[] list, String glue) { return Join(list, glue, null, null, null); } /** - * Joins an array together (using StringBuilder's { - * - * @see StringBuilder#append(Object)} method to "toString" the Object) using - * the specified string for glue. If lastGlue is null, it is the same as - * glue, but otherwise it is used to glue just the last two items together, - * which is useful for lists that are being read by a human, to have a - * proper conjunction at the end. + * Joins an array together (using StringBuilder's {@link StringBuilder#append(Object)} method + * to "toString" the Object) using the specified string for glue. + * If lastGlue is null, it is the same as glue, but otherwise it is used to glue just the last two items together, + * which is useful for lists that are being read by a human, to have a proper conjunction at the end. + * @param The array type * @param list The array to concatenate * @param glue The glue to use * @param lastGlue The glue for the last two elements * @return The concatenated string */ - public static String Join(Object[] list, String glue, String lastGlue) { + public static String Join(T[] list, String glue, String lastGlue) { return Join(list, glue, lastGlue, null, null); } /** - * Joins an array together (using StringBuilder's { - * - * @see StringBuilder#append(Object)} method to "toString" the Object) using - * the specified string for glue. If lastGlue is null, it is the same as - * glue, but otherwise it is used to glue just the last two items together, - * which is useful for lists that are being read by a human, to have a - * proper conjunction at the end. + * Joins an array together (using StringBuilder's {@link StringBuilder#append(Object)} method + * to "toString" the Object) using the specified string for glue. + * If lastGlue is null, it is the same as glue, but otherwise it is used to glue just the last two items together, + * which is useful for lists that are being read by a human, to have a proper conjunction at the end. + * @param The array type * @param list The array to concatenate * @param glue The glue to use * @param lastGlue The glue for the last two elements - * @param glueForTwoItems If only two items are in the array, then this glue - * is used instead. If it is null, then lastGlue is used instead. + * @param glueForTwoItems If only two items are in the array, then this glue is used instead. If it is null, then + * lastGlue is used instead. * @return The concatenated string */ - public static String Join(Object[] list, String glue, String lastGlue, String glueForTwoItems) { + public static String Join(T[] list, String glue, String lastGlue, String glueForTwoItems) { return Join(list, glue, lastGlue, glueForTwoItems, null); } /** - * Joins an array together (using StringBuilder's { - * - * @see StringBuilder#append(Object)} method to "toString" the Object) using - * the specified string for glue. If lastGlue is null, it is the same as - * glue, but otherwise it is used to glue just the last two items together, - * which is useful for lists that are being read by a human, to have a - * proper conjunction at the end. + * Joins an array together (using StringBuilder's {@link StringBuilder#append(Object)} method + * to "toString" the Object) using the specified string for glue. + * If lastGlue is null, it is the same as glue, but otherwise it is used to glue just the last two items together, + * which is useful for lists that are being read by a human, to have a proper conjunction at the end. + * @param The array type * @param list The array to concatenate * @param glue The glue to use * @param lastGlue The glue for the last two elements - * @param glueForTwoItems If only two items are in the array, then this glue - * is used instead. If it is null, then lastGlue is used instead. - * @param empty If the array is completely empty, this string is simply - * returned. If null, an empty string is used. + * @param glueForTwoItems If only two items are in the array, then this glue is used instead. If it is null, then + * lastGlue is used instead. + * @param empty If the array is completely empty, this string is simply returned. If null, an empty string is used. * @return The concatenated string */ - public static String Join(Object[] list, String glue, String lastGlue, String glueForTwoItems, String empty) { + public static String Join(T[] list, String glue, String lastGlue, String glueForTwoItems, String empty) { return Join(list, glue, lastGlue, glueForTwoItems, empty, null); } /** - * Joins an array together (using StringBuilder's { - * - * @see StringBuilder#append(Object)} method to "toString" the Object) using - * the specified string for glue. If lastGlue is null, it is the same as - * glue, but otherwise it is used to glue just the last two items together, - * which is useful for lists that are being read by a human, to have a - * proper conjunction at the end. + * Joins an array together (using StringBuilder's {@link StringBuilder#append(Object)} method + * to "toString" the Object) using the specified string for glue. + * If lastGlue is null, it is the same as glue, but otherwise it is used to glue just the last two items together, + * which is useful for lists that are being read by a human, to have a proper conjunction at the end. + * @param The array type * @param list The array to concatenate * @param glue The glue to use * @param lastGlue The glue for the last two elements - * @param glueForTwoItems If only two items are in the array, then this glue - * is used instead. If it is null, then lastGlue is used instead. - * @param empty If the array is completely empty, this string is simply - * returned. If null, an empty string is used. - * @param renderer The item renderer. This renders each item in the list, - * one at a time. If null, toString will be used by default on each item. + * @param glueForTwoItems If only two items are in the array, then this glue is used instead. If it is null, then + * lastGlue is used instead. + * @param empty If the array is completely empty, this string is simply returned. If null, an empty string is used. + * @param renderer The item renderer. This renders each item in the list, one at a time. If null, toString will be + * used by default on each item. * @return The concatenated string */ - public static String Join(final Object[] list, String glue, String lastGlue, String glueForTwoItems, String empty, Renderer renderer) { - return doJoin(new ItemGetter() { + public static String Join(final T[] list, String glue, String lastGlue, String glueForTwoItems, String empty, Renderer renderer) { + return doJoin(new ItemGetter() { @Override - public Object get(int index) { + public T get(int index) { return list[index]; } @@ -320,10 +293,8 @@ public boolean isEmpty() { } /** - * Joins a list together (using StringBuilder's { - * - * @see StringBuilder#append(Object)} method to "toString" the Object) using - * the specified string for glue. + * Joins a list together (using StringBuilder's {@link StringBuilder#append(Object)} method + * to "toString" the Object) using the specified string for glue. * @param list The list to concatenate * @param glue The glue to use * @return The concatenated string @@ -333,13 +304,10 @@ public static String Join(List list, String glue) { } /** - * Joins a list together (using StringBuilder's { - * - * @see StringBuilder#append(Object)} method to "toString" the Object) using - * the specified string for glue. If lastGlue is null, it is the same as - * glue, but otherwise it is used to glue just the last two items together, - * which is useful for lists that are being read by a human, to have a - * proper conjunction at the end. + * Joins a list together (using StringBuilder's {@link StringBuilder#append(Object)} method + * to "toString" the Object) using the specified string for glue. + * If lastGlue is null, it is the same as glue, but otherwise it is used to glue just the last two items together, + * which is useful for lists that are being read by a human, to have a proper conjunction at the end. * @param list The list to concatenate * @param glue The glue to use * @param lastGlue The glue for the last two elements @@ -350,18 +318,15 @@ public static String Join(List list, String glue, String lastGlue) { } /** - * Joins a list together (using StringBuilder's { - * - * @see StringBuilder#append(Object)} method to "toString" the Object) using - * the specified string for glue. If lastGlue is null, it is the same as - * glue, but otherwise it is used to glue just the last two items together, - * which is useful for lists that are being read by a human, to have a - * proper conjunction at the end. + * Joins a list together (using StringBuilder's {@link StringBuilder#append(Object)} method + * to "toString" the Object) using the specified string for glue. + * If lastGlue is null, it is the same as glue, but otherwise it is used to glue just the last two items together, + * which is useful for lists that are being read by a human, to have a proper conjunction at the end. * @param list The list to concatenate * @param glue The glue to use * @param lastGlue The glue for the last two elements - * @param glueForTwoItems If only two items are in the list, then this glue - * is used instead. If it is null, then lastGlue is used instead. + * @param glueForTwoItems If only two items are in the list, then this glue is used instead. If it is null, then + * lastGlue is used instead. * @return The concatenated string */ public static String Join(List list, String glue, String lastGlue, String glueForTwoItems) { @@ -369,20 +334,16 @@ public static String Join(List list, String glue, String lastGlue, String glueFo } /** - * Joins a list together (using StringBuilder's { - * - * @see StringBuilder#append(Object)} method to "toString" the Object) using - * the specified string for glue. If lastGlue is null, it is the same as - * glue, but otherwise it is used to glue just the last two items together, - * which is useful for lists that are being read by a human, to have a - * proper conjunction at the end. + * Joins a list together (using StringBuilder's {@link StringBuilder#append(Object)} method + * to "toString" the Object) using the specified string for glue. + * If lastGlue is null, it is the same as glue, but otherwise it is used to glue just the last two items together, + * which is useful for lists that are being read by a human, to have a proper conjunction at the end. * @param list The list to concatenate * @param glue The glue to use * @param lastGlue The glue for the last two elements - * @param glueForTwoItems If only two items are in the list, then this glue - * is used instead. If it is null, then lastGlue is used instead. - * @param empty If the list is completely empty, this string is simply - * returned. If null, an empty string is used. + * @param glueForTwoItems If only two items are in the list, then this glue is used instead. If it is null, then + * lastGlue is used instead. + * @param empty If the list is completely empty, this string is simply returned. If null, an empty string is used. * @return The concatenated string */ public static String Join(final List list, String glue, String lastGlue, String glueForTwoItems, String empty) { @@ -390,25 +351,23 @@ public static String Join(final List list, String glue, String lastGlue, String } /** - * Joins a list together (using StringBuilder's { - * - * @see StringBuilder#append(Object)} method to "toString" the Object) using - * the specified string for glue. If lastGlue is null, it is the same as - * glue, but otherwise it is used to glue just the last two items together, - * which is useful for lists that are being read by a human, to have a - * proper conjunction at the end. + * Joins a list together (using StringBuilder's {@link StringBuilder#append(Object)} method + * to "toString" the Object) using the specified string for glue. + * If lastGlue is null, it is the same as glue, but otherwise it is used to glue just the last two items together, + * which is useful for lists that are being read by a human, to have a proper conjunction at the end. + * @param The list type * @param list The list to concatenate * @param glue The glue to use - * @param lastGlue The glue for the last two elements - * @param glueForTwoItems If only two items are in the list, then this glue - * is used instead. If it is null, then lastGlue is used instead. - * @param empty If the list is completely empty, this string is simply - * returned. If null, an empty string is used. - * @param renderer The item renderer. This renders each item in the list, - * one at a time. If null, toString will be used by default on each item. + * @param lastGlue The glue for the last two elements. If it is null, then glue is used instead. + * @param glueForTwoItems If only two items are in the list, then this glue is used instead. If it is null, then + * lastGlue is used instead. + * @param empty If the list is completely empty, this string is simply returned. If null, an empty string is used. + * @param renderer The item renderer. This renders each item in the list, one at a time. If null, toString will be + * used by default on each item. * @return The concatenated string */ - public static String Join(final List list, String glue, String lastGlue, String glueForTwoItems, String empty, Renderer renderer) { + public static String Join(final List list, String glue, String lastGlue, String glueForTwoItems, + String empty, Renderer renderer) { return doJoin(new ItemGetter() { @Override @@ -441,12 +400,12 @@ public boolean isEmpty() { * @return */ private static String doJoin(ItemGetter items, String glue, String lastGlue, String glueForTwoItems, String empty, Renderer renderer) { - if (renderer == null) { + if(renderer == null) { renderer = new Renderer() { @Override public String toString(T item) { - if (item == null) { + if(item == null) { return "null"; } else { return item.toString(); @@ -454,25 +413,25 @@ public String toString(T item) { } }; } - if (lastGlue == null) { + if(lastGlue == null) { lastGlue = glue; } - if (glueForTwoItems == null) { + if(glueForTwoItems == null) { glueForTwoItems = lastGlue; } - if (items.isEmpty()) { + if(items.isEmpty()) { return empty == null ? "" : empty; - } else if (items.size() == 2) { + } else if(items.size() == 2) { StringBuilder b = new StringBuilder(); return b.append(renderer.toString(items.get(0))) .append(glueForTwoItems) .append(renderer.toString(items.get(1))).toString(); } else { StringBuilder b = new StringBuilder(); - for (int i = 0; i < items.size(); i++) { + for(int i = 0; i < items.size(); i++) { T o = items.get(i); - if (i != 0) { - if (i == items.size() - 1) { + if(i != 0) { + if(i == items.size() - 1) { b.append(lastGlue); } else { b.append(glue); @@ -485,6 +444,7 @@ public String toString(T item) { } private static interface ItemGetter { + T get(int index); int size(); @@ -498,6 +458,7 @@ private static interface ItemGetter { * @param The type of each item */ public static interface Renderer { + /** * * @param item @@ -511,9 +472,8 @@ private static int minimum(int a, int b, int c) { } /** - * Returns the levenshtein distance of two character sequences. For - * instance, "123" and "133" would have a string distance of 1, while "123" - * and "123" would be 0, since they are the same string. + * Returns the levenshtein distance of two character sequences. For instance, "123" and "133" would have a string + * distance of 1, while "123" and "123" would be 0, since they are the same string. * * @param str1 * @param str2 @@ -523,15 +483,15 @@ public static int LevenshteinDistance(CharSequence str1, CharSequence str2) { int[][] distance = new int[str1.length() + 1][str2.length() + 1]; - for (int i = 0; i <= str1.length(); i++) { + for(int i = 0; i <= str1.length(); i++) { distance[i][0] = i; } - for (int j = 0; j <= str2.length(); j++) { + for(int j = 0; j <= str2.length(); j++) { distance[0][j] = j; } - for (int i = 1; i <= str1.length(); i++) { - for (int j = 1; j <= str2.length(); j++) { + for(int i = 1; i <= str1.length(); i++) { + for(int j = 1; j <= str2.length(); j++) { distance[i][j] = minimum( distance[i - 1][j] + 1, distance[i][j - 1] + 1, @@ -549,24 +509,27 @@ public static int LevenshteinDistance(CharSequence str1, * * this is "a 'quoted'" '\'string\'' * - * would parse into 4 arguments, individually, "this", "is", "a 'quoted'", - * "'string'". It essentially handles the very basic case of command line - * argument parsing. + * would parse into 4 arguments, individually, "this", "is", "a 'quoted'", "'string'". It essentially handles the + * very basic case of command line argument parsing. * * @param args * @return */ public static List ArgParser(String args) { - List arguments = new ArrayList(); + List arguments = new ArrayList<>(); StringBuilder buf = new StringBuilder(); char escape = 0; char quote = 0; - boolean was_quote = false; - for (int i = 0; i < args.length(); i++) { + boolean wasQuote = false; + for(int i = 0; i < args.length(); i++) { char ch = args.charAt(i); - if (quote != 0) { // we're in a quote - if (escape != 0) { // we're in an escape too - if (ch == quote) { // escaping the same quote gives just that quote + char ch2 = 0; + if(args.length() > i + 1) { + ch2 = args.charAt(i + 1); + } + if(quote != 0) { // we're in a quote + if(escape != 0) { // we're in an escape too + if(ch == quote) { // escaping the same quote gives just that quote buf.append(ch); } else { // escaping anything else gives the escape and char as written buf.append(escape); @@ -575,47 +538,51 @@ public static List ArgParser(String args) { // in either case, this terminates the escape. escape = 0; continue; - } else if (ch == quote) { // Specifying the same quote again terminates the quote. + } else if(ch == quote) { // Specifying the same quote again terminates the quote. quote = 0; - was_quote = true; + wasQuote = true; continue; } - } else { - if (escape != 0) { - // all escapes outside quotes which are supported simply output the - // second character, as we aren't handling special ones like \t or \n - buf.append(ch); - escape = 0; - continue; - } else { // outside of quotes and escapes - switch (ch) { - case ' ': // we can tokenize - if (was_quote || buf.length() != 0) { - arguments.add(buf.toString()); - buf = new StringBuilder(); - was_quote = false; - } - continue; - case '"': // we can start quotes - case '\'': - quote = ch; - continue; - } + } else if(escape != 0) { + // all escapes outside quotes which are supported simply output the + // second character, as we aren't handling special ones like \t or \n + buf.append(ch); + escape = 0; + continue; + } else { // outside of quotes and escapes + switch(ch) { + case ' ': // we can tokenize + if(wasQuote || buf.length() != 0) { + arguments.add(buf.toString()); + buf = new StringBuilder(); + wasQuote = false; + } + continue; + case '"': // we can start quotes + case '\'': + quote = ch; + continue; } } // escape handling and default handling can fall through from either branch to here - switch (ch) { - case '\\': - escape = ch; - break; - default: - buf.append(ch); + if(ch == '\\' && ch2 == quote) { + buf.append(ch2); + i++; + } else { + buf.append(ch); } - } - if (escape != 0) { // makes trailing escapes be appended (erroneous string, though, IMO) +// switch(ch) { +// case '\\': +// escape = ch; +// break; +// default: +// buf.append(ch); +// } + } + if(escape != 0) { // makes trailing escapes be appended (erroneous string, though, IMO) buf.append(escape); } - if (was_quote || buf.length() != 0) { // add the final string + if(wasQuote || buf.length() != 0) { // add the final string arguments.add(buf.toString()); } return arguments; @@ -623,7 +590,7 @@ public static List ArgParser(String args) { public static String trimLeft(String str) { //If the argument is null then return empty string - if (str == null) { + if(str == null) { return ""; } @@ -632,7 +599,7 @@ public static String trimLeft(String str) { * If it is, use substring to make a new String that starts after the space. */ int len = 0; - while (str.charAt(len) == ' ') { + while(str.charAt(len) == ' ') { len++; } return str.substring(len); @@ -640,7 +607,7 @@ public static String trimLeft(String str) { public static String trimRight(String str) { //If the argument is null then return empty string - if (str == null) { + if(str == null) { return ""; } @@ -648,7 +615,7 @@ public static String trimRight(String str) { * In the code, take the length of the string and use it to determine if the last character is a space. */ int len = str.length(); - while (len > 0 && str.charAt(len - 1) == ' ') { + while(len > 0 && str.charAt(len - 1) == ' ') { len--; } str = str.substring(0, len); @@ -664,7 +631,7 @@ public static String trimRight(String str) { */ public static String[] trimSplit(String string, String regex) { String[] split = string.split(regex); - for (int i = 0; i < split.length; i++) { + for(int i = 0; i < split.length; i++) { split[i] = split[i].trim(); } return split; @@ -679,24 +646,24 @@ public static String[] trimSplit(String string, String regex) { * @return */ public static String replaceLast(String string, String regex, String replacement) { - if (regex == null) { + if(regex == null) { return string; } - if (string == null) { + if(string == null) { return null; } - if (regex.length() > string.length()) { + if(regex.length() > string.length()) { //It can't be contained in here return string; } Matcher m = Pattern.compile(regex).matcher(string); int start = -1; int end = -1; - while (m.find()) { + while(m.find()) { start = m.start(); end = m.end(); } - if (start == -1 || end == -1) { + if(start == -1 || end == -1) { //Didn't find it, return the whole string return string; } else { @@ -707,8 +674,8 @@ public static String replaceLast(String string, String regex, String replacement /** * Convenience method for HumanReadableByteCount(bytes, true). * - * @param bytes - * @return + * @param bytes The total number of bytes. + * @return The number of bytes, rounded to the nearest uppermost unit. For instance, 1024 will return "1.0 kB" */ public static String HumanReadableByteCount(long bytes) { return HumanReadableByteCount(bytes, true); @@ -717,35 +684,46 @@ public static String HumanReadableByteCount(long bytes) { /** * Returns a human readable byte count, given a byte count. * - * @param bytes - * @param si - * @return + * @param bytes The total number of bytes. + * @param si If true, the unit division is 1000, if false, it's 1024. + * @return The number of bytes, rounded to the nearest uppermost unit. For instance, 1024 will return "1.0 kiB" or + * "1.0 kB" if si is true. */ public static String HumanReadableByteCount(long bytes, boolean si) { - int unit = si ? 1000 : 1024; - if (bytes < unit) { - return bytes + " B"; - } - int exp = (int) (Math.log(bytes) / Math.log(unit)); - String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i"); - return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); - } - - /** - * Returns a properly agreeing subject verb clause given a count, and - * singular subject. This version assumes that the plural subject can be - * made simply by appending s to the singular subject, which is - * not always true. This is useful in cases where forming a sentence - * requires different wording depending on the count. Usually, you might use - * a fairly complex tertiary statement, for instance: String message = "There " + (count==1?"is":"are") + - * " " + count + " test failure" + (count==1?"":"s"); This is time - * consuming, and easy to mess up or accidentally reverse. Instead, you can - * use this function. Note that this will add is or - * are for you. You need only to provide the count, singular - * subject, and plural subject. If the subject cannot be made plural with - * just an s, use - * {@link #PluralHelper(int, java.lang.String, java.lang.String)} instead. - * Usage example: + // Code copied from https://stackoverflow.com/a/3758880/731752 + if(si) { + String s = bytes < 0 ? "-" : ""; + long b = bytes == Long.MIN_VALUE ? Long.MAX_VALUE : Math.abs(bytes); + return b < 1000L ? bytes + " B" + : b < 999_950L ? String.format("%s%.1f kB", s, b / 1e3) + : (b /= 1000) < 999_950L ? String.format("%s%.1f MB", s, b / 1e3) + : (b /= 1000) < 999_950L ? String.format("%s%.1f GB", s, b / 1e3) + : (b /= 1000) < 999_950L ? String.format("%s%.1f TB", s, b / 1e3) + : (b /= 1000) < 999_950L ? String.format("%s%.1f PB", s, b / 1e3) + : String.format("%s%.1f EB", s, b / 1e6); + } else { + long b = bytes == Long.MIN_VALUE ? Long.MAX_VALUE : Math.abs(bytes); + return b < 1024L ? bytes + " B" + : b <= 0xfffccccccccccccL >> 40 ? String.format("%.1f KiB", bytes / 0x1p10) + : b <= 0xfffccccccccccccL >> 30 ? String.format("%.1f MiB", bytes / 0x1p20) + : b <= 0xfffccccccccccccL >> 20 ? String.format("%.1f GiB", bytes / 0x1p30) + : b <= 0xfffccccccccccccL >> 10 ? String.format("%.1f TiB", bytes / 0x1p40) + : b <= 0xfffccccccccccccL ? String.format("%.1f PiB", (bytes >> 10) / 0x1p40) + : String.format("%.1f EiB", (bytes >> 20) / 0x1p40); + } + } + + /** + * Returns a properly agreeing subject verb clause given a count, and singular subject. This version assumes that + * the plural subject can be made simply by appending s to the singular subject, which is not always + * true. This is useful in cases where forming a sentence requires different wording depending on the count. + * Usually, you might use a fairly complex tertiary statement, for instance: + * String message = "There " + (count==1?"is":"are") + " " + count + " test failure" + (count==1?"":"s"); + * This is time consuming, and easy to mess up or + * accidentally reverse. Instead, you can use this function. Note that this will add is or + * are for you. You need only to provide the count, singular subject, and plural subject. If the + * subject cannot be made plural with just an s, use + * {@link #PluralHelper(int, java.lang.String, java.lang.String)} instead. Usage example: * *
 	 * String message = "There " + PluralHelper(count, "test failure");
@@ -762,17 +740,14 @@ public static String PluralHelper(int count, String singular) {
 	}
 
 	/**
-	 * Returns a properly agreeing subject verb clause given a count, singular
-	 * subject, and plural subject. This is useful in cases where forming a
-	 * sentence requires different wording depending on the count. Usually, you
-	 * might use a fairly complex tertiary statement, for instance: String message = "There " + (count==1?"is":"are") +
-	 * " " + count + " test failure" + (count==1?"":"s"); This is time
-	 * consuming, and easy to mess up or accidentally reverse. Instead, you can
-	 * use this function. Note that this will add is or
-	 * are for you. You need only to provide the count, singular
-	 * subject, and plural subject. If the subject can be made plural with just
-	 * an s, use {@link #PluralHelper(int, java.lang.String)}
-	 * instead. Usage example:
+	 * Returns a properly agreeing subject verb clause given a count, singular subject, and plural subject. This is
+	 * useful in cases where forming a sentence requires different wording depending on the count. Usually, you might
+	 * use a fairly complex tertiary statement, for instance: String message = "There " + (count==1?"is":"are") +
+	 * " " + count + " test failure" + (count==1?"":"s"); This is time consuming, and easy to mess up or
+	 * accidentally reverse. Instead, you can use this function. Note that this will add is or
+	 * are for you. You need only to provide the count, singular subject, and plural subject. If the
+	 * subject can be made plural with just an s, use {@link #PluralHelper(int, java.lang.String)} instead.
+	 * Usage example:
 	 *
 	 * 
 	 * String message = "There " + PluralHelper(count, "fish", "fish");
@@ -790,12 +765,10 @@ public static String PluralHelper(int count, String singular, String plural) {
 	}
 
 	/**
-	 * For even more complex sentences, it may just be easiest to provide a
-	 * template, which will be replaced, if the count is singular or plural.
-	 * Both singularTemplate and pluralTemplate are expected to be String.format
-	 * templates with a %d in them, which will be replaced with the actual count
-	 * number. If the count == 1, then the singularTemplate will be used, else
-	 * the pluralTemplate will be used. Usage example:
+	 * For even more complex sentences, it may just be easiest to provide a template, which will be replaced, if the
+	 * count is singular or plural. Both singularTemplate and pluralTemplate are expected to be String.format templates
+	 * with a %d in them, which will be replaced with the actual count number. If the count == 1, then the
+	 * singularTemplate will be used, else the pluralTemplate will be used. Usage example:
 	 *
 	 * 
 	 * String message = PluralTemplateHelper(count, "I will buy %d car if it has a good price",
@@ -808,7 +781,7 @@ public static String PluralHelper(int count, String singular, String plural) {
 	 * @return
 	 */
 	public static String PluralTemplateHelper(int count, String singularTemplate, String pluralTemplate) {
-		if (count == 1) {
+		if(count == 1) {
 			return String.format(singularTemplate, count);
 		} else {
 			return String.format(pluralTemplate, count);
@@ -816,25 +789,31 @@ public static String PluralTemplateHelper(int count, String singularTemplate, St
 	}
 
 	/**
-	 * This is the system newline string. For instance, on windows, this would
-	 * likely be \r\n, and unix systems would likely be \n.
+	 * This is the system newline string. For instance, on windows, this would likely be \r\n, and unix systems would
+	 * likely be \n.
+	 */
+	public static final String NL = System.getProperty("line.separator");
+
+	/**
+	 * @deprecated Use {@link #NL} instead.
 	 */
-	public static final String nl = System.getProperty("line.separator");
+	@SuppressWarnings("checkstyle:constantname") // Fixing this violation might break dependents.
+	@Deprecated // Deprecated on 14-06-2018 dd-mm-yyyy.
+	public static final String nl = NL;
 
 	/**
-	 * This returns the system newline string. For instance, on windows, this
-	 * would likely return \r\n, and unix systems would likely return \n.
+	 * This returns the system newline string. For instance, on windows, this would likely return \r\n, and unix systems
+	 * would likely return \n.
 	 *
 	 * @return The system newline string.
 	 */
 	public static String nl() {
-		return nl;
+		return NL;
 	}
 
 	/**
-	 * Multiplies a string. For instance, stringMultiply(3, "abc") would return
-	 * "abcabcabc". If count is 0, an empty string is returned, and if count is
-	 * 1, the character sequence itself is returned.
+	 * Multiplies a string. For instance, stringMultiply(3, "abc") would return "abcabcabc". If count is 0, an empty
+	 * string is returned, and if count is 1, the character sequence itself is returned.
 	 *
 	 * @param count The repeat count
 	 * @param s The sequence to repeat
@@ -842,27 +821,26 @@ public static String nl() {
 	 * @throws IllegalArgumentException If count is less than 0.
 	 */
 	public static String stringMultiply(int count, CharSequence s) {
-		if (count < 0) {
+		if(count < 0) {
 			throw new IllegalArgumentException("Count must be greater than or equal to 0");
 		}
-		if (count == 0) {
+		if(count == 0) {
 			return "";
 		}
-		if (count == 1) {
+		if(count == 1) {
 			return s.toString();
 		}
 		//Ok, actually have to do the multiply now.
 		StringBuilder b = new StringBuilder(s.length() * count);
-		for (int i = 0; i < count; i++) {
+		for(int i = 0; i < count; i++) {
 			b.append(s);
 		}
 		return b.toString();
 	}
 
 	/**
-	 * Given a string, returns a string that could be printed out in Java source
-	 * code. That is, all escapable characters are reversed. The returned string
-	 * will already be surrounded by quotes.
+	 * Given a string, returns a string that could be printed out in Java source code. That is, all escapable characters
+	 * are reversed. The returned string will already be surrounded by quotes.
 	 *
 	 * @param s
 	 * @return
@@ -885,22 +863,22 @@ public static String toHex(byte[] bytes) {
 		return String.format("%0" + (bytes.length << 1) + "X", bi);
 	}
 
-
 	/**
-	 * Splits a string on word boundries.
+	 * Splits a string on word boundaries.
+	 *
 	 * @param text
 	 * @param len
-	 * @return 
+	 * @return
 	 */
 	public static List lineSplit(String text, int len) {
 		// return empty array for null text
-		if (text == null) {
+		if(text == null) {
 			return new ArrayList<>();
 		}
 
 		// return text if len is zero or less
 		// or text is less than length
-		if (len <= 0 || text.length() <= len) {
+		if(len <= 0 || text.length() <= len) {
 			return new ArrayList<>(Arrays.asList(new String[]{text}));
 		}
 
@@ -909,11 +887,11 @@ public static List lineSplit(String text, int len) {
 		StringBuilder line = new StringBuilder();
 		StringBuilder word = new StringBuilder();
 
-		for (int i = 0; i < chars.length; i++) {
+		for(int i = 0; i < chars.length; i++) {
 			word.append(chars[i]);
 
-			if (chars[i] == ' ') {
-				if ((line.length() + word.length()) > len) {
+			if(chars[i] == ' ') {
+				if((line.length() + word.length()) > len) {
 					lines.add(line.toString());
 					line.delete(0, line.length());
 				}
@@ -924,8 +902,8 @@ public static List lineSplit(String text, int len) {
 		}
 
 		// handle any extra chars in current word
-		if (word.length() > 0) {
-			if ((line.length() + word.length()) > len) {
+		if(word.length() > 0) {
+			if((line.length() + word.length()) > len) {
 				lines.add(line.toString());
 				line.delete(0, line.length());
 			}
@@ -933,10 +911,108 @@ public static List lineSplit(String text, int len) {
 		}
 
 		// handle extra line
-		if (line.length() > 0) {
+		if(line.length() > 0) {
 			lines.add(line.toString());
 		}
 
 		return lines;
 	}
+
+	/**
+	 * Calls {@link #lineWrap(java.lang.String, int, java.lang.String, boolean)} with newline string as \n, and
+	 * wrapLongWords true.
+	 *
+	 * @param str The string to word wrap
+	 * @param wrapLength The max length of the line
+	 * @return a line with newlines inserted, null if null input
+	 */
+	public static String lineWrap(String str, int wrapLength) {
+		return lineWrap(str, wrapLength, "\n", true);
+	}
+
+	/**
+	 * 

+ * Wraps a single line of text, identifying words by ' '.

+ * + *

+ * Leading spaces on a new line are stripped. Trailing spaces are not stripped.

+ * + *
+	 * WordUtils.wrap(null, *, *, *) = null
+	 * WordUtils.wrap("", *, *, *) = ""
+	 * 
+ * + * (Code from org.apache.commons.lang.WordUtils and slightly modified) + * + * @param str the String to be word wrapped, may be null + * @param wrapLength the column to wrap the words at, less than 1 is treated as 1 + * @param newLineStr the string to insert for a new line, null uses the system property line separator + * @param wrapLongWords true if long words (such as URLs) should be wrapped + * @return a line with newlines inserted, null if null input + */ + public static String lineWrap(String str, int wrapLength, String newLineStr, boolean wrapLongWords) { + if(str == null) { + return null; + } + if(newLineStr == null) { + newLineStr = OSUtils.GetLineEnding(); + } + if(wrapLength < 1) { + wrapLength = 1; + } + int inputLineLength = str.length(); + int offset = 0; + StringBuilder wrappedLine = new StringBuilder(inputLineLength + 32); + + while((inputLineLength - offset) > wrapLength) { + if(str.charAt(offset) == ' ') { + offset++; + continue; + } + int spaceToWrapAt = str.lastIndexOf(' ', wrapLength + offset); + + if(spaceToWrapAt >= offset) { + // normal case + wrappedLine.append(str.substring(offset, spaceToWrapAt)); + wrappedLine.append(newLineStr); + offset = spaceToWrapAt + 1; + + } else { + // really long word or URL + if(wrapLongWords) { + // wrap really long word one line at a time + wrappedLine.append(str.substring(offset, wrapLength + offset)); + wrappedLine.append(newLineStr); + offset += wrapLength; + } else { + // do not wrap really long word, just extend beyond limit + spaceToWrapAt = str.indexOf(' ', wrapLength + offset); + if(spaceToWrapAt >= 0) { + wrappedLine.append(str.substring(offset, spaceToWrapAt)); + wrappedLine.append(newLineStr); + offset = spaceToWrapAt + 1; + } else { + wrappedLine.append(str.substring(offset)); + offset = inputLineLength; + } + } + } + } + + // Whatever is left in line is short enough to just pass through + wrappedLine.append(str.substring(offset)); + + return wrappedLine.toString(); + } + + /** + * Works like {@link String#contains(java.lang.CharSequence)}, except case is ignored. + * + * @param container + * @param contains + * @return + */ + public static boolean containsIgnoreCase(String container, String contains) { + return container.toLowerCase().contains(contains.toLowerCase()); + } } diff --git a/src/main/java/com/laytonsmith/PureUtilities/Common/TemplateBuilder.java b/src/main/java/com/laytonsmith/PureUtilities/Common/TemplateBuilder.java index e740700599..39d0cc0712 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Common/TemplateBuilder.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Common/TemplateBuilder.java @@ -6,20 +6,16 @@ import java.util.regex.Pattern; /** - * A utility class for building templates using a standard template format. - * The format is limited, but very simple to use. Templates start with two percent - * signs, and end with two percent signs. Arguments may also be passed in. First, - * the template name is given, followed by the pipe character, with the first argument, - * followed by pipe characters separating each argument, finally closing out with - * two percent signs. + * A utility class for building templates using a standard template format. The format is limited, but very simple to + * use. Templates start with two percent signs, and end with two percent signs. Arguments may also be passed in. First, + * the template name is given, followed by the pipe character, with the first argument, followed by pipe characters + * separating each argument, finally closing out with two percent signs. * - * This is a simple, no argument template: %%template%% - * This is a one argument template: %%template|arg1%% - * This is a two argument template: %%template|arg1|arg2%% + * This is a simple, no argument template: %%template%% This is a one argument template: %%template|arg1%% This is a two + * argument template: %%template|arg1|arg2%% * - * On the implementation side, the template name and generator are provided. Once - * the template is encountered in the text, the generator is triggered, and the - * text to replace the template is returned. + * On the implementation side, the template name and generator are provided. Once the template is encountered in the + * text, the generator is triggered, and the text to replace the template is returned. */ public class TemplateBuilder { @@ -30,52 +26,55 @@ public class TemplateBuilder { /** * Creates a new TemplateBuilder with no templates in it. */ - public TemplateBuilder(){ + public TemplateBuilder() { templates = new HashMap<>(); } /** * Creates a new TemplateBuilder with the given templates in it. + * * @param templates */ - public TemplateBuilder(Map templates){ + public TemplateBuilder(Map templates) { this.templates = new HashMap<>(templates); } /** * Adds a new template to this builder. + * * @param name * @param replacement */ - public void addTemplate(String name, Generator replacement){ + public void addTemplate(String name, Generator replacement) { templates.put(name, replacement); } /** * Removes the specified template from this builder. + * * @param name */ - public void removeTemplate(String name){ + public void removeTemplate(String name) { templates.remove(name); } /** - * Sets the silent fail flag. This flag determines whether or not the build - * method will throw an exception if a template is asked for which does not - * exist. If the state is false, (the default) then the template engine will + * Sets the silent fail flag. This flag determines whether or not the build method will throw an exception if a + * template is asked for which does not exist. If the state is false, (the default) then the template engine will * silently fail, and replace the template tag with an empty string. + * * @param silentFail */ - public void setSilentFail(boolean silentFail){ + public void setSilentFail(boolean silentFail) { this.silentFail = silentFail; } /** - * Given the input with possible template tags, parses and replaces any - * templates, returning the rendered string. + * Given the input with possible template tags, parses and replaces any templates, returning the rendered string. + * * @param template - * @throws IllegalArgumentException If the silent fail flag is false, and - * the input text contains a template which does not exist. + * @throws IllegalArgumentException If the silent fail flag is false, and the input text contains a template which + * does not exist. * @return */ public String build(String template) throws IllegalArgumentException { @@ -83,8 +82,8 @@ public String build(String template) throws IllegalArgumentException { StringBuilder templateBuilder = new StringBuilder(); int lastMatch = 0; boolean appended = false; - while(m.find()){ - if(!appended){ + while(m.find()) { + if(!appended) { templateBuilder.append(template.substring(lastMatch, m.start())); appended = true; } @@ -97,17 +96,17 @@ public String build(String template) throws IllegalArgumentException { // //template = template.replaceAll("%%" + Pattern.quote(name) + ".*?%%", customTemplates.get(name)); // } // } - String [] tmplArgs = new String[0]; - if(m.group(2) != null && !m.group(2).equals("")){ + String[] tmplArgs = ArrayUtils.EMPTY_STRING_ARRAY; + if(m.group(2) != null && !m.group(2).isEmpty()) { //We have arguments //remove the initial |, then split tmplArgs = m.group(2).substring(1).split("\\|"); } - if(templates.containsKey(name)){ + if(templates.containsKey(name)) { String templateValue = templates.get(name).generate(tmplArgs); templateBuilder.append(templateValue); } else { - if(!silentFail){ + if(!silentFail) { throw new IllegalArgumentException("Template with name \"" + name + "\" was found in the input" + " text, but no such template exists."); } @@ -115,14 +114,15 @@ public String build(String template) throws IllegalArgumentException { lastMatch = m.end(); appended = false; } - if(!appended){ + if(!appended) { templateBuilder.append(template.substring(lastMatch)); } return templateBuilder.toString(); } public static interface Generator { - String generate(String ... args); + + String generate(String... args); } } diff --git a/src/main/java/com/laytonsmith/PureUtilities/Common/TimeConversionUtil.java b/src/main/java/com/laytonsmith/PureUtilities/Common/TimeConversionUtil.java new file mode 100644 index 0000000000..808382ec6b --- /dev/null +++ b/src/main/java/com/laytonsmith/PureUtilities/Common/TimeConversionUtil.java @@ -0,0 +1,109 @@ +package com.laytonsmith.PureUtilities.Common; + +import java.math.BigDecimal; + +/** + * This class provides various methods for working with time units, but ultimately converting human readable times + * to seconds or milliseconds. + * @author cailin + */ +public class TimeConversionUtil { + + public enum TimeUnit { + /** + * 1/1,000 of a second + */ + MILLISECOND(1), + /** + * 1 second is 1000 milliseconds + */ + SECOND(1000), + /** + * 1 minute is 60 seconds + */ + MINUTE(60 * 1000), + /** + * 1 hour is 60 minutes + */ + HOUR(60 * 60 * 1000), + /** + * 1 day is 24 hours + */ + DAY(24 * 60 * 60 * 1000), + /** + * 1 week is 7 days + */ + WEEK(7 * 24 * 60 * 60 * 1000), + /** + * While the definition of a month may vary depending on the particular month, this is a synonym for + * {@link #MONTH31} + */ + MONTH(31 * 24 * 60 * 60 * 1000), + /** + * This is a month with 28 days (typically only February in a non-leap year has this many days) + */ + MONTH28(28 * 24 * 60 * 60 * 1000), + /** + * This is a month with 29 days (typically only February in a leap year has this many days) + */ + MONTH29(29 * 24 * 60 * 60 * 1000), + /** + * This is a month with 30 days. (Typically April, June, September, and + * November have 30 days). + */ + MONTH30(30 * 24 * 60 * 60 * 1000), + /** + * This is a month with 31 days. This is a synonym for {@link #MONTH}. (Typically, January, March, May, July, + * August, October, and December have 31 days). + */ + MONTH31(31 * 24 * 60 * 60 * 1000), + /** + * While a year has a different number of days depending on whether or not this is a leap year, this is + * a synonym for {@link #YEAR365}. + */ + YEAR(365 * 24 * 60 * 60 * 1000), + /** + * This is a year with 365 days, the typical year length on non-leap years + */ + YEAR365(365 * 24 * 60 * 60 * 1000), + /** + * This is a year with 366 days, the typical year length on leap years + */ + YEAR366(366 * 24 * 60 * 60 * 1000); + + private final long factor; + private TimeUnit(long factor) { + this.factor = factor; + } + + protected long factor() { + return this.factor; + } + } + + /** + * This wraps the {@link #inMilliseconds(int, com.laytonsmith.PureUtilities.Common.TimeUtils.TimeUnit)} method, + * but rounds the result to the nearest second. + * @param number The number of time units you wish to convert + * @param unit The time unit you wish to use + * @return The number of seconds in the specified time unit + */ + public static long inSeconds(int number, TimeUnit unit) { + long ms = inMilliseconds(number, unit); + // Use precise division via Big Decimal + return Math.round(BigDecimal.valueOf(ms).divide(BigDecimal.valueOf(1000)).doubleValue()); + } + + /** + * Returns the number of milliseconds in the given time unit. For instance, + * {@code inMilliseconds(1, TimeUnit.MINUTE)} would return 60000 and + * {@code inMilliseconds(3, TimeUnit.MINUTE)} would return 180000. + * @param number The number of time units you wish to convert + * @param unit The time unit you wish to use + * @return The number of milliseconds in the specified time unit + */ + public static long inMilliseconds(int number, TimeUnit unit) { + return number * unit.factor(); + } + +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/Common/UIUtils.java b/src/main/java/com/laytonsmith/PureUtilities/Common/UIUtils.java index a76d14ca10..0c6016b0da 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Common/UIUtils.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Common/UIUtils.java @@ -2,6 +2,7 @@ import java.awt.Component; import java.awt.Desktop; +import java.awt.EventQueue; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.MouseInfo; @@ -9,33 +10,34 @@ import java.awt.Rectangle; import java.awt.Window; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; -import java.util.logging.Level; -import java.util.logging.Logger; +import javax.swing.JOptionPane; /** - * Provides common UI utilites + * Provides common UI utilities */ public class UIUtils { /** - * Centers the window on the current "active" monitor. The active monitor is - * defined as the monitor that the mouse is currently in. + * Centers the window on the current "active" monitor. The active monitor is defined as the monitor that the mouse + * is currently in. + * * @param w */ - public static void centerWindow(Window w){ + public static void centerWindow(Window w) { // For multimonitor support, we need to iterate the monitors GraphicsEnvironment g = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice[] devices = g.getScreenDevices(); Point mousePoint = MouseInfo.getPointerInfo().getLocation(); Rectangle primary = null; - for(GraphicsDevice gg : devices){ + for(GraphicsDevice gg : devices) { Rectangle r = gg.getDefaultConfiguration().getBounds(); primary = r; // Set this as the primary, so that primary will never be null. if(mousePoint.x > r.x && mousePoint.x < (r.x + r.width) - && mousePoint.y > r.y && mousePoint.y < (r.y + r.height)){ + && mousePoint.y > r.y && mousePoint.y < (r.y + r.height)) { // This is the "primary" monitor primary = r; break; @@ -53,43 +55,178 @@ public static void centerWindow(Window w){ /** * Centers a window on another window. + * * @param windowToCenter The window that will be moved * @param windowUponWhichToCenterOn The window that will be centered on */ - public static void centerWindowOnWindow(Window windowToCenter, Window windowUponWhichToCenterOn){ + public static void centerWindowOnWindow(Window windowToCenter, Window windowUponWhichToCenterOn) { windowToCenter.setLocationRelativeTo(windowUponWhichToCenterOn); } /** * Provides an easy way to setEnabled on multiple components at once. + * * @param enabled * @param components */ - public static void setEnabled(boolean enabled, Component ... components){ - for(Component component : components){ + public static void setEnabled(boolean enabled, Component... components) { + for(Component component : components) { component.setEnabled(enabled); } } /** - * Opens the system's default browser to the specified URI. + * Opens the system's default browser to the specified URI. Returns false if the web browser was + * definitely not opened. True returned means that as far as this code can be aware, the browser + * was launched, but there is no guarantee. + * * @param uri * @throws java.io.IOException */ - public static void openWebpage(URI uri) throws IOException { + public static boolean openWebpage(URI uri) throws IOException { Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null; - if(desktop != null && desktop.isSupported(Desktop.Action.BROWSE)){ + if(desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) { desktop.browse(uri); + return true; } + return false; } /** - * Opens the system's default browser to the specified URL. + * Opens the system's default browser to the specified URL. Returns false if the web browser was + * definitely not opened. True returned means that as far as this code can be aware, the browser + * was launched, but there is no guarantee. + * * @param url * @throws java.io.IOException * @throws java.net.URISyntaxException */ - public static void openWebpage(URL url) throws IOException, URISyntaxException { - openWebpage(url.toURI()); + public static boolean openWebpage(URL url) throws IOException, URISyntaxException { + return openWebpage(url.toURI()); } + + /** + * The various types of message boxes available. This primarily decides the icon to use, but generally determines + * the way the dialog looks. + */ + public static enum MessageType { + ERROR(JOptionPane.ERROR_MESSAGE), + INFORMATION(JOptionPane.INFORMATION_MESSAGE), + WARNING(JOptionPane.WARNING_MESSAGE), + QUESTION(JOptionPane.QUESTION_MESSAGE), + PLAIN(JOptionPane.PLAIN_MESSAGE); + + private final int joptionPaneType; + private MessageType(int joptionPaneType) { + this.joptionPaneType = joptionPaneType; + } + + public int getJOptionPaneType() { + return joptionPaneType; + } + } + + /** + * Provides a simple Yes/No confirm dialog. If the user clicks Yes, then true is returned. The type defaults + * to QUESTION. The actual creation of + * the dialog is done on the main UI thread using invokeAndWait (if necessary). + * Normally this method throws an InterruptedException + * or InvocationTargetException, but these are wrapped in a RuntimeException and rethrown. + * @param parent + * @param title + * @param message + * @return + */ + public static boolean confirm(Window parent, String title, String message) { + return confirm(parent, title, message, MessageType.QUESTION); + } + + /** + * Provides a simple Yes/No confirm dialog. If the user clicks Yes, then true is returned. The actual creation of + * the dialog is done on the main UI thread using invokeAndWait (if necessary). + * Normally this method throws an InterruptedException + * or InvocationTargetException, but these are wrapped in a RuntimeException and rethrown. + * @param parent + * @param title + * @param message + * @param type + * @return + */ + public static boolean confirm(Window parent, String title, String message, MessageType type) { + MutableObject ret = new MutableObject<>(); + Runnable r = () -> { + Object[] options = {"Yes", "No"}; + int n = JOptionPane.showOptionDialog(parent, + message, + title, + JOptionPane.YES_NO_OPTION, + type.getJOptionPaneType(), + null, //do not use a custom Icon + options, //the titles of buttons + options[0]); //default button title + ret.setObject(n == 0); + }; + if(EventQueue.isDispatchThread()) { + r.run(); + } else { + try { + EventQueue.invokeAndWait(r); + } catch (InterruptedException | InvocationTargetException ex) { + throw new RuntimeException(ex); + } + } + return ret.getObject(); + } + + /** + * Shows an alert message to the user, defaulting to the INFORMATION type. The dialog is shown on the UI thread. + * @param parent + * @param title + * @param message + */ + public static void alert(Window parent, String title, String message) { + alert(parent, title, message, MessageType.INFORMATION); + } + + /** + * Shows an alert message to the user. The dialog is shown on the UI thread. + * @param parent + * @param title + * @param message + * @param type + */ + public static void alert(Window parent, String title, String message, MessageType type) { + EventQueue.invokeLater(() -> { + JOptionPane.showMessageDialog(parent, + message, + title, + type.getJOptionPaneType()); + }); + } + + /** + * Prompts the user for input. The call is blocking, and returns the entered input as a string. + * @param parent + * @param title + * @param message + * @param type + * @return + */ + public static String prompt(Window parent, String title, String message, MessageType type) { + return JOptionPane.showInputDialog(parent, message, title, type.getJOptionPaneType()); + } + + /** + * Prompts the user for input. The call is blocking, and returns the entered input as a string. The type + * will be a QUESTION type. + * @param parent + * @param title + * @param message + * @return + */ + public static String prompt(Window parent, String title, String message) { + return JOptionPane.showInputDialog(parent, message, title, MessageType.QUESTION.getJOptionPaneType()); + } + + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/Common/Wildcard.java b/src/main/java/com/laytonsmith/PureUtilities/Common/Wildcard.java index 288f999e1f..3693322f10 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Common/Wildcard.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Common/Wildcard.java @@ -1,24 +1,19 @@ package com.laytonsmith.PureUtilities.Common; /** - * A Wildcard object is a regex-like matcher, which uses a greatly simplified - * syntax, and is useful only for specific application needs. A Wildcard is used - * to specify a fuzzy match on file patters, namespace patterns, or other - * segmented data. A Wildcard object must be constructed with a separator - * character and a pattern, and then data can be fed to it to be matched. - * Wildcards only support 3 special symbols, *, **, and ?, and support escaping - * of those characters for literals with a backslash. A * symbol matches all - * characters across one and only one segment, ** matches all characters across - * any number of segments, and ? matches any one character. For instance, if you - * have a file path: /home/user/files/file.html, then the separator would be a - * "/", and to match this file, you might use the pattern - * "/home/user/files/*.htm?" This pattern would also match - * /home/user/files/file2.htm, /home/user/files/file3.html, etc, but it would - * not match /home/user/files/resources/frame.html, because the last match does - * not span segments. However, "/home/user/files/**.htm?" would match. + * A Wildcard object is a regex-like matcher, which uses a greatly simplified syntax, and is useful only for specific + * application needs. A Wildcard is used to specify a fuzzy match on file patters, namespace patterns, or other + * segmented data. A Wildcard object must be constructed with a separator character and a pattern, and then data can be + * fed to it to be matched. Wildcards only support 3 special symbols, *, **, and ?, and support escaping of those + * characters for literals with a backslash. A * symbol matches all characters across one and only one segment, ** + * matches all characters across any number of segments, and ? matches any one character. For instance, if you have a + * file path: /home/user/files/file.html, then the separator would be a "/", and to match this file, you might use the + * pattern "/home/user/files/*.htm?" This pattern would also match /home/user/files/file2.htm, + * /home/user/files/file3.html, etc, but it would not match /home/user/files/resources/frame.html, because the last + * match does not span segments. However, "/home/user/files/**.htm?" would match. + * * - * */ public class Wildcard { - + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/Common/WinRegistry.java b/src/main/java/com/laytonsmith/PureUtilities/Common/WinRegistry.java new file mode 100644 index 0000000000..873eea4103 --- /dev/null +++ b/src/main/java/com/laytonsmith/PureUtilities/Common/WinRegistry.java @@ -0,0 +1,419 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.laytonsmith.PureUtilities.Common; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +import java.util.ArrayList; +import java.util.List; +import java.util.prefs.Preferences; + +import com.laytonsmith.PureUtilities.JavaVersion; + +public final class WinRegistry { + + public static final int HKEY_CURRENT_USER = 0x80000001; + public static final int HKEY_LOCAL_MACHINE = 0x80000002; + public static final int REG_SUCCESS = 0; + public static final int REG_NOTFOUND = 2; + public static final int REG_ACCESSDENIED = 5; + + private static final int KEY_ALL_ACCESS = 0xf003f; + private static final int KEY_READ = 0x20019; + private static final Preferences USER_ROOT = Preferences.userRoot(); + private static final Preferences SYSTEM_ROOT = Preferences.systemRoot(); + private static final Class USER_CLASS = USER_ROOT.getClass(); + + private static final boolean USE_LONG_HANDLES = JavaVersion.GetMajorVersion() >= 11; + + private static Method regOpenKey = null; + private static Method regCloseKey = null; + private static Method regQueryValueEx = null; + private static Method regEnumValue = null; + private static Method regQueryInfoKey = null; + private static Method regEnumKeyEx = null; + private static Method regCreateKeyEx = null; + private static Method regSetValueEx = null; + private static Method regDeleteKey = null; + private static Method regDeleteValue = null; + + static { + try { + Class handleClass = (USE_LONG_HANDLES ? long.class : int.class); + regOpenKey = USER_CLASS.getDeclaredMethod( + "WindowsRegOpenKey", new Class[] {handleClass, byte[].class, int.class}); + regCloseKey = USER_CLASS.getDeclaredMethod( + "WindowsRegCloseKey", new Class[] {handleClass}); + regQueryValueEx = USER_CLASS.getDeclaredMethod( + "WindowsRegQueryValueEx", new Class[] {handleClass, byte[].class}); + regEnumValue = USER_CLASS.getDeclaredMethod( + "WindowsRegEnumValue", new Class[] {handleClass, int.class, int.class}); + regQueryInfoKey = USER_CLASS.getDeclaredMethod( + "WindowsRegQueryInfoKey1", new Class[] {handleClass}); + regEnumKeyEx = USER_CLASS.getDeclaredMethod( + "WindowsRegEnumKeyEx", new Class[] {handleClass, int.class, int.class}); + regCreateKeyEx = USER_CLASS.getDeclaredMethod( + "WindowsRegCreateKeyEx", new Class[] {handleClass, byte[].class}); + regSetValueEx = USER_CLASS.getDeclaredMethod( + "WindowsRegSetValueEx", new Class[] {handleClass, byte[].class, byte[].class}); + regDeleteValue = USER_CLASS.getDeclaredMethod( + "WindowsRegDeleteValue", new Class[] {handleClass, byte[].class}); + regDeleteKey = USER_CLASS.getDeclaredMethod( + "WindowsRegDeleteKey", new Class[] {handleClass, byte[].class}); + regOpenKey.setAccessible(true); + regCloseKey.setAccessible(true); + regQueryValueEx.setAccessible(true); + regEnumValue.setAccessible(true); + regQueryInfoKey.setAccessible(true); + regEnumKeyEx.setAccessible(true); + regCreateKeyEx.setAccessible(true); + regSetValueEx.setAccessible(true); + regDeleteValue.setAccessible(true); + regDeleteKey.setAccessible(true); + } catch (NoSuchMethodException | SecurityException e) { + throw new Error(e); + } + } + + private WinRegistry() { + } + + /** + * Read a value from key and value name + * + * @param hkey HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE + * @param key + * @param valueName + * @return the value + * @throws IllegalArgumentException + * @throws IllegalAccessException + * @throws InvocationTargetException + */ + public static String readString(long hkey, String key, String valueName) + throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + if(hkey == HKEY_LOCAL_MACHINE) { + return readString(SYSTEM_ROOT, hkey, key, valueName); + } else if(hkey == HKEY_CURRENT_USER) { + return readString(USER_ROOT, hkey, key, valueName); + } else { + throw new IllegalArgumentException("hkey=" + hkey); + } + } + + private static String readString(Preferences root, long hkey, String key, String value) + throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + byte[] valb; + if(USE_LONG_HANDLES) { + long[] handles = (long[]) regOpenKey.invoke(root, new Object[] {hkey, toCstr(key), KEY_READ}); + if(handles[1] != REG_SUCCESS) { + return null; + } + valb = (byte[]) regQueryValueEx.invoke(root, new Object[] {handles[0], toCstr(value)}); + regCloseKey.invoke(root, new Object[] {handles[0]}); + } else { + int[] handles = (int[]) regOpenKey.invoke(root, new Object[] {(int) hkey, toCstr(key), KEY_READ}); + if(handles[1] != REG_SUCCESS) { + return null; + } + valb = (byte[]) regQueryValueEx.invoke(root, new Object[] {handles[0], toCstr(value)}); + regCloseKey.invoke(root, new Object[] {handles[0]}); + } + return (valb != null ? new String(valb).trim() : null); + } + + /** + * Read value(s) and value name(s) form given key + * + * @param hkey HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE + * @param key + * @return the value name(s) plus the value(s) + * @throws IllegalArgumentException + * @throws IllegalAccessException + * @throws InvocationTargetException + */ + public static Map readStringValues(long hkey, String key) + throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + if(hkey == HKEY_LOCAL_MACHINE) { + return readStringValues(SYSTEM_ROOT, hkey, key); + } else if(hkey == HKEY_CURRENT_USER) { + return readStringValues(USER_ROOT, hkey, key); + } else { + throw new IllegalArgumentException("hkey=" + hkey); + } + } + + private static Map readStringValues(Preferences root, long hkey, String key) + throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + HashMap results = new HashMap(); + if(USE_LONG_HANDLES) { + long[] handles = (long[]) regOpenKey.invoke(root, new Object[] {hkey, toCstr(key), KEY_READ}); + if(handles[1] != REG_SUCCESS) { + return null; + } + long[] info = (long[]) regQueryInfoKey.invoke(root, new Object[] {handles[0]}); + + int count = (int) info[0]; // count + int maxlen = (int) info[3]; // value length max + for(int index = 0; index < count; index++) { + byte[] name = (byte[]) regEnumValue.invoke(root, new Object[] {handles[0], index, (int) (maxlen + 1)}); + String value = readString(hkey, key, new String(name)); + results.put(new String(name).trim(), value); + } + regCloseKey.invoke(root, new Object[] {handles[0]}); + } else { + int[] handles = (int[]) regOpenKey.invoke(root, new Object[] {(int) hkey, toCstr(key), KEY_READ}); + if(handles[1] != REG_SUCCESS) { + return null; + } + int[] info = (int[]) regQueryInfoKey.invoke(root, new Object[] {handles[0]}); + + int count = info[0]; // count + int maxlen = info[3]; // value length max + for(int index = 0; index < count; index++) { + byte[] name = (byte[]) regEnumValue.invoke(root, new Object[] {handles[0], index, maxlen + 1}); + String value = readString(hkey, key, new String(name)); + results.put(new String(name).trim(), value); + } + regCloseKey.invoke(root, new Object[] {handles[0]}); + } + return results; + } + + /** + * Read the value name(s) from a given key + * + * @param hkey HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE + * @param key + * @return the value name(s) + * @throws IllegalArgumentException + * @throws IllegalAccessException + * @throws InvocationTargetException + */ + public static List readStringSubKeys(long hkey, String key) + throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + if(hkey == HKEY_LOCAL_MACHINE) { + return readStringSubKeys(SYSTEM_ROOT, hkey, key); + } else if(hkey == HKEY_CURRENT_USER) { + return readStringSubKeys(USER_ROOT, hkey, key); + } else { + throw new IllegalArgumentException("hkey=" + hkey); + } + } + + private static List readStringSubKeys(Preferences root, long hkey, String key) + throws IllegalArgumentException, IllegalAccessException, + InvocationTargetException { + List results = new ArrayList(); + if(USE_LONG_HANDLES) { + long[] handles = (long[]) regOpenKey.invoke(root, new Object[] {hkey, toCstr(key), KEY_READ}); + if(handles[1] != REG_SUCCESS) { + return null; + } + long[] info = (long[]) regQueryInfoKey.invoke(root, new Object[] {handles[0]}); + + int count = (int) info[0]; // Fix: info[2] was being used here with wrong results. Suggested by davenpcj, confirmed by Petrucio + int maxlen = (int) info[3]; // value length max + for(int index = 0; index < count; index++) { + byte[] name = (byte[]) regEnumKeyEx.invoke(root, new Object[] {handles[0], index, maxlen + 1}); + results.add(new String(name).trim()); + } + regCloseKey.invoke(root, new Object[] {handles[0]}); + } else { + int[] handles = (int[]) regOpenKey.invoke(root, new Object[] {(int) hkey, toCstr(key), KEY_READ}); + if(handles[1] != REG_SUCCESS) { + return null; + } + int[] info = (int[]) regQueryInfoKey.invoke(root, new Object[] {handles[0]}); + + int count = info[0]; // Fix: info[2] was being used here with wrong results. Suggested by davenpcj, confirmed by Petrucio + int maxlen = info[3]; // value length max + for(int index = 0; index < count; index++) { + byte[] name = (byte[]) regEnumKeyEx.invoke(root, new Object[] {handles[0], index, maxlen + 1}); + results.add(new String(name).trim()); + } + regCloseKey.invoke(root, new Object[] {handles[0]}); + } + return results; + } + + /** + * Create a key + * + * @param hkey HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE + * @param key + * @throws IllegalArgumentException + * @throws IllegalAccessException + * @throws InvocationTargetException + */ + public static void createKey(long hkey, String key) + throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + long rc; + Preferences prefs; + if(hkey == HKEY_LOCAL_MACHINE) { + prefs = SYSTEM_ROOT; + } else if(hkey == HKEY_CURRENT_USER) { + prefs = USER_ROOT; + } else { + throw new IllegalArgumentException("hkey=" + hkey); + } + if(USE_LONG_HANDLES) { + long[] ret = (long[]) createKey(prefs, hkey, key); + regCloseKey.invoke(prefs, new Object[] {ret[0]}); + rc = ret[1]; + } else { + int[] ret = (int[]) createKey(prefs, hkey, key); + regCloseKey.invoke(prefs, new Object[] {ret[0]}); + rc = ret[1]; + } + if(rc != REG_SUCCESS) { + throw new IllegalArgumentException("rc=" + rc + " key=" + key); + } + } + + private static Object createKey(Preferences root, long hkey, String key) + throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + if(USE_LONG_HANDLES) { + return (long[]) regCreateKeyEx.invoke(root, new Object[] {hkey, toCstr(key)}); + } else { + return (int[]) regCreateKeyEx.invoke(root, new Object[] {(int) hkey, toCstr(key)}); + } + } + + /** + * Write a value in a given key/value name + * + * @param hkey + * @param key + * @param valueName + * @param value + * @throws IllegalArgumentException + * @throws IllegalAccessException + * @throws InvocationTargetException + */ + public static void writeStringValue(long hkey, String key, String valueName, String value) + throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + if(hkey == HKEY_LOCAL_MACHINE) { + writeStringValue(SYSTEM_ROOT, hkey, key, valueName, value); + } else if(hkey == HKEY_CURRENT_USER) { + writeStringValue(USER_ROOT, hkey, key, valueName, value); + } else { + throw new IllegalArgumentException("hkey=" + hkey); + } + } + + private static void writeStringValue(Preferences root, long hkey, String key, String valueName, String value) + throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + if(USE_LONG_HANDLES) { + long[] handles = (long[]) regOpenKey.invoke(root, new Object[] {hkey, toCstr(key), KEY_ALL_ACCESS}); + regSetValueEx.invoke(root, new Object[] {handles[0], toCstr(valueName), toCstr(value)}); + regCloseKey.invoke(root, new Object[] {handles[0]}); + } else { + int[] handles = (int[]) regOpenKey.invoke(root, new Object[] {(int) hkey, toCstr(key), KEY_ALL_ACCESS}); + regSetValueEx.invoke(root, new Object[] {handles[0], toCstr(valueName), toCstr(value)}); + regCloseKey.invoke(root, new Object[] {handles[0]}); + } + } + + /** + * Delete a given key + * + * @param hkey + * @param key + * @throws IllegalArgumentException + * @throws IllegalAccessException + * @throws InvocationTargetException + */ + public static void deleteKey(long hkey, String key) + throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + int rc = -1; + if(hkey == HKEY_LOCAL_MACHINE) { + rc = deleteKey(SYSTEM_ROOT, hkey, key); + } else if(hkey == HKEY_CURRENT_USER) { + rc = deleteKey(USER_ROOT, hkey, key); + } + if(rc != REG_SUCCESS) { + throw new IllegalArgumentException("rc=" + rc + " key=" + key); + } + } + + private static int deleteKey(Preferences root, long hkey, String key) + throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + // Can return: REG_NOTFOUND, REG_ACCESSDENIED, REG_SUCCESS. + return (int) regDeleteKey.invoke(root, new Object[] {(USE_LONG_HANDLES ? hkey : (int) hkey), toCstr(key)}); + } + + /** + * delete a value from a given key/value name + * + * @param hkey + * @param key + * @param value + * @throws IllegalArgumentException + * @throws IllegalAccessException + * @throws InvocationTargetException + */ + public static void deleteValue(long hkey, String key, String value) + throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + int rc = -1; + if(hkey == HKEY_LOCAL_MACHINE) { + rc = deleteValue(SYSTEM_ROOT, hkey, key, value); + } else if(hkey == HKEY_CURRENT_USER) { + rc = deleteValue(USER_ROOT, hkey, key, value); + } + if(rc != REG_SUCCESS) { + throw new IllegalArgumentException("rc=" + rc + " key=" + key + " value=" + value); + } + } + + // ===================== + private static int deleteValue(Preferences root, long hkey, String key, String value) + throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + int rc; + if(USE_LONG_HANDLES) { + long[] handles = (long[]) regOpenKey.invoke(root, new Object[] {hkey, toCstr(key), KEY_ALL_ACCESS}); + if(handles[1] != REG_SUCCESS) { + return (int) handles[1]; // can be REG_NOTFOUND, REG_ACCESSDENIED + } + rc = ((Integer) regDeleteValue.invoke(root, new Object[] {handles[0], toCstr(value)})).intValue(); + regCloseKey.invoke(root, new Object[] {handles[0]}); + } else { + int[] handles = (int[]) regOpenKey.invoke(root, new Object[] {(int) hkey, toCstr(key), KEY_ALL_ACCESS}); + if(handles[1] != REG_SUCCESS) { + return handles[1]; // can be REG_NOTFOUND, REG_ACCESSDENIED + } + rc = ((Integer) regDeleteValue.invoke(root, new Object[] {handles[0], toCstr(value)})).intValue(); + regCloseKey.invoke(root, new Object[] {handles[0]}); + } + return rc; + } + + // utility + private static byte[] toCstr(String str) { + byte[] result = new byte[str.length() + 1]; + + for(int i = 0; i < str.length(); i++) { + result[i] = (byte) str.charAt(i); + } + result[str.length()] = 0; + return result; + } +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/ConcurrentSingletonHashMap.java b/src/main/java/com/laytonsmith/PureUtilities/ConcurrentSingletonHashMap.java new file mode 100644 index 0000000000..4a54e51e27 --- /dev/null +++ b/src/main/java/com/laytonsmith/PureUtilities/ConcurrentSingletonHashMap.java @@ -0,0 +1,170 @@ +package com.laytonsmith.PureUtilities; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * This class provides a generic way to have a threadsafe map of singleton values, where a key maps to a single value, + * and where the value is only created if it doesn't exist and only once. + * + * There is a decent amount of complexity involved in this task, and so this class wraps the functionality. The class + * extends Map, so it can generally be used in place of other Map objects. + * + * Insertions will trigger synchronization, but given it is a singleton pool, this is assumed to not happen frequently. + * The put and remove methods will trigger an exception if they are called. Only the internal generator is allowed to + * insert values into the internal map, and values are not allowed to be removed. + * + * @author cailin + */ +public class ConcurrentSingletonHashMap implements Map { + + /* + * You might notice that no fields in this class are volatile. Normally, when you double lock, you must do + * something like this to be totally correct: + * + *
+	 * volatile Object value = null; // Note the volatility
+	 * construct() {
+	 *	Object result = value;
+	 *	if(result == null) {
+	 *		synchronized(result) {
+	 *		if(result == null) {
+	 *			result = new Object();
+	 *			value = result;
+	 *		}
+	 *		}
+	 *	}
+	 *	return result;
+	 * }
+	 * 
+ * + * Note that we are doing the double locking per usual, but the value is volatile. The local result value seems + * unnecessary at first, but the effect of this is that in cases where value is already initialized + * (i.e., most of the time), the volatile field is only accessed once (due to "return result;" instead of + * "return value;"), which can improve the method's overall performance by as much as 25 percent. + * + * However, in the case that we have before us, the ConcurrentHashMap handles this for us, by guaranteeing that + * we never get a value that is partially constructed in the get() method. + * + * + */ + private final Map map = new ConcurrentHashMap<>(); + private final ValueGenerator generator; + + public interface ValueGenerator { + + V generate(T key); + } + + public ConcurrentSingletonHashMap(ValueGenerator generator) { + this.generator = generator; + } + + @Override + public int size() { + return map.size(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + @Override + public V get(Object key) { + @SuppressWarnings("unchecked") + T k = (T) key; + // Usual case, it already exists. No synchronization. + if(map.containsKey(k)) { + return map.get(k); + } + // It does not exist. We must now synchronize. + synchronized(map) { + // It may have since been created since we got the lock + if(map.containsKey(k)) { + return map.get(k); + } + // It truly does not exist, so now we must create it, put it in the map, then return it. + V value = generator.generate(k); + map.put(k, value); + return value; + } + } + + /** + * This method unconditionally throws an exception. + * + * @param key + * @param value + * @return + * @throws UnsupportedOperationException Put operations are not allowed, and so this exception is always thrown. + */ + @Override + public V put(T key, V value) { + throw new UnsupportedOperationException("Put operations are not allowed in " + this.getClass().getSimpleName()); + } + + /** + * This method unconditionally throws an exception. + * + * @param key + * @return + * @throws UnsupportedOperationException Remove operations are not allowed, and so this exception is always thrown. + */ + @Override + public V remove(Object key) { + throw new UnsupportedOperationException("Remove operations are not allowed in " + this.getClass().getSimpleName()); + } + + /** + * This method unconditionally throws an exception. + * + * @param m + * @return + * @throws UnsupportedOperationException Put operations are not allowed, and so this exception is always thrown. + */ + @Override + public void putAll(Map m) { + throw new UnsupportedOperationException("Put operations are not allowed in " + this.getClass().getSimpleName()); + } + + /** + * This method unconditionally throws an exception. + * + * @param key + * @return + * @throws UnsupportedOperationException Remove operations are not allowed, and so this exception is always thrown. + */ + @Override + public void clear() { + throw new UnsupportedOperationException("Remove operations are not allowed in " + this.getClass().getSimpleName()); + } + + @Override + public Set keySet() { + return map.keySet(); + } + + @Override + public Collection values() { + return map.values(); + } + + @Override + public Set> entrySet() { + return map.entrySet(); + } + +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/DaemonManager.java b/src/main/java/com/laytonsmith/PureUtilities/DaemonManager.java index f1ce62ef89..a54fd71829 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/DaemonManager.java +++ b/src/main/java/com/laytonsmith/PureUtilities/DaemonManager.java @@ -1,31 +1,28 @@ - package com.laytonsmith.PureUtilities; import java.util.HashSet; import java.util.Set; /** - * A daemon manager allows for a layer on top of Java threads to determine - * if there are any threads open that are actually doing any processing. - * This allows for a thread to wait for all spinoff threads before exiting, whether - * or not those threads are daemon threads or not. All the methods in this class - * are threadsafe. + * A daemon manager allows for a layer on top of Java threads to determine if there are any threads open that are + * actually doing any processing. This allows for a thread to wait for all spinoff threads before exiting, whether or + * not those threads are daemon threads or not. All the methods in this class are threadsafe. */ public class DaemonManager { + private final Object lock = new Object(); private final Set threads = new HashSet<>(); private int count = 0; - + /** - * Sets a thread to "daemon" mode, meaning it is currently - * active. Null may be sent, in which case the current thread - * is used. You should - * always put a deactivateThread call for every activateThread call. + * Sets a thread to "daemon" mode, meaning it is currently active. Null may be sent, in which case the current + * thread is used. You should always put a deactivateThread call for every activateThread call. + * * @param t The thread to activate */ - public void activateThread(Thread t){ - synchronized(lock){ - if(t != null){ + public void activateThread(Thread t) { + synchronized(lock) { + if(t != null) { threads.add(t); } else { threads.add(Thread.currentThread()); @@ -33,15 +30,15 @@ public void activateThread(Thread t){ ++count; } } - + /** - * Sets a thread to "non daemon" mode, meaning it is currently - * inactive. If null, the current thread is used. + * Sets a thread to "non daemon" mode, meaning it is currently inactive. If null, the current thread is used. + * * @param t The thread to deactivate */ - public void deactivateThread(Thread t){ - synchronized(lock){ - if(t != null){ + public void deactivateThread(Thread t) { + synchronized(lock) { + if(t != null) { threads.remove(t); } else { threads.remove(Thread.currentThread()); @@ -50,27 +47,29 @@ public void deactivateThread(Thread t){ lock.notify(); } } - + /** * Returns an array of all active threads. - * @return + * + * @return */ - public Thread[] getActiveThreads(){ - synchronized(lock){ + public Thread[] getActiveThreads() { + synchronized(lock) { return threads.toArray(new Thread[threads.size()]); } } - + /** * Waits for all threads to finish, then returns. - * @throws InterruptedException + * + * @throws InterruptedException */ - public void waitForThreads() throws InterruptedException{ - synchronized(lock){ - while(count > 0){ + public void waitForThreads() throws InterruptedException { + synchronized(lock) { + while(count > 0) { lock.wait(); } } } - + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/ExecutionQueue.java b/src/main/java/com/laytonsmith/PureUtilities/ExecutionQueue.java index 12c95e38aa..99ce516f54 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/ExecutionQueue.java +++ b/src/main/java/com/laytonsmith/PureUtilities/ExecutionQueue.java @@ -1,287 +1,72 @@ package com.laytonsmith.PureUtilities; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Deque; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.SynchronousQueue; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; /** - * This class manages execution queues. A task added to a queue - * is guaranteed to be sequential with respect to other tasks in that - * queue, but not necessarily with respect to other tasks on other queues. - * Tasks will block the queue. - * + * */ -public class ExecutionQueue { - - private ExecutorService service; - private static int threadCount = 0; - private Map> queues; - private final Map locks; - private Map runningQueues; - private String defaultQueueName; - private Thread.UncaughtExceptionHandler uncaughtExceptionHandler = null; - private ThreadFactory threadFactory; - - public ExecutionQueue(String threadPrefix, String defaultQueueName){ - this(threadPrefix, defaultQueueName, null); - } - - /** - * Creates a new ExecutionQueue instance. - * @param threadPrefix The prefix to use when naming the threads - * @param defaultQueueName The name of the default queue - * @param exceptionHandler The uncaught exception handler for these queues - * @throws NullPointerException if either threadPrefix or defaultQueueName are null - */ - public ExecutionQueue(final String threadPrefix, String defaultQueueName, final Thread.UncaughtExceptionHandler exceptionHandler){ - if(threadPrefix == null || defaultQueueName == null){ - throw new NullPointerException(); - } - uncaughtExceptionHandler = exceptionHandler; - threadFactory = new ThreadFactory() { +public interface ExecutionQueue { - @Override - public Thread newThread(Runnable r) { - Thread t = new Thread(r, threadPrefix + "-" + (++threadCount)); - t.setDaemon(false); - return t; - } - }; - - queues = new HashMap>(); - this.defaultQueueName = defaultQueueName; - locks = new HashMap(); - runningQueues = new HashMap(); - } - - public final void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler exceptionHandler){ - this.uncaughtExceptionHandler = exceptionHandler; - } - /** - * Pushes a new runnable onto the end of the specified queue - * @param queue The named queue - * @param r + * Returns a list of active queues; that is, isRunning will return true for all these queues. + * + * @return */ - public final void push(DaemonManager dm, String queue, Runnable r){ - queue = prepareLock(queue); - synchronized(locks.get(queue)){ - Deque q = prepareQueue(queue); - q.addLast(r); - startQueue(dm, queue); - } - } - + List activeQueues(); + /** - * Pushes a new element to the front of the queue, barring other calls - * to pushFront, this runnable will go next. + * Clears all elements from this queue + * * @param queue - * @param r - */ - public final void pushFront(DaemonManager dm, String queue, Runnable r){ - queue = prepareLock(queue); - synchronized(locks.get(queue)){ - Deque q = prepareQueue(queue); - q.addFirst(r); - startQueue(dm, queue); - } - } - - /** - * Removes the last element added to the back of the queue - * @param queue */ - public final void remove(String queue){ - queue = prepareLock(queue); - synchronized(locks.get(queue)){ - Deque q = prepareQueue(queue); - try{ - q.removeLast(); - } catch(NoSuchElementException e){ - // - } - } - } - - + void clear(String queue); + /** - * Removes the front element from the queue - * @param queue + * Returns true if this queue has elements on the queue, or is currently running one. + * + * @param queue + * @return */ - public final void removeFront(String queue){ - try{ - pop(queue); - } catch(NoSuchElementException e){ - // - } - } - + boolean isRunning(String queue); + /** - * Clears all elements from this queue - * @param queue + * Pushes a new runnable onto the end of the specified queue + * + * @param queue The named queue + * @param r */ - public final void clear(String queue){ - queue = prepareLock(queue); - synchronized(locks.get(queue)){ - prepareQueue(queue).clear(); - } - } - + void push(DaemonManager dm, String queue, Runnable r); + /** - * Returns true if this queue has elements on the queue, - * or is currently running one. + * Pushes a new element to the front of the queue, barring other calls to pushFront, this runnable will go next. + * * @param queue - * @return - */ - public final boolean isRunning(String queue){ - queue = prepareLock(queue); - synchronized(locks.get(queue)){ - return runningQueues.containsKey(queue) && runningQueues.get(queue).equals(true); - } - } - - /** - * Returns a list of active queues; that is, isRunning will - * return true for all these queues. - * @return + * @param r */ - public final List activeQueues(){ - List q = new ArrayList(); - for(String queue : queues.keySet()){ - synchronized(locks.get(queue)){ - if(queues.containsKey(queue) && !queues.get(queue).isEmpty()){ - q.add(queue); - } - } - } - return q; - } - + void pushFront(DaemonManager dm, String queue, Runnable r); + /** - * Sets up a queue initially - * @param queueName + * Removes the last element added to the back of the queue + * + * @param queue */ - private Deque prepareQueue(String queueName){ - if(!queues.containsKey(queueName)){ - queues.put(queueName, new ArrayDeque()); - } - return queues.get(queueName); - } - - private String prepareLock(String queueName){ - if(queueName == null){ - queueName = defaultQueueName; - } - if(!locks.containsKey(queueName)){ - locks.put(queueName, new Object()); - } - return queueName; - } - + void remove(String queue); + /** - * Destroys a no-longer-in-use queue - * @param queueName + * Removes the front element from the queue + * + * @param queue */ - private void destroyQueue(String queueName){ - synchronized(locks.get(queueName)){ - queues.remove(queueName); - } - } - + void removeFront(String queue); + /** - * This method actually runs the queue management - * @param queueName + * Attempts an orderly shutdown of all existing tasks. */ - private void pumpQueue(String queueName){ - while(true){ - Runnable r; - synchronized(locks.get(queueName)){ - - r = pop(queueName); - } - r.run(); - synchronized(locks.get(queueName)){ - if(queues.get(queueName).isEmpty()){ - runningQueues.put(queueName, false); - destroyQueue(queueName); - break; - } - } - } - } - - private synchronized void startQueue(final DaemonManager dm, final String queue){ - synchronized(locks.get(queue)){ - if(!isRunning(queue)){ - //We need to create a new thread - runningQueues.put(queue, true); - if(dm != null){ - dm.activateThread(null); - } - if(service == null){ - service = new ThreadPoolExecutor(0, Integer.MAX_VALUE, - 50L, TimeUnit.MILLISECONDS, - new SynchronousQueue(), - threadFactory); - } - service.submit(new Runnable() { + void stopAll(); - @Override - public void run() { - try{ - pumpQueue(queue); - } catch(RuntimeException t){ - if(uncaughtExceptionHandler != null){ - uncaughtExceptionHandler.uncaughtException(Thread.currentThread(), t); - } else { - System.err.println("The queue \"" + queue + "\" threw an exception, and it was not handled."); - t.printStackTrace(System.err); - } - } finally { - if(dm != null){ - dm.deactivateThread(null); - } - } - } - }); - } - } - } - - private Runnable pop(String queue) throws NoSuchElementException{ - queue = prepareLock(queue); - synchronized(locks.get(queue)){ - Deque q = queues.get(queue); - return q.removeFirst(); - } - } - /** * Stops all executing tasks on a best effort basis. */ - public synchronized void stopAllNow(){ - if(service != null){ - service.shutdownNow(); - service = null; - } - } - - /** - * Attempts an orderly shutdown of all existing tasks. - */ - public synchronized void stopAll(){ - service.shutdown(); - service = null; - } - - + void stopAllNow(); + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/ExecutionQueueImpl.java b/src/main/java/com/laytonsmith/PureUtilities/ExecutionQueueImpl.java new file mode 100644 index 0000000000..c941bbdf99 --- /dev/null +++ b/src/main/java/com/laytonsmith/PureUtilities/ExecutionQueueImpl.java @@ -0,0 +1,296 @@ +package com.laytonsmith.PureUtilities; + +import com.laytonsmith.PureUtilities.Common.StreamUtils; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * This class manages execution queues. A task added to a queue is guaranteed to be sequential with respect to other + * tasks in that queue, but not necessarily with respect to other tasks on other queues. Tasks will block the queue. + * + */ +public class ExecutionQueueImpl implements ExecutionQueue { + + private ExecutorService service; + private static int threadCount = 0; + private Map> queues; + private final Map locks; + private Map runningQueues; + private String defaultQueueName; + private ThreadFactory threadFactory; + + /** + * Creates a new ExecutionQueue instance. + * + * @param threadPrefix The prefix to use when naming the threads + * @param defaultQueueName The name of the default queue + * @throws NullPointerException if either threadPrefix or defaultQueueName are null + */ + public ExecutionQueueImpl(final String threadPrefix, String defaultQueueName) { + if(threadPrefix == null || defaultQueueName == null) { + throw new NullPointerException(); + } + threadFactory = new ThreadFactory() { + + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r, threadPrefix + "-" + (++threadCount)); + t.setDaemon(false); + return t; + } + }; + + queues = new HashMap>(); + this.defaultQueueName = defaultQueueName; + locks = new HashMap(); + runningQueues = new HashMap(); + } + + /** + * Pushes a new runnable onto the end of the specified queue + * + * @param queue The named queue + * @param r + */ + @Override + public final void push(DaemonManager dm, String queue, Runnable r) { + queue = prepareLock(queue); + synchronized(locks.get(queue)) { + Deque q = prepareQueue(queue); + q.addLast(r); + startQueue(dm, queue); + } + } + + /** + * Pushes a new element to the front of the queue, barring other calls to pushFront, this runnable will go next. + * + * @param queue + * @param r + */ + @Override + public final void pushFront(DaemonManager dm, String queue, Runnable r) { + queue = prepareLock(queue); + synchronized(locks.get(queue)) { + Deque q = prepareQueue(queue); + q.addFirst(r); + startQueue(dm, queue); + } + } + + /** + * Removes the last element added to the back of the queue + * + * @param queue + */ + @Override + public final void remove(String queue) { + queue = prepareLock(queue); + synchronized(locks.get(queue)) { + Deque q = prepareQueue(queue); + try { + q.removeLast(); + } catch (NoSuchElementException e) { + // + } + } + } + + /** + * Removes the front element from the queue + * + * @param queue + */ + @Override + public final void removeFront(String queue) { + try { + pop(queue); + } catch (NoSuchElementException e) { + // + } + } + + /** + * Clears all elements from this queue + * + * @param queue + */ + @Override + public final void clear(String queue) { + queue = prepareLock(queue); + synchronized(locks.get(queue)) { + prepareQueue(queue).clear(); + } + } + + /** + * Returns true if this queue has elements on the queue, or is currently running one. + * + * @param queue + * @return + */ + @Override + public final boolean isRunning(String queue) { + queue = prepareLock(queue); + synchronized(locks.get(queue)) { + return runningQueues.containsKey(queue) && runningQueues.get(queue).equals(true); + } + } + + /** + * Returns a list of active queues; that is, isRunning will return true for all these queues. + * + * @return + */ + @Override + public final List activeQueues() { + List q = new ArrayList(); + for(String queue : queues.keySet()) { + synchronized(locks.get(queue)) { + if(queues.containsKey(queue) && !queues.get(queue).isEmpty()) { + q.add(queue); + } + } + } + return q; + } + + /** + * Sets up a queue initially + * + * @param queueName + */ + private Deque prepareQueue(String queueName) { + if(!queues.containsKey(queueName)) { + queues.put(queueName, new ArrayDeque()); + } + return queues.get(queueName); + } + + private String prepareLock(String queueName) { + if(queueName == null) { + queueName = defaultQueueName; + } + if(!locks.containsKey(queueName)) { + locks.put(queueName, new Object()); + } + return queueName; + } + + /** + * Destroys a no-longer-in-use queue + * + * @param queueName + */ + private void destroyQueue(String queueName) { + synchronized(locks.get(queueName)) { + queues.remove(queueName); + } + } + + /** + * This method actually runs the queue management + * + * @param queueName + */ + private void pumpQueue(String queueName) { + while(true) { + Runnable r; + synchronized(locks.get(queueName)) { + + r = pop(queueName); + } + r.run(); + synchronized(locks.get(queueName)) { + if(queues.get(queueName).isEmpty()) { + runningQueues.put(queueName, false); + destroyQueue(queueName); + break; + } + } + } + } + + private synchronized void startQueue(final DaemonManager dm, final String queue) { + synchronized(locks.get(queue)) { + if(!isRunning(queue)) { + //We need to create a new thread + runningQueues.put(queue, true); + if(dm != null) { + dm.activateThread(null); + } + if(service == null) { + service = new ThreadPoolExecutor(0, Integer.MAX_VALUE, + 50L, TimeUnit.MILLISECONDS, + new SynchronousQueue(), + threadFactory); + } + service.submit(new Runnable() { + + @Override + public void run() { + try { + pumpQueue(queue); + } catch (RuntimeException t) { + StreamUtils.GetSystemErr().println("The queue \"" + queue + "\" threw an exception, and it was not handled."); + t.printStackTrace(StreamUtils.GetSystemErr()); + } finally { + if(dm != null) { + dm.deactivateThread(null); + } + } + } + }); + } + } + } + + private Runnable pop(String queue) throws NoSuchElementException { + queue = prepareLock(queue); + synchronized(locks.get(queue)) { + Deque q = queues.get(queue); + if(q == null) { + throw new NoSuchElementException("The given queue does not exist."); + } + return q.removeFirst(); + } + } + + /** + * Stops all executing tasks on a best effort basis. + */ + @Override + public synchronized void stopAllNow() { + // shutdownNow doesn't fully clear the queue. It only clears the leading delay. + // Clearing the queue first fixes this, but there may be a better fix. + for(String queue : activeQueues()) { + clear(queue); + } + if(service != null) { + service.shutdownNow(); + service = null; + } + } + + /** + * Attempts an orderly shutdown of all existing tasks. + */ + @Override + public synchronized void stopAll() { + if(service != null) { + service.shutdown(); + service = null; + } + } + +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/ExhaustiveVisitor.java b/src/main/java/com/laytonsmith/PureUtilities/ExhaustiveVisitor.java new file mode 100644 index 0000000000..d57aa40eab --- /dev/null +++ b/src/main/java/com/laytonsmith/PureUtilities/ExhaustiveVisitor.java @@ -0,0 +1,318 @@ +package com.laytonsmith.PureUtilities; + +import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery; +import com.laytonsmith.PureUtilities.ClassLoading.ClassMirror.ClassMirror; +import com.laytonsmith.PureUtilities.Common.StringUtils; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * In programming, a Visitor pattern (also called Double Dispatch) is used to provide an easy and type safe way of + * selecting the method to handle an object of a specific type, without resorting to usage of {@code instanceof} chains + * or a {@code switch} statement. Consider the following example: + * + *

+ * interface UserID {}
+ * class PhoneNumber implements UserID {}
+ * class GeneratedIDV1 implements UserID {}
+ * class GeneratedIDV2 implements UserID {}
+ *
+ * void mainAmerican(UserID o) {
+ *   if(o instanceof PhoneNumber) handlePhoneNumberAmerican();
+ *   if(o instanceof GeneratedIDV1() handleGIDV1American();
+ *   if(o instanceof GeneratedIDV2) handleGIDV2American();
+ * }
+ * void mainCanadian(UserID o) {
+ *   if(o instanceof PhoneNumber) handlePhoneNumberCanadian();
+ *   if(o instanceof GeneratedIDV1() handleGIDV1Canadian();
+ *   if(o instanceof GeneratedIDV2) handleGIDV2Canadian();
+ * }
+ * 
+ * + * This code can be better written with a Visitor pattern + * + *

+ *
+ *   public static interface UserIDVisitable {
+ *	void accept(UserIDVisitor v);
+ *    }
+ *    public static interface UserIDVisitor {
+ *	void handle(PhoneNumber m);
+ *	void handle(GeneratedIDV1 c);
+ *	void handle(GeneratedIDV2 c);
+ *   }
+ *   public static interface UserID extends UserIDVisitable {}
+ *
+ *   public static class PhoneNumber implements UserID {
+ *	{@code @Override}
+ *	public void accept(UserIDVisitor visitor) {
+ *		visitor.handle(this);
+ *	}
+ *    }
+ *   public static abstract class GeneratedID implements UserID {}
+ *   public static class GeneratedIDV1 extends GeneratedID {
+ *	{@code @Override}
+ *	public void accept(UserIDVisitor visitor) {
+ *		visitor.handle(this);
+ *	}
+ *   }
+ *   public static class GeneratedIDV2 extends GeneratedID {
+ *	{@code @Override}
+ *	public void accept(UserIDVisitor visitor) {
+ *		visitor.handle(this);
+ *	}
+ *   }
+ *
+ *
+ *   public static class CanadianVisitor implements UserIDVisitor {
+ *
+ *	{@code @Override}
+ *	public void handle(PhoneNumber m) {
+ *		System.out.println("Canadian PhoneNumber");
+ *	}
+ *
+ *	{@code @Override}
+ *	public void handle(GeneratedIDV1 c) {
+ *		System.out.println("Canadian GeneratedIDV1");
+ *	}
+ *
+ *	{@code @Override}
+ *	public void handle(GeneratedIDV2 c) {
+ *		System.out.println("Canadian GeneratedIDV2");
+ *	}
+ *
+ *  }
+ *
+ *   public static class AmericanVisitor implements UserIDVisitor {
+ *
+ *	{@code @Override}
+ *	public void handle(PhoneNumber m) {
+ *		System.out.println("American PhoneNumber");
+ *	}
+ *
+ *	{@code @Override}
+ *	public void handle(GeneratedIDV1 c) {
+ *		System.out.println("American GeneratedIDV1");
+ *	}
+ *
+ *	{@code @Override}
+ *	public void handle(GeneratedIDV2 c) {
+ *		System.out.println("American GeneratedIDV2");
+ *	}
+ *
+ *    }
+ *
+ *    void mainAmerican(UserID o) {
+ *       o.accept(new AmericanVisitor());
+ *    }
+ *
+ *    void mainCanadian(UserID o) {
+ *	 o.accept(new CanadianVisitor());
+ *    }
+ * 
+ * + * Alas, this introduces a new problem. Now we have to go back to classes A, B, and C and make them extend Visitable. + * This might be ok, but in cases where we don't want to mix concerns, or perhaps in code we do not control, possible. + * Furthermore, it creates duplicated code, because each class will simply call visitor.handle(this) in all cases, and + * generally substantially increases our code size. Also, if we create a new subtype for which different handling is + * required, we must manually remember to update the UserIDVisitor interface with the new signature. + * + * {@link ExhaustiveVisitor} solves all three of these problems. Instead of the above code, we can now use the + * following: + * + *

+ *   public static interface UserID {}
+ *
+ *   public static class PhoneNumber implements UserID {}
+ *   public static abstract class GeneratedID implements UserID {}
+ *   public static class GeneratedIDV1 extends GeneratedID {}
+ *   public static class GeneratedIDV2 extends GeneratedID {}
+ *
+ *   {@code @ExhaustiveVisitor.VisitorInfo(baseClass = UserID.class, directSubclassOnly = false)}
+ *    public static class CanadianVisitor extends ExhaustiveVisitor<UserID> {
+ *	public void visit(PhoneNumber n) {
+ *		System.out.println("Canadian PhoneNumber");
+ *	}
+ *
+ *	public void visit(GeneratedIDV1 id) {
+ *		System.out.println("Canadian GeneratedIDV1");
+ *	}
+ *
+ *	public void visit(GeneratedIDV2 id) {
+ *		System.out.println("Canadian GeneratedIDV1");
+ *	}
+ *   }
+ *
+ *   {@code @ExhaustiveVisitor.VisitorInfo(baseClass = UserID.class, directSubclassOnly = false)}
+ *   public static class AmericanVisitor extends ExhaustiveVisitor<UserID> {
+ *	public void visit(PhoneNumber n) {
+ *		System.out.println("American PhoneNumber");
+ *	}
+ *
+ *	public void visit(GeneratedIDV1 id) {
+ *		System.out.println("American GeneratedIDV1");
+ *	}
+ *
+ *	public void visit(GeneratedIDV2 id) {
+ *		System.out.println("American GeneratedIDV1");
+ *	}
+ *   }
+ *
+ *   void mainAmerican(UserID o) {
+ *      new AmericanVisitor().visit(o);
+ *   }
+ *
+ *   void mainCanadian(UserID o) {
+ *	new CanadianVisitor().visit(o);
+ *   }
+ * 
+ * + * This code has multiple advantages: No need to change the base classes, less code overall, no code duplication, and no + * extra visitor interfaces need be created and maintained. With the addition of the compiler changes, this also becomes + * a compile error if you forget a required implementation of visit(). + * + * The VisitorInfo annotation is optional, but if provided, will control whether or not only direct subclasses must be + * implemented, or if all known subclasses must be. + * + * + * @author cailin + */ +public class ExhaustiveVisitor { + + /** + * The name of the visit method + */ + private static final String VISIT = "visit"; + + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + public static @interface VisitorInfo { + + /** + * If true, then the visitor subclass is only required to implement methods that represent direct subclasses of + * the base class. If an object needs visiting, it simply uses the method for which it extends from. If this is + * false, then all possible subclass values must be implemented, even if they simply call other visit methods. + * The default is false. + * + * @return + */ + boolean directSubclassOnly() default false; + } + + /** + * Calls the appropriate subclassed method based on the runtime type of the parameter passed in. + * + * @param object + */ + public final void visit(T object) { + VisitorInfo info = this.getClass().getDeclaredAnnotation(VisitorInfo.class); + Method candidate = null; + Class searchFor = object.getClass(); + for(Method m : this.getClass().getMethods()) { + if(VISIT.equals(m.getName())) { + Class visitParam = m.getParameterTypes()[0]; + if(info != null && info.directSubclassOnly()) { + if(visitParam.isAssignableFrom(searchFor)) { + candidate = m; + break; + } + } else { + if(visitParam == searchFor) { + candidate = m; + break; + } + } + } + } + if(candidate == null) { + throw new NoSuchMethodError("Missing implementation of method with signature (or superclass of): " + + " public void visit(" + searchFor.getName().replace("$", ".") + ") in class " + + this.getClass().getName()); + } + try { + candidate.invoke(this, object); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + throw new RuntimeException(ex); + } + } + + public static void verify(ClassMirror classMirror) throws ClassNotFoundException { + Class clazz = classMirror.loadClass(); + System.out.println("Verifying " + clazz); + VisitorInfo info = clazz.getAnnotation(VisitorInfo.class); + Class baseClass = classMirror.getGenerics() + .get(new ClassMirror<>(ExhaustiveVisitor.class).getClassReference()).get(0).loadClass(); + List uhohs = new ArrayList<>(); + // Make sure all public visit methods have only one parameter (which extends T) and return void + Set> handledClasses = new HashSet<>(); + for(Method m : clazz.getMethods()) { + if(m.getDeclaringClass() == ExhaustiveVisitor.class) { + // This is the method defined in this class, which doesn't need to be checked. Skip it. + continue; + } + if(VISIT.equals(m.getName()) && (m.getModifiers() & Modifier.PUBLIC) != 0) { + if(m.getReturnType() != void.class) { + uhohs.add("Return type of public visit() methods must be void, but " + + clazz.getName() + " " + m + " does not conform"); + } + if(m.getParameterTypes().length != 1) { + uhohs.add("Public visit() methods must accept exactly one parameter, but" + + clazz.getName() + " " + m + " does not conform"); + } else { + Class param = m.getParameterTypes()[0]; + if(baseClass.isAssignableFrom(param)) { + handledClasses.add(param); + } else { + uhohs.add("Public visit() methods parameters must extend the given base class's type, but the" + + " parameter of method " + m + " in " + clazz.getName() + + " has a disjoint type than " + + baseClass.getName() + ". Make the method non-public, or rename it, if you would" + + " like to keep the method."); + } + } + } + } + + // Make sure that all subclasses are accounted for, taking into + // account the value of directSubclassOnly + Set> needsToHandle = new HashSet<>(); + for(Class c : ClassDiscovery.getDefaultInstance().loadClassesThatExtend(baseClass)) { + if((c.getModifiers() & Modifier.ABSTRACT) != 0) { + // Abstract class, skip this, because an item can never be a concrete instance of this, and + // thus is not required to be implemented + continue; + } + if(info != null && info.directSubclassOnly()) { + if(c.getSuperclass() == baseClass || Arrays.asList(c.getInterfaces()).contains(c)) { + needsToHandle.add(c); + } + } else { + needsToHandle.add(c); + } + } + if(!needsToHandle.equals(handledClasses)) { + String s = clazz.getName() + " is missing needed implementations of the visit method. It is required" + + " that it handle the following: " + needsToHandle + ", however, it only handles the following:" + + " " + handledClasses + ". Please add the following implementations:\n"; + needsToHandle.removeAll(handledClasses); + for(Class n : needsToHandle) { + s += "public void visit(" + n.getName().replace("$", ".") + " obj) { /* Implement me */ }\n"; + } + uhohs.add(s); + } + if(!uhohs.isEmpty()) { + throw new RuntimeException(StringUtils.Join(uhohs, "\n")); + } + } + +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/FileLocations.java b/src/main/java/com/laytonsmith/PureUtilities/FileLocations.java index 10afa9208d..eac1af26cb 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/FileLocations.java +++ b/src/main/java/com/laytonsmith/PureUtilities/FileLocations.java @@ -1,87 +1,103 @@ - package com.laytonsmith.PureUtilities; import java.io.File; +import java.io.IOException; /** - * This class contains several constant file locations, which can be used - * throughout the rest of the application. It also includes a static factory - * method, which can be used to get the default class. Subclasses should be sure - * to call the parent's static getter method, so that chaining will work properly - * throughout the application. This particular class is agnostic to the application - * itself, and so only provides generic locations that may be useful to most Java - * applications. The controller for the application should use the most specific - * subclass available when starting up the application, though classes themselves - * should use as generic a class as possible when requesting file information. + * This class contains several constant file locations, which can be used throughout the rest of the application. It + * also includes a static factory method, which can be used to get the default class. Subclasses should be sure to call + * the parent's static getter method, so that chaining will work properly throughout the application. This particular + * class is agnostic to the application itself, and so only provides generic locations that may be useful to most Java + * applications. The controller for the application should use the most specific subclass available when starting up the + * application, though classes themselves should use as generic a class as possible when requesting file information. * Files are immutable, and likely are cached. */ public class FileLocations { - + private static FileLocations defaultInstance = null; - - private final static File USER_HOME; - private final static File USER_DIR; - private final static File JAVA_HOME; - + + private static final File USER_HOME; + private static final File USER_DIR; + private static final File JAVA_HOME; + private static final File TEMP_DIR; + static { File userHome = null; File userDir = null; File javaHome = null; - try{ + File tempDir = null; + try { userHome = new File(System.getProperty("user.home")); userDir = new File(System.getProperty("user.dir")); javaHome = new File(System.getProperty("java.home")); - } catch(SecurityException e){ - //This could happen in applets or some other wierd security configuration. + File tmp = File.createTempFile("FileLocationTempFile", ".tmp"); + tempDir = tmp.getParentFile(); + tmp.delete(); + tmp.deleteOnExit(); + } catch (SecurityException | IOException e) { + //This could happen in applets or some other weird security configuration. //Regardless, we don't want this to ever fail. } USER_HOME = userHome; USER_DIR = userDir; JAVA_HOME = javaHome; + TEMP_DIR = tempDir; } - + /** - * Returns the default FileLocations instance. If the controller - * has set a subclass, it will be returned, but by default it will be - * an instance of FileLocations. - * @return + * Returns the default FileLocations instance. If the controller has set a subclass, it will be returned, but by + * default it will be an instance of FileLocations. + * + * @return */ - public static FileLocations getDefault(){ - if(defaultInstance == null){ + public static FileLocations getDefault() { + if(defaultInstance == null) { defaultInstance = new FileLocations(); } return defaultInstance; } - + /** * Sets the default FileLocations provider. - * @param provider + * + * @param provider */ - public static void setDefault(FileLocations provider){ + public static void setDefault(FileLocations provider) { defaultInstance = provider; } - + /** * Returns the user's home directory. - * @return + * + * @return */ - public File getUserHome(){ + public File getUserHome() { return USER_HOME; } - + /** * Returns the user's working directory. - * @return + * + * @return */ - public File getUserDir(){ + public File getUserDir() { return USER_DIR; } - + /** * Returns the installation directory for the Java Runtime Environment (JRE). - * @return + * + * @return */ - public File getJavaHome(){ + public File getJavaHome() { return JAVA_HOME; } + + /** + * Returns the location of the system's temporary directory. + * @return + */ + public File getTempDir() { + return TEMP_DIR; + } } diff --git a/src/main/java/com/laytonsmith/PureUtilities/GCUtil.java b/src/main/java/com/laytonsmith/PureUtilities/GCUtil.java new file mode 100644 index 0000000000..a4a260b72e --- /dev/null +++ b/src/main/java/com/laytonsmith/PureUtilities/GCUtil.java @@ -0,0 +1,128 @@ +package com.laytonsmith.PureUtilities; + +import com.laytonsmith.PureUtilities.Common.StringUtils; +import java.lang.management.GarbageCollectorMXBean; +import java.lang.management.ManagementFactory; +import java.lang.management.RuntimeMXBean; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * Provides utilities for dealing with the java garbage collector. + */ +public final class GCUtil { + + private GCUtil() {} + + public static void main(String[] args) { + debug = true; + BlockUntilGC(); + } + + private static boolean debug = false; + + /** + * This method calls System.gc, but it blocks until it detects that a garbage collection has run. This + * should only be used when absolutely necessary, for instance, with file based operations. + * There is one caveat, the Epsilon GC does + * nothing, ever. So if we run this method with that GC, we get stuck in an infinite loop until we die due + * to out of memory error. So if that garbage collector is the only one, then we just throw without blocking. + * In such a situation, since no garbage collection will ever occur anyways, whatever was trying to be + * accomplished by calling this method will never happen anyways. + *

+ * If -XX:+DisableExplicitGC was specified on the command line, this function respects that, and silently returns + * (otherwise we would block for quite some time waiting for a natural garbage collection to happen). + *

+ * This overload uses a default timeout of 0ms, which means that it is basically equivalent to just calling + * {@code System.gc()}. + */ + public static void BlockUntilGC() { + BlockUntilGC(0); + } + + /** + * This method calls System.gc, but it blocks until it detects that a garbage collection has run. This + * should only be used when absolutely necessary, for instance, with file based operations. + * There is one caveat, the Epsilon GC does + * nothing, ever. So if we run this method with that GC, we get stuck in an infinite loop until we die due + * to out of memory error. So if that garbage collector is the only one, then we just throw without blocking. + * In such a situation, since no garbage collection will ever occur anyways, whatever was trying to be + * accomplished by calling this method will never happen anyways. + *

+ * If -XX:+DisableExplicitGC was specified on the command line, this function respects that, and silently returns + * (otherwise we would block for quite some time waiting for a natural garbage collection to happen). + * @param timeout The amount of time in ms to wait before giving up. If the value is 0 or less, the system will only + * try once. + */ + public static void BlockUntilGC(int timeout) { + final long start = System.currentTimeMillis(); + final long finish = start + timeout; + debug(() -> "Starting (now: " + start + "; stopping at: " + finish + ")"); + + RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean(); + List arguments = runtimeMxBean.getInputArguments(); + for(String arg : arguments) { + if(arg.matches("(?i)\\+DisableExplicitGC")) { + debug(() -> "Found +DisableExplicitGC, returning with no action."); + return; + } + } + /* + There may be multiple garbage collectors on a system, for instance, old generations and new generations. + We want to ensure that at least one has run before moving on. There is one caveat, the Epsilon GC does + nothing, ever. So if we run this method with that GC, we get stuck in an infinite loop until we die due + to out of memory error. So if that garbage collector is the only one, then we just return. + */ + List gcs = ManagementFactory.getGarbageCollectorMXBeans(); + if(gcs.size() == 1) { + debug(() -> gcs.get(0).getObjectName().getCanonicalName()); + if(gcs.get(0).getObjectName().getCanonicalName() + .equals("java.lang:name=Epsilon Heap,type=GarbageCollector")) { + throw new UnsupportedOperationException("Cannot continue, Epsilon GC is the only garbage collector."); + } + } + Map startCounts = new HashMap<>(); + for(GarbageCollectorMXBean gc : gcs) { + debug(() -> "Found GC " + gc.getObjectName() + " with run count " + gc.getCollectionCount()); + startCounts.put(gc, gc.getCollectionCount()); + } + debug(() -> "Starting free memory: " + h(Runtime.getRuntime().freeMemory())); + outer: while(true) { + System.gc(); + Iterator it = gcs.iterator(); + while(it.hasNext()) { + GarbageCollectorMXBean g = it.next(); + debug(() -> "Checking " + g.getObjectName() + ". Run count: " + g.getCollectionCount() + + ". Current free memory: " + h(Runtime.getRuntime().freeMemory()) + ". Total GC Count " + + ManagementFactory.getGarbageCollectorMXBeans().size() + + ". Heap: " + h(Runtime.getRuntime().totalMemory()) + "/" + + h(Runtime.getRuntime().maxMemory())); + if(g.getCollectionCount() > startCounts.get(g)) { + debug(() -> "Found that " + g.getObjectName() + " has run, returning"); + break outer; + } + } + if(System.currentTimeMillis() > finish) { + debug(() -> "I've waited too long, so I'm giving up now."); + return; + } + } + } + + private static interface StringProvider { + String provide(); + } + + private static String h(long n) { + return StringUtils.HumanReadableByteCount(n); + } + + private static void debug(StringProvider msg) { + if(debug) { + System.out.println(msg.provide()); + } + } + +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/Geometry.java b/src/main/java/com/laytonsmith/PureUtilities/Geometry.java deleted file mode 100644 index d8432999ce..0000000000 --- a/src/main/java/com/laytonsmith/PureUtilities/Geometry.java +++ /dev/null @@ -1,65 +0,0 @@ - -package com.laytonsmith.PureUtilities; - -/** - * Provided various geometry convenience classes and methods. - * - */ -public class Geometry { - - private Geometry(){} - - public static class Point3D{ - private double x; - private double y; - private double z; - - public Point3D(){ - this(0, 0, 0); - } - - public Point3D(double x, double y, double z){ - setPoints(x, y, z); - } - - public void setPoints(double x, double y, double z){ - setX(x); - setY(y); - setZ(z); - } - - public void setX(double x){ - this.x = x; - } - - public void setY(double y){ - this.y = y; - } - - public void setZ(double z){ - this.z = z; - } - - public double getX(){ - return x; - } - - public double getY(){ - return y; - } - - public double getZ(){ - return z; - } - - public double distance(Point3D other){ - //for efficiency, we write this out a longer way - return Math.sqrt( - ((other.x - x) * (other.x - x)) - + ((other.y - y) * (other.y - y)) - + ((other.z - z) * (other.z - z)) - ); - } - } - -} diff --git a/src/main/java/com/laytonsmith/PureUtilities/GithubUtil.java b/src/main/java/com/laytonsmith/PureUtilities/GithubUtil.java new file mode 100644 index 0000000000..28797551d1 --- /dev/null +++ b/src/main/java/com/laytonsmith/PureUtilities/GithubUtil.java @@ -0,0 +1,314 @@ +package com.laytonsmith.PureUtilities; + +import com.laytonsmith.PureUtilities.Web.HTTPHeaders; +import com.laytonsmith.PureUtilities.Web.HTTPMethod; +import com.laytonsmith.PureUtilities.Web.HTTPResponse; +import com.laytonsmith.PureUtilities.Web.RequestSettings; +import com.laytonsmith.PureUtilities.Web.WebUtility; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.JSONValue; + +/** + * A utility class containing methods for calling towards the github api. + */ +public class GithubUtil { + + private static final String GITHUB_URL = "https://api.github.com"; + private static final String DATE_FORMAT = "YYYY-MM-DDTHH:MM:SSZ"; + + private final String authToken; + + public GithubUtil(String bearerToken) { + this.authToken = bearerToken; + } + + public static class GithubException extends IOException { + + private final int responseCode; + private final String responseCodeText; + + public GithubException(int responseCode, String responseCodeText, String message) { + super(responseCode + " " + responseCodeText + ": " + message); + this.responseCode = responseCode; + this.responseCodeText = responseCodeText; + } + + public GithubException(IOException ex) { + super(ex.getMessage(), ex); + responseCode = 0; + responseCodeText = ""; + } + + /** + * Returns the response code. If the underlying request failed due to a general IOException, this will be + * 0. + * @return + */ + public int getResponseCode() { + return responseCode; + } + + /** + * Returns the response code text, for instance "Bad Request" for a 400. + * @return + */ + public String getResponesCodeText() { + return responseCodeText; + } + } + + /** + * Represents a generic Github response for all calls. The content varies, but will be either a JSONObject or + * JSONArray, depending on the call. You can look up that fact with isObject. The remaining quota is also stored + * in the response. + */ + public static class GithubResponse { + public Object content; + public Boolean isObject; + public int remainingQuota; + + public JSONObject asObject() { + return (JSONObject) content; + } + + public JSONArray asArray() { + return (JSONArray) content; + } + } + + /** + * Makes a generic call towards the Github API. This method handles all authentication and pagination steps. + * @param method The http method + * @param path The path, including template, starting with a slash. For instance, {@code /repos/:owner/:repo/forks}. + * The path params "owner" and "repo" must be included in the pathParams object. + * @param pathParams The parameters that should be replaced in the path. + * @param queryParams Any additional query parameters. + * @return The raw contents + * @throws com.laytonsmith.PureUtilities.GithubUtil.GithubException If we received a response from github, but + * it was a non-2xx response, or the connection failed for a more general IOException. + */ + public GithubResponse githubApiCall(HTTPMethod method, String path, Map pathParams, + Map queryParams) throws GithubException { + if(pathParams == null) { + pathParams = new HashMap<>(); + } + if(queryParams == null) { + queryParams = new HashMap<>(); + } + for(Map.Entry e : pathParams.entrySet()) { + path = path.replaceAll(":" + e.getKey(), e.getValue()); + } + + queryParams = new HashMap<>(queryParams); + queryParams.put("per_page", "100"); + + GithubResponse r = new GithubResponse(); + String url = GITHUB_URL + path; + while(true) { + RequestSettings rs = new RequestSettings(); + rs.setMethod(method); + rs.setHeaders(MapBuilder + .start("Accept", Arrays.asList("application/vnd.github.v3+json")) + .set("Authorization", Arrays.asList("token " + authToken))); + rs.setQueryParameters(queryParams); + HTTPResponse response; + try { + response = WebUtility.GetPage(new URL(url), rs); + } catch (IOException ex) { + throw new GithubException(ex); + } + if(response.getResponseCode() < 200 || response.getResponseCode() > 299) { + throw new GithubException(response.getResponseCode(), response.getResponseText(), + response.getContentAsString()); + } + + HTTPHeaders headers = response.getHeaderObject(); + + r.remainingQuota = Integer.parseInt(headers.getFirstHeader("X-RateLimit-Remaining")); + Object value; + try { + value = JSONValue.parse(response.getContentAsString(headers.getContentType().charset)); + } catch (UnsupportedEncodingException ex) { + throw new GithubException(ex); + } + if(r.isObject == null) { + r.isObject = value instanceof JSONObject; + } + + + if(r.content == null) { + r.content = value; + } else if(!r.isObject) { + // At this point, we know it's an array, and that there were previous pages, so just append these + // to the existing list + JSONArray existing = (JSONArray) r.content; + JSONArray current = (JSONArray) value; + existing.addAll(current); + } + + if(r.isObject || headers.getLink("next") == null) { + break; + } + + url = headers.getLink("next"); + if(url.startsWith("/")) { + // relative path, put the domain back on + url = GITHUB_URL + url; + } + } + return r; + } + + + /** + * Lists repositories that the authenticated user has explicit permission ({@code :read}, {@code :write}, or + * {@code :admin}) to access. + *

+ * The authenticated user has explicit permission to access repositories they own, repositories where they are a + * collaborator, and repositories that they can access through an organization membership. + * + * https://developer.github.com/v3/repos/#list-your-repositories + * @param visibility Can be one of {@code all}, {@code public}, or {@code private}. Default: {@code all} + * @param affiliation Comma-separated list of values. Can include: + *

    + *
  • {@code owner}: Repositories that are owned by the authenticated user.
  • + *
  • {@code collaborator}: Repositories that the user has been added to as a collaborator.
  • + *
  • {@code organization_member}: Repositories that the user has access to through being a member of + * an organization. + * This includes every repository on every team that the user is on.
  • + *
+ *
Default: {@code owner,collaborator,organization_member} + * @param type Can be one of {@code all}, {@code owner}, {@code public}, {@code private}, {@code member}. + *
Default: {@code all} + *
Will cause a 422 error if used in the same request as visibility or affiliation. + * @param sort Can be one of {@code created}, {@code updated}, {@code pushed}, {@code full_name}. + *
Default: {@code full_name} + * @param direction Can be one of {@code asc} or {@code desc}. Default: {@code asc} when using full_name, + * otherwise desc + * @return The returned repos will not have complete information available. For those you are interested in details + * about, you should call {@link #getRepo} to get full repository information. The owner parameter is the + * owner.login field, and the repo is the name field. + * @throws com.laytonsmith.PureUtilities.GithubUtil.GithubException + */ + public List listRepos(String visibility, String affiliation, String type, String sort, + String direction) throws GithubException { + + GithubResponse r = githubApiCall(HTTPMethod.GET, "/user/repos", null, + MapBuilder.empty(String.class, String.class) + .setIfValueNotNull("visibility", visibility) + .setIfValueNotNull("affiliation", affiliation) + .setIfValueNotNull("type", type) + .setIfValueNotNull("sort", sort) + .setIfValueNotNull("direction", direction)); + List ret = new ArrayList<>(); + for(Object o : r.asArray()) { + ret.add(Repository.parse((JSONObject) o)); + } + return ret; + } + + /** + * Returns full details about an existing repository. + * @param owner (Required) repo.owner.login + * @param repo (Required) repo.name + * @return + * @throws com.laytonsmith.PureUtilities.GithubUtil.GithubException + */ + public Repository getRepo(String owner, String repo) throws GithubException { + GithubResponse r = githubApiCall(HTTPMethod.GET, "/repos/:owner/:repo", MapBuilder + .start("owner", owner) + .set("repo", repo), null); + + return Repository.parse(r.asObject()); + } + + /** + * Forks the given repository. The resulting fork is returned. + *

+ * Note: Forking a Repository happens asynchronously. You may have to wait a short period of time before you can + * access the git objects. + * @param owner (Required) owner.login + * @param repo (Required) repo.name + * @param organization + * @return + * @throws com.laytonsmith.PureUtilities.GithubUtil.GithubException + */ + public Repository forkRepo(String owner, String repo, String organization) throws GithubException { + GithubResponse r = githubApiCall(HTTPMethod.POST, "/repos/:owner/:repo/forks", + MapBuilder.start("owner", owner).set("repo", repo), + MapBuilder.empty(String.class, String.class).setIfValueNotNull("organization", organization)); + return Repository.parse(r.asObject()); + } + + + // ***************************************************************************************************************** + // * Models * + // ***************************************************************************************************************** + + public static class Owner { + public String login; + public int id; + + static Owner parse(JSONObject obj) { + Owner o = new Owner(); + o.login = obj.get("login").toString(); + o.id = Integer.parseInt(obj.get("id").toString()); + return o; + } + } + + public static class Repository { + public int id; + public String name; + public String fullName; + public Owner owner; + public String htmlUrl; + /** + * This is the http based clone url. + */ + public String cloneUrl; + /** + * This is the ssh based clone url. + */ + public String sshUrl; + public boolean fork; + /** + * parent is the repository this repository was forked from. Will be null if {@code fork} is false. + */ + public Repository parent; + /** + * source is the ultimate source for the network. Will be null if {@code fork} is false. + */ + public Repository source; + + static Repository parse(JSONObject obj) { + if(obj == null) { + return null; + } + Repository r = new Repository(); + r.id = Integer.parseInt(obj.get("id").toString()); + r.name = obj.get("name").toString(); + r.fullName = obj.get("full_name").toString(); + r.owner = Owner.parse((JSONObject) obj.get("owner")); + r.htmlUrl = obj.get("html_url").toString(); + r.cloneUrl = obj.get("clone_url").toString(); + r.sshUrl = obj.get("ssh_url").toString(); + r.fork = Boolean.parseBoolean(obj.get("fork").toString()); + if(r.fork) { + r.parent = Repository.parse((JSONObject) obj.get("parent")); + r.source = Repository.parse((JSONObject) obj.get("source")); + } + return r; + } + } + +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/HeapDumper.java b/src/main/java/com/laytonsmith/PureUtilities/HeapDumper.java index 6bb7f93f81..30f194e351 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/HeapDumper.java +++ b/src/main/java/com/laytonsmith/PureUtilities/HeapDumper.java @@ -7,14 +7,13 @@ public class HeapDumper { // This is the name of the HotSpot Diagnostic MBean - private static final String HOTSPOT_BEAN_NAME = - "com.sun.management:type=HotSpotDiagnostic"; - // field to store the hotspot diagnostic MBean + private static final String HOTSPOT_BEAN_NAME + = "com.sun.management:type=HotSpotDiagnostic"; + // field to store the hotspot diagnostic MBean private static volatile HotSpotDiagnosticMXBean hotspotMBean; /** - * Call this method from your application whenever you want to dump the heap - * snapshot into a file. + * Call this method from your application whenever you want to dump the heap snapshot into a file. * * @param fileName name of the heap dump file * @param live flag that tells whether to dump only the live objects @@ -33,9 +32,9 @@ public static void dumpHeap(String fileName, boolean live) { // initialize the hotspot diagnostic MBean field private static void initHotspotMBean() { - if (hotspotMBean == null) { - synchronized (HeapDumper.class) { - if (hotspotMBean == null) { + if(hotspotMBean == null) { + synchronized(HeapDumper.class) { + if(hotspotMBean == null) { hotspotMBean = getHotspotMBean(); } } @@ -47,9 +46,9 @@ private static void initHotspotMBean() { private static HotSpotDiagnosticMXBean getHotspotMBean() { try { MBeanServer server = ManagementFactory.getPlatformMBeanServer(); - HotSpotDiagnosticMXBean bean = - ManagementFactory.newPlatformMXBeanProxy(server, - HOTSPOT_BEAN_NAME, HotSpotDiagnosticMXBean.class); + HotSpotDiagnosticMXBean bean + = ManagementFactory.newPlatformMXBeanProxy(server, + HOTSPOT_BEAN_NAME, HotSpotDiagnosticMXBean.class); return bean; } catch (RuntimeException re) { throw re; @@ -57,4 +56,4 @@ private static HotSpotDiagnosticMXBean getHotspotMBean() { throw new RuntimeException(exp); } } -} \ No newline at end of file +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/JSONUtil.java b/src/main/java/com/laytonsmith/PureUtilities/JSONUtil.java new file mode 100644 index 0000000000..d25a5cbd09 --- /dev/null +++ b/src/main/java/com/laytonsmith/PureUtilities/JSONUtil.java @@ -0,0 +1,357 @@ +package com.laytonsmith.PureUtilities; + +import com.laytonsmith.PureUtilities.Common.ClassUtils; +import com.laytonsmith.PureUtilities.Common.ReflectionUtils; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.JSONValue; + +/** + * Wraps the JSONObject class, and parses the input into the given Bean-like class. The bean class has some + * simple rules it must follow. It must have a no-arg constructor, though it may just be the default constructor. + * The fields in the class do not need to be public, but they can only be composed of booleans, ints (as well as shorts + * and bytes), doubles (as well as floats), + * and strings, as well as arrays of those values (including multi dimensional arrays), and other objects that are only + * composed of objects that follow + * these same rules, and enums. All fields intended to be deserialized must be public, though they are allowed to be + * final as well. + *

+ * Both primitive values and Object versions may be used (boolean/Boolean, int/Integer, double/Double, etc). + *

+ * Classes may extend other classes that follow the rules, and the object inheritance will be respected. Generic + * inheritance is supported as well. A super class may define a field with a generic type, such as: + *

+ * class A<T> {
+ *   int id;
+ *   T obj;
+ * }
+ * 
+ * And then a subclass can extend that, providing a concrete type for T as well. Unfortunately, the object itself + * also needs to be overridden with the concrete type of T, due to the type erasure effect in Java. However, other + * fields in the superclass do not need to be overridden, and anyways elsewhere in the Java code, you will retain + * type safety. + *
+ * class B extends A<String> {
+ *   String obj;
+ * }
+ * // Now elsewhere in the code
+ * B b = deserialize(json, B.class);
+ * System.out.println(b.id); // id is inherited properly from A
+ * System.out.println(b.obj instanceof String); // true, because we've overridden the type of Object
+ * 
+ *

+ * Enums will serialize and deserialize as integers, based on their ordinal value, though enums may implement + * the {@link CustomEnum} interface, and they are then allowed to map to any long value they wish. + * @author Cailin + */ +public class JSONUtil { + + public static class JSONException extends Exception { + public JSONException(String message) { + super(message); + } + } + + /** + * Normally enums are serialized and deserialized based on their ordinal. However, this is not always desirable, + * if the enum represents something such as an error code, rather than a true enum. To facilitate these custom + * enum ordinals, the enum may implement this interface, which manages going back and forth through the custom + * values to represent ordinals. + * @param The enum class + */ + static interface CustomEnum { + /** + * Returns the enum given the associated value. It is up to the implementation to decide what to do if the + * value can't be found, but it may choose to return a default value, return null, or throw an exception. + * @param value + * @return + */ + T getFromValue(M value); + + /** + * Gets the value that this enum represents. + * @return + */ + M getValue(); + } + + /** + * Normally enums are serialized and deserialized based on their ordinal. However, this is not always desirable, + * if the enum represents something such as an error code, rather than a true enum. To facilitate these custom + * enum ordinals, the enum may implement this interface, which manages going back and forth through the custom + * ordinals. + * @param The enum class + */ + public static interface CustomLongEnum extends CustomEnum { + + } + + /** + * Normally enums are serialized and deserialized based on their ordinal. However, this is not always desirable, + * if the enum represents something such as an error code, rather than a true enum. To facilitate these custom + * enum ordinals, the enum may implement this interface, which manages going back and forth through the custom + * ordinals. + * @param The enum class + */ + public static interface CustomStringEnum extends CustomEnum { + + } + + public static class Options { + /** + * Skip values altogether if they're null. + */ + public boolean skipNulls = false; + } + + private final Options options; + + public JSONUtil() { + options = new Options(); + } + + public JSONUtil(Options options) { + this.options = options; + } + + /** + * Parses the input into the given Bean-like class. The bean class has some + * simple rules it must follow. It must have a no-arg constructor, though it may just be the default constructor. + * The fields in the class do not need to be public, but they can only be composed of booleans, ints, doubles, + * and strings, as well as arrays of those values (including multi dimensional arrays), and other objects that + * follow + * these same rules. + *

+ * Both primitive values and Object versions may be used (boolean/Boolean, int/Integer, double/Double). + * @param An instance of the specified bean class + * @param json The json string + * @param bean The bean class to deserialize into + * @return An instance of the specified bean + * @throws com.laytonsmith.PureUtilities.JSONUtil.JSONException If there was an exception parsing the value. + * This can happen if a value is of the wrong type, or there were other parsing errors in the json itself. + * Extra values in the object are not an error. + */ + public T deserialize(String json, Class bean) throws JSONException { + JSONObject obj; + try { + obj = (JSONObject) JSONValue.parse(json); + } catch (ClassCastException ex) { + throw new JSONException("Value is not an object!"); + } + + T t = getType(obj, bean, null); + return t; + } + + /** + * Parses the input into the given supported bean class. The input class type may only be one of Integer, Double, + * Boolean, String, or arrays of these types (including multi dimensional arrays) and objects that follow the rules + * described in {@link #deserialize(java.lang.String, java.lang.Class)}. + * + * @param An instance of the array type + * @param json The json string + * @param bean The array class to deserialize into. Note that this is not the array type. If you have an array of + * strings, the appropriate value to pass in here is {@code string.class}, not {@code string[].class}. + * @return An instance of the specified bean + * @throws com.laytonsmith.PureUtilities.JSONUtil.JSONException If there was an exception parsing the value. + * This can happen if a value is of the wrong type, or there were other parsing errors in the json itself. + */ + public T[] deserializeArray(String json, Class bean) throws JSONException { + JSONArray obj; + try { + obj = (JSONArray) JSONValue.parse(json); + } catch (ClassCastException ex) { + throw new JSONException("Value is not an array!"); + } + Class arrayClass = ClassUtils.getArrayClassFromType(bean); + return (T[]) getType(obj, arrayClass, null); + } + + private T getType(Object o, Class c, Field holder) { + if(o == null) { + if(c == int.class) { + return (T) Integer.valueOf(0); + } else if(c == double.class) { + return (T) Double.valueOf(0.0); + } else if(c == boolean.class) { + return (T) Boolean.FALSE; + } + return null; + } + + if(c.isArray()) { + JSONArray a = (JSONArray) o; + Object array = Array.newInstance(c.getComponentType(), a.size()); + for(int i = 0; i < a.size(); i++) { + Object subValue = getType(a.get(i), c.getComponentType(), null); + Array.set(array, i, subValue); + } + return (T) array; + } else if(double.class.isAssignableFrom(c) || Double.class.isAssignableFrom(c)) { + return (T) (Double) Double.parseDouble(o.toString()); + } else if(float.class.isAssignableFrom(c) || Float.class.isAssignableFrom(c)) { + return (T) (Float) Float.parseFloat(o.toString()); + } else if(byte.class.isAssignableFrom(c) || Byte.class.isAssignableFrom(c)) { + return (T) (Byte) Byte.parseByte(o.toString()); + } else if(short.class.isAssignableFrom(c) || Short.class.isAssignableFrom(c)) { + return (T) (Short) Short.parseShort(o.toString()); + } else if(int.class.isAssignableFrom(c) || Integer.class.isAssignableFrom(c)) { + return (T) (Integer) Integer.parseInt(o.toString()); + } else if(long.class.isAssignableFrom(c) || Long.class.isAssignableFrom(c)) { + return (T) (Long) Long.parseLong(o.toString()); + } else if(String.class.isAssignableFrom(c)) { + return (T) o.toString(); + } else if(boolean.class.isAssignableFrom(c) || Boolean.class.isAssignableFrom(c)) { + return (T) Boolean.valueOf(o.toString()); + } else if(Enum.class.isAssignableFrom(c)) { + // Enum values. We need to see if c implements CustomEnum. If so, use that to deserialize. If not, + // just use the ordinal. + if(CustomEnum.class.isAssignableFrom(c)) { + return (T) ((CustomEnum) c.getEnumConstants()[0]).getFromValue((T) o); + } else { + Object e = c.getEnumConstants()[Integer.parseInt(o.toString())]; + return (T) e; + } + } else { + // Another bean, we need to loop through it and recurse + JSONObject obj = (JSONObject) o; + if(Map.class.isAssignableFrom(c)) { + MapType type = holder.getAnnotation(MapType.class); + if(type == null) { + throw new Error(holder.getDeclaringClass() + "." + holder.getName() + + " must have the @MapType annotation"); + } + Map map = new HashMap<>(); + for(Object key : obj.keySet()) { + map.put(key.toString(), getType(obj.get(key), type.value(), null)); + } + return (T) map; + } + T t = ReflectionUtils.newInstance(c); + Class clz = c; + Set setFields = new HashSet<>(); + do { + // Walk up the object inheritance chain, but don't set already set fields, because this is + // how we override generic types within the superclasses, by giving priority to the type + // of the lower class. + for(Field f : clz.getDeclaredFields()) { + if(setFields.contains(f.getName())) { + continue; + } + ReflectionUtils.set(clz, t, f.getName(), getType(obj.get(f.getName()), f.getType(), f)); + setFields.add(f.getName()); + } + clz = clz.getSuperclass(); + } while(clz != Object.class); + return t; + } + } + + public String serialize(Object obj) { + Object r; + if(obj.getClass().isArray()) { + r = fromArrayType(obj); + } else { + r = fromType(obj); + } + return JSONValue.toJSONString(r); + } + + private JSONArray fromArrayType(Object array) { + JSONArray r = new JSONArray(); + for(int i = 0; i < Array.getLength(array); i++) { + r.add(fromType(Array.get(array, i))); + } + return r; + } + + private Object fromType(Object obj) { + if(obj instanceof Double || obj instanceof Float + || obj instanceof Integer || obj instanceof Long + || obj instanceof Short || obj instanceof Byte + || obj instanceof String || obj instanceof Boolean) { + // If it's already a primitive, just return that. Don't need to check for actual primitives, because + // java boxes them for us in instanceof operations. + return obj; + } + if(Enum.class.isAssignableFrom(obj.getClass())) { + if(CustomEnum.class.isAssignableFrom(obj.getClass())) { + return ((CustomEnum) obj).getValue(); + } else { + return ((Enum) obj).ordinal(); + } + } + JSONObject r = new JSONObject(); + Class clz = obj.getClass(); + if(Map.class.isAssignableFrom(clz)) { + Map map = (Map) obj; + r.putAll(map); + return r; + } + do { + for(Field f : clz.getDeclaredFields()) { + String name = f.getName(); + Class walkUp = obj.getClass(); + Object o = null; + boolean found = false; + do { + try { + o = ReflectionUtils.get(walkUp, obj, name); + found = true; + } catch (ReflectionUtils.ReflectionException ex) { + walkUp = walkUp.getSuperclass(); + } + } while(walkUp != clz.getSuperclass() && !found); + if(o == null) { + if(!options.skipNulls) { + r.put(name, null); + } + continue; + } + Class c = o.getClass(); + if(c.isArray()) { + o = fromArrayType(o); + } else if(!( + double.class.isAssignableFrom(c) || Double.class.isAssignableFrom(c) + || float.class.isAssignableFrom(c) || Float.class.isAssignableFrom(c) + || byte.class.isAssignableFrom(c) || Byte.class.isAssignableFrom(c) + || short.class.isAssignableFrom(c) || Short.class.isAssignableFrom(c) + || int.class.isAssignableFrom(c) || Integer.class.isAssignableFrom(c) + || long.class.isAssignableFrom(c) || Long.class.isAssignableFrom(c) + || String.class.isAssignableFrom(c) + || boolean.class.isAssignableFrom(c) || Boolean.class.isAssignableFrom(c))) { + o = fromType(o); + } + + r.put(name, o); + } + clz = clz.getSuperclass(); + } while(clz != Object.class); + return r; + } + + /** + * Annotates a map, so we can know the type of the value during deserialization. + * Note that the key value is always a string. + */ + @Target(ElementType.FIELD) + @Retention(RetentionPolicy.RUNTIME) + public static @interface MapType { + + /** + * The type of the value. + * @return + */ + Class value(); + } +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/JavaVersion.java b/src/main/java/com/laytonsmith/PureUtilities/JavaVersion.java new file mode 100644 index 0000000000..d1ff3825bb --- /dev/null +++ b/src/main/java/com/laytonsmith/PureUtilities/JavaVersion.java @@ -0,0 +1,47 @@ +package com.laytonsmith.PureUtilities; + +/** + * Utilities for getting the java version + */ +public class JavaVersion { + + /** + * Gets the major version of the running JVM, for instance 6 for "1.6" or 8 for "1.8" or 11 for "11". + * @return + */ + public static int GetMajorVersion() { + String version = System.getProperty("java.version"); + if(version.startsWith("1.")) { + version = version.substring(2, 3); + } else { + int dot = version.indexOf("."); + if(dot != -1) { + version = version.substring(0, dot); + } + } + return Integer.parseInt(version); + } + + /** + * Returns the bit depth of the currently running JVM, NOT the OS bit depth. + * @return Currently, either 32 or 64, could be different in future versions. + * @throws UnsupportedOperationException If the value returned by the "os.arch" system property was unexpected. + * If this happens, a new JVM type has been released, and this function needs to be updated. + */ + public static int GetJVMBitDepth() { + // Note that despite the name, this is not the architecture of the operating system, + // it is in fact the JVM architecture. On a 32 bit system, this will always be 32 bit, + // but it's possible to install a 32 bit JVM on a 64 bit system. + String arch = System.getProperty("os.arch"); + switch(arch) { + case "x86": + return 32; + case "amd64": + case "x64": + return 64; + default: + throw new UnsupportedOperationException("JVM bit depth could not be determined."); + } + } + +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/LimitedQueue.java b/src/main/java/com/laytonsmith/PureUtilities/LimitedQueue.java index 61df1a454d..43713625d7 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/LimitedQueue.java +++ b/src/main/java/com/laytonsmith/PureUtilities/LimitedQueue.java @@ -4,29 +4,28 @@ import java.util.LinkedList; /** - * Provides a Queue with a limited size. As more elements are added, if they - * exceed the limit set initially, elements are removed from the head of the - * queue. + * Provides a Queue with a limited size. As more elements are added, if they exceed the limit set initially, elements + * are removed from the head of the queue. * * @param The type of elements held in this collection. */ public class LimitedQueue extends LinkedList { + private int limit; /** * Creates a new LimitedQueue. * - * @param limit The limit to set. If an element is added to this queue, and - * the size of the queue exceeds this, the head element is removed. + * @param limit The limit to set. If an element is added to this queue, and the size of the queue exceeds this, the + * head element is removed. */ public LimitedQueue(int limit) { this.limit = limit; } /** - * Changes the limit after construction. If the limit decreases, and the - * size of the elements is greater, the excess elements are discarded at - * that time. + * Changes the limit after construction. If the limit decreases, and the size of the elements is greater, the excess + * elements are discarded at that time. * * @param limit The new limit */ @@ -43,11 +42,10 @@ public boolean add(E o) { } //Override addAll to be more efficient. Only need to do the check at the end. - @Override public boolean addAll(int index, Collection c) { int i = 0; - for(E e : c){ + for(E e : c) { super.add(index + i, e); i++; } @@ -56,7 +54,7 @@ public boolean addAll(int index, Collection c) { } private void checkSize() { - while (size() > limit) { + while(size() > limit) { super.remove(); } } diff --git a/src/main/java/com/laytonsmith/PureUtilities/LinkedComparatorSet.java b/src/main/java/com/laytonsmith/PureUtilities/LinkedComparatorSet.java index bb8d928c3d..bbe93dd72f 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/LinkedComparatorSet.java +++ b/src/main/java/com/laytonsmith/PureUtilities/LinkedComparatorSet.java @@ -10,14 +10,12 @@ import java.util.Set; /** - * A LinkedComparatorSet works like a {@link LinkedHashSet}, but the comparison can - * be given with a custom comparator, instead of forcing use of the hashCode or equals - * method. In a normal LinkedHashSet, there is no way to provide a custom comparator, - * so if you wanted a case insensitive LinkedHashSet, you couldn't do this because there - * is no way to override the comparison to check if "A" were equal to "a". You could do - * this with a TreeSet, by providing a custom comparator, but if ordering is also important, - * then you can't use it either. This provides the best of both worlds by providing insertion - * order guarantees, while still allowing you to override the comparison mechanism. + * A LinkedComparatorSet works like a {@link LinkedHashSet}, but the comparison can be given with a custom comparator, + * instead of forcing use of the hashCode or equals method. In a normal LinkedHashSet, there is no way to provide a + * custom comparator, so if you wanted a case insensitive LinkedHashSet, you couldn't do this because there is no way to + * override the comparison to check if "A" were equal to "a". You could do this with a TreeSet, by providing a custom + * comparator, but if ordering is also important, then you can't use it either. This provides the best of both worlds by + * providing insertion order guarantees, while still allowing you to override the comparison mechanism. */ public class LinkedComparatorSet extends AbstractSet implements Set { @@ -26,50 +24,51 @@ public class LinkedComparatorSet extends AbstractSet implements Set { /** * Creates an empty {@link LinkedComparatorSet} with the given comparator. - * @param comparator + * + * @param comparator */ public LinkedComparatorSet(EqualsComparator comparator) { this(null, comparator); } /** - * Creates a new LinkedComparatorSet, based on the given collection. The - * comparator, if not null is used to do the comparison of equality. This constructor - * has better runtime performance than doing an {@link #addAll(java.util.Collection)} - * operation, n log(n) instead of n2. + * Creates a new LinkedComparatorSet, based on the given collection. The comparator, if not null is used to do the + * comparison of equality. This constructor has better runtime performance than doing an + * {@link #addAll(java.util.Collection)} operation, n log(n) instead of n2. + * * @param c The collection to start off with - * @param comparator The comparator to use in place of the equals method on the underlying - * objects, or null if a simple {@link Object#equals(java.lang.Object)} check is sufficient. + * @param comparator The comparator to use in place of the equals method on the underlying objects, or null if a + * simple {@link Object#equals(java.lang.Object)} check is sufficient. */ public LinkedComparatorSet(Collection c, EqualsComparator comparator) { this.comparator = comparator; - if (c != null && comparator != null) { + if(c != null && comparator != null) { Set skip = new HashSet(); List array = new ArrayList(c); - for (int i = 0; i < c.size(); i++) { - if (skip.contains(i)) { + for(int i = 0; i < c.size(); i++) { + if(skip.contains(i)) { continue; } boolean foundMatch = false; T item1 = array.get(i); - for (int j = i + 1; j < array.size(); j++) { - if (skip.contains(j)) { + for(int j = i + 1; j < array.size(); j++) { + if(skip.contains(j)) { continue; } T item2 = array.get(j); - if (comparator.checkIfEquals(item1, item2)) { + if(comparator.checkIfEquals(item1, item2)) { skip.add(j); - if (!foundMatch) { + if(!foundMatch) { list.add(item1); } foundMatch = true; } } - if (!foundMatch) { + if(!foundMatch) { list.add(item1); } } - } else if(c != null){ + } else if(c != null) { addAll(c); } } @@ -83,10 +82,10 @@ public Iterator iterator() { public int size() { return list.size(); } - + @Override public boolean add(T e) { - if (!contains(e)) { + if(!contains(e)) { list.add(e); return true; } else { @@ -96,27 +95,28 @@ public boolean add(T e) { /** * {@inheritDoc} - * - * This implementation uses the custom comparator if provided to check for - * equality, as opposed to the underlying object's equals methods. + * + * This implementation uses the custom comparator if provided to check for equality, as opposed to the underlying + * object's equals methods. + * * @param o - * @return + * @return */ @Override public boolean contains(Object o) { - if (comparator == null) { + if(comparator == null) { return super.contains(o); } else { Iterator e = iterator(); - if (o == null) { - while (e.hasNext()) { - if (e.next() == null) { + if(o == null) { + while(e.hasNext()) { + if(e.next() == null) { return true; } } } else { - while (e.hasNext()) { - if (comparator.checkIfEquals(o, e.next())) { + while(e.hasNext()) { + if(comparator.checkIfEquals(o, e.next())) { return true; } } @@ -124,31 +124,32 @@ public boolean contains(Object o) { return false; } } - + /** * {@inheritDoc} - * - * This implementation uses the custom comparator if provided to check - * for equality, as opposed to the underlying object's equals methods. + * + * This implementation uses the custom comparator if provided to check for equality, as opposed to the underlying + * object's equals methods. + * * @param o - * @return + * @return */ @Override public boolean remove(Object o) { - if(comparator == null){ + if(comparator == null) { return super.remove(o); } else { Iterator e = iterator(); - if (o==null) { - while (e.hasNext()) { - if (e.next()==null) { + if(o == null) { + while(e.hasNext()) { + if(e.next() == null) { e.remove(); return true; } } } else { - while (e.hasNext()) { - if (comparator.checkIfEquals(o, e.next())) { + while(e.hasNext()) { + if(comparator.checkIfEquals(o, e.next())) { e.remove(); return true; } @@ -156,19 +157,20 @@ public boolean remove(Object o) { } return false; } - } + } /** - * This can be passed in to the constructor of {@link LinkedComparatorSet} to - * "override" the equals contract of the sub elements, and that will be used instead. + * This can be passed in to the constructor of {@link LinkedComparatorSet} to "override" the equals contract of the + * sub elements, and that will be used instead. */ public static interface EqualsComparator { + /** - * Should return true if val1 and val2 are "equals" according to your custom - * contract. + * Should return true if val1 and val2 are "equals" according to your custom contract. + * * @param val1 * @param val2 - * @return + * @return */ boolean checkIfEquals(T val1, T val2); } diff --git a/src/main/java/com/laytonsmith/PureUtilities/MSP/Burst.java b/src/main/java/com/laytonsmith/PureUtilities/MSP/Burst.java index e421b4e6c3..db7dc29603 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/MSP/Burst.java +++ b/src/main/java/com/laytonsmith/PureUtilities/MSP/Burst.java @@ -1,14 +1,14 @@ package com.laytonsmith.PureUtilities.MSP; -import com.laytonsmith.core.CHVersion; +import com.laytonsmith.core.MSVersion; import com.laytonsmith.core.SimpleDocumentation; /** * A Burst is a single transmission from client to server, or server to client. - * + * */ public class Burst { - + BurstType type; String id; String value; @@ -17,35 +17,35 @@ public class Burst { String rider; //TODO: Is this type right? String riderChecksum; - + public static enum BurstType implements SimpleDocumentation { @RemoteCapability() - META("Provides meta information to the client/server. The payload will be a json with further information", CHVersion.V3_3_1), + META("Provides meta information to the client/server. The payload will be a json with further information", MSVersion.V3_3_1), @RemoteCapability() FUNCTION("This is a static function/procedure call. No rider information is provided, but the payload will" + " be a json array, with [0] being the fully qualified function name, and [1..] being the json" - + " encoded arguments.", CHVersion.V3_3_1), + + " encoded arguments.", MSVersion.V3_3_1), @RemoteCapability() METHOD("This is an instance based method call. The rider will be the json encoded object that this method" - + " is being called on, and the payload will be the same as " + FUNCTION.name() + "'s payload.", CHVersion.V3_3_1), + + " is being called on, and the payload will be the same as " + FUNCTION.name() + "'s payload.", MSVersion.V3_3_1), @RemoteCapability() - RESPONSE("This is a response from a previous call. The payload will be the json encoded response.", CHVersion.V3_3_1), + RESPONSE("This is a response from a previous call. The payload will be the json encoded response.", MSVersion.V3_3_1), @RemoteCapability() VOID("This is a response from a previous call, but the function/procedure/method returned void. This" + " response is simply to inform the client/server that the response succeeded, while minimizing" - + " the data transmitted", CHVersion.V3_3_1), + + " the data transmitted", MSVersion.V3_3_1), @RemoteCapability() EXCEPTION("This is a response from a previous call, but the function/procedure/method returned with an exception." - + " The payload will be the exception type, and the rider will be the exception message", CHVersion.V3_3_1), + + " The payload will be the exception type, and the rider will be the exception message", MSVersion.V3_3_1), @RemoteCapability() ERROR("While handling the request, the remote failed unexpectedly. The payload will simply contain error information" + " in an unspecified format, which is intended to be helpful, but should not typically be shown to the" - + " end user.", CHVersion.V3_3_1) - ; - + + " end user.", MSVersion.V3_3_1); + String doc; - CHVersion version; - private BurstType(String doc, CHVersion version){ + MSVersion version; + + private BurstType(String doc, MSVersion version) { this.doc = doc; this.version = version; } @@ -61,9 +61,9 @@ public String docs() { } @Override - public CHVersion since() { + public MSVersion since() { return version; } - + } } diff --git a/src/main/java/com/laytonsmith/PureUtilities/MSP/CapabilityList.java b/src/main/java/com/laytonsmith/PureUtilities/MSP/CapabilityList.java index 0a8d93ab9f..83479d37a4 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/MSP/CapabilityList.java +++ b/src/main/java/com/laytonsmith/PureUtilities/MSP/CapabilityList.java @@ -4,143 +4,135 @@ import java.util.Map; /** - * A capability list is a list of known supported - * actions by a remote. The remote is free to update - * this list at any time, and capabilities can have - * various states. - * + * A capability list is a list of known supported actions by a remote. The remote is free to update this list at any + * time, and capabilities can have various states. + * */ public class CapabilityList { - - private Map caps = new HashMap(); - private Connection connection; - + + private final Map caps = new HashMap(); + private final Connection connection; + /** * Creates a new CapabilityList object. - * @param connection The connection to the server. This will be used - * to automatically determine server capabilites when required. + * + * @param connection The connection to the server. This will be used to automatically determine server capabilites + * when required. */ - public CapabilityList(Connection connection){ + public CapabilityList(Connection connection) { this.connection = connection; } - + /** * Clears out this capability list. */ - public void clear(){ + public void clear() { caps.clear(); } - - public void setCapability(Capability capability, CapabilityValue value){ - if(value.serverReturnable()){ + + public void setCapability(Capability capability, CapabilityValue value) { + if(value.serverReturnable()) { caps.put(capability, value); } else { - throw new RuntimeException("An error occured during runtime, the server returned an invalid capability: " + value); + throw new RuntimeException("An error occurred during runtime, the server returned an invalid capability: " + value); } } - + /** - * Returns true if the server supports this capability. If the capability is - * dynamic or unknown, it is looked up from the server. + * Returns true if the server supports this capability. If the capability is dynamic or unknown, it is looked up + * from the server. + * * @param capability - * @return + * @return */ - public CapabilityValue hasCapability(Capability capability){ + public CapabilityValue hasCapability(Capability capability) { CapabilityValue value = caps.get(capability); - if(value == null){ + if(value == null) { //If the capability is unknown, we need to look it up. setCapability(capability, connection.getCapability(capability)); return hasCapability(capability); - } else if(value == CapabilityValue.DYNAMIC){ + } else if(value == CapabilityValue.DYNAMIC) { //Do a one time lookup return connection.getCapability(capability); } return value; } - + /** - * A capability is intended to be an enum, but since the valid - * values may vary from version to version, an interface is defined instead, - * which various enum values should implement. All capabilities are required - * to return a namespace as well, which, in combination with the name, should - * uniquely identify this capability. Generally, the namespace should return the - * fully qualified class name. + * A capability is intended to be an enum, but since the valid values may vary from version to version, an interface + * is defined instead, which various enum values should implement. All capabilities are required to return a + * namespace as well, which, in combination with the name, should uniquely identify this capability. Generally, the + * namespace should return the fully qualified class name. */ - public interface Capability{ + public interface Capability { + /** * The namespace of the capability. - * @return + * + * @return */ String namespace(); + /** * The name of the capability. - * @return + * + * @return */ String name(); } - - public static enum CapabilityValue{ + + public static enum CapabilityValue { /** - * This capability is always supported by this server, and - * the client should never request if this is - * supported or not. (It still may fail, but that is unexpected). - * This is generally reserved for core operations, but - * may be also used for other requests. If the request fails, it - * will not automatically switch the value of this capability, however, - * the server may still actively change the capability value. + * This capability is always supported by this server, and the client should never request if this is supported + * or not. (It still may fail, but that is unexpected). This is generally reserved for core operations, but may + * be also used for other requests. If the request fails, it will not automatically switch the value of this + * capability, however, the server may still actively change the capability value. */ ALWAYS_SUPPORTED(true), /** - * This capability is never supported by this server, and - * the client should never request if this is supported - * or not. (It still may succeed, but that is unexpected). - * This is generally reserved for the "default" case, where a - * server doesn't know about a capability at all. If the client - * still attempts the operation, and it succeeds, it will not - * automatically switch the value of this capability, however, - * the server may still actively change the capability value. + * This capability is never supported by this server, and the client should never request if this is supported + * or not. (It still may succeed, but that is unexpected). This is generally reserved for the "default" case, + * where a server doesn't know about a capability at all. If the client still attempts the operation, and it + * succeeds, it will not automatically switch the value of this capability, however, the server may still + * actively change the capability value. */ ALWAYS_UNSUPPORTED(true), /** - * This capability is supported, but given certain runtime conditions, - * it may change (for instance, lack of permissions). - * If the client attempts the operation and it doesn't - * succeed, the value will automatically change to UNSUPPORTED, and the - * server may actively change this. + * This capability is supported, but given certain runtime conditions, it may change (for instance, lack of + * permissions). If the client attempts the operation and it doesn't succeed, the value will automatically + * change to UNSUPPORTED, and the server may actively change this. */ SUPPORTED(true), /** - * This capability is unsupported, but given certain runtime conditions, - * it may change (for instance, elevation of permissions). - * If the client forces the operation and it does succeed, - * the value will change automatically to SUPPORTED, and the server may - * actively change this. + * This capability is unsupported, but given certain runtime conditions, it may change (for instance, elevation + * of permissions). If the client forces the operation and it does succeed, the value will change automatically + * to SUPPORTED, and the server may actively change this. */ UNSUPPORTED(true), /** - * This capability is requested from the server each time it is used, and - * that new value is temporarily used to determine support or not. + * This capability is requested from the server each time it is used, and that new value is temporarily used to + * determine support or not. */ DYNAMIC(true), /** - * This is the "default" value, that is, if a client does not know if a server - * supports this capabilty, it works like {@see #DYNAMIC}, but the server's response - * will be cached. + * This is the "default" value, that is, if a client does not know if a server supports this capabilty, it works + * like {@link #DYNAMIC}, but the server's response will be cached. */ UNKNOWN(false); - + private final boolean serverReturnable; - private CapabilityValue(boolean serverReturnable){ + + private CapabilityValue(boolean serverReturnable) { this.serverReturnable = serverReturnable; } - + /** - * Returns true if the server can return this value during a request - * for capabilities. - * @return + * Returns true if the server can return this value during a request for capabilities. + * + * @return */ - public boolean serverReturnable(){ + public boolean serverReturnable() { return serverReturnable; } } diff --git a/src/main/java/com/laytonsmith/PureUtilities/MSP/Connection.java b/src/main/java/com/laytonsmith/PureUtilities/MSP/Connection.java index 85037400da..11eb643657 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/MSP/Connection.java +++ b/src/main/java/com/laytonsmith/PureUtilities/MSP/Connection.java @@ -4,58 +4,58 @@ import com.laytonsmith.PureUtilities.MSP.CapabilityList.CapabilityValue; /** - * A Connection object represents some sort of connection to a remote. The - * basic defined behavior is simply to do a connection, and then gather initial permission - * and capability lists. - * + * A Connection object represents some sort of connection to a remote. The basic defined behavior is simply to do a + * connection, and then gather initial permission and capability lists. + * */ public interface Connection { - + /** - * Connects to the remote. If the connection cannot be made, a ConnectionException - * is thrown. During the actual connection, if isAttemptSecure returned true, - * and the connection is actually insecure, it MUST fail, prior to transmission - * of any data. - * @throws com.laytonsmith.PureUtilities.MSP.Connection.ConnectionException + * Connects to the remote. If the connection cannot be made, a ConnectionException is thrown. During the actual + * connection, if isAttemptSecure returned true, and the connection is actually insecure, it MUST fail, prior to + * transmission of any data. + * + * @throws com.laytonsmith.PureUtilities.MSP.Connection.ConnectionException */ public void connect() throws ConnectionException; - + /** - * Connects to the remote. If the connection cannot be made, a ConnectionException - * is thrown. The connection should succeed even if the connection is insecure, - * and isAttemptSecure returned true. This should normally not be called without - * a user's intervention however. - * @throws com.laytonsmith.PureUtilities.MSP.Connection.ConnectionException + * Connects to the remote. If the connection cannot be made, a ConnectionException is thrown. The connection should + * succeed even if the connection is insecure, and isAttemptSecure returned true. This should normally not be called + * without a user's intervention however. + * + * @throws com.laytonsmith.PureUtilities.MSP.Connection.ConnectionException */ public void forceConnection() throws ConnectionException; - + /** - * Returns true iff a connection attempt to this server is anticipated - * to be secure. During the actual connection, if this method returned true, - * and the connection is actually insecure, it MUST fail, prior to transmission - * of any data. - * @return + * Returns true iff a connection attempt to this server is anticipated to be secure. During the actual connection, + * if this method returned true, and the connection is actually insecure, it MUST fail, prior to transmission of any + * data. + * + * @return */ public boolean isAttemptSecure(); - + /** - * Requests the value of a single capability. This is a blocking - * call. + * Requests the value of a single capability. This is a blocking call. + * * @param capability - * @return + * @return */ public CapabilityValue getCapability(Capability capability); - + public static class ConnectionException extends Exception { - - public static enum FailureReason{ + + public static enum FailureReason { UNKNOWN, IOEXCEPTION, INSUFFICIENT_PERMISSIONS, INSECURE; } - + private final FailureReason reason; + public ConnectionException(FailureReason reason) { super(); this.reason = reason; @@ -75,11 +75,10 @@ public ConnectionException(String message, Throwable cause, FailureReason reason super(message, cause); this.reason = reason; } - - public FailureReason getFailureReason(){ + + public FailureReason getFailureReason() { return this.reason; } - - + } } diff --git a/src/main/java/com/laytonsmith/PureUtilities/MSP/MSPUtil.java b/src/main/java/com/laytonsmith/PureUtilities/MSP/MSPUtil.java index 20d004988c..6c2a5fa266 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/MSP/MSPUtil.java +++ b/src/main/java/com/laytonsmith/PureUtilities/MSP/MSPUtil.java @@ -2,13 +2,14 @@ /** * Contains static utility methods for the MSP package. - * + * */ -public class MSPUtil { - - private MSPUtil(){} +public final class MSPUtil { - public static String getCapabilityName(CapabilityList.Capability capability){ + private MSPUtil() { + } + + public static String getCapabilityName(CapabilityList.Capability capability) { return capability.namespace() + "." + capability.name(); } } diff --git a/src/main/java/com/laytonsmith/PureUtilities/MSP/RemoteCapability.java b/src/main/java/com/laytonsmith/PureUtilities/MSP/RemoteCapability.java index 686d2414ac..46a37d411c 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/MSP/RemoteCapability.java +++ b/src/main/java/com/laytonsmith/PureUtilities/MSP/RemoteCapability.java @@ -6,21 +6,22 @@ import java.lang.annotation.Target; /** - * If this element is used, it must have a capability request associated with - * it. The capability name will be calculated to be the fully qualified name of the class - * (if annotating a class element) or the fully qualified name of the class "dot" the name - * of the field (if annotating a field). The value may optionally be hardcoded as a string - * provided in the value, which defaults to the NULL constant defined here. + * If this element is used, it must have a capability request associated with it. The capability name will be calculated + * to be the fully qualified name of the class (if annotating a class element) or the fully qualified name of the class + * "dot" the name of the field (if annotating a field). The value may optionally be hardcoded as a string provided in + * the value, which defaults to the NULL constant defined here. + * * - * */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.FIELD}) public @interface RemoteCapability { + /** - * Java doesn't allow actual nulls for annotation values, so use this instead, to pick the "default" value, - * or simply leave the value blank. + * Java doesn't allow actual nulls for annotation values, so use this instead, to pick the "default" value, or + * simply leave the value blank. */ public static final String NULL = "~!@#$%^&*()_+NULL value, do not use this, unless you intend on this value being null+_)(*&^%$#@!~"; + String value() default NULL; } diff --git a/src/main/java/com/laytonsmith/PureUtilities/MapBuilder.java b/src/main/java/com/laytonsmith/PureUtilities/MapBuilder.java new file mode 100644 index 0000000000..d17353a41f --- /dev/null +++ b/src/main/java/com/laytonsmith/PureUtilities/MapBuilder.java @@ -0,0 +1,251 @@ +package com.laytonsmith.PureUtilities; + +import java.util.AbstractMap; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * Eases the act of building a map which has static values. + * @param The key type + * @param The value type + */ +public class MapBuilder extends AbstractMap implements Map { + + protected MapBuilder(Map map) { + this.map = map; + } + + /** + * Creates a new MapBuilder and adds the given values to the map. + * @param The key type. + * @param The value type. + * @param key The key + * @param value The value + * @return A new MapBuilder object. + */ + public static MapBuilder start(T key, U value) { + return new MapBuilder(new HashMap()).set(key, value); + } + + /** + * Creates a new, empty map builder, which supports the given key and value types. + * @param The key type. + * @param The value type. + * @param keyType + * @param valueType + * @return A new MapBuilder object. + */ + public static MapBuilder empty(Class keyType, Class valueType) { + return new MapBuilder<>(new HashMap<>()); + } + + /** + * By default, the other constructors use {@link HashMap}s as the backing Map implementation. This may not be + * desirable. In that case, you can provide an existing Map with whatever implementation you see fit. The Map + * may also be non-empty if you wish to prepopulate the map through other means. + * @param The key type. + * @param The value type. + * @param existingMap An existing Map. + * @return A new MapBuilder object wrapping the provided Map instance. + */ + public static MapBuilder empty(Map existingMap) { + return new MapBuilder<>(existingMap); + } + + private final Map map; + + /** + * Puts a new entry in the map, and returns this. + * @param key They key to add. + * @param value The value to add. + * @return The MapBuilder object, for easy chaining. + */ + public MapBuilder set(K key, V value) { + map.put(key, value); + return this; + } + + /** + * Puts a new entry in the map, unless the value is null, in which case the default is used instead. + * @param key The key to add. + * @param value The value to add, possibly null. + * @param def The value to use if the provided value is null. + * @return The MapBuilder object, for easy chaining. + */ + public MapBuilder set(K key, V value, V def) { + if(value == null) { + set(key, def); + } else { + set(key, value); + } + return this; + } + + /** + * Puts a new entry in the map only if the value is not null. If it is null, nothing is changed. Either way, + * the MapBuilder object is returned. + * @param key The key to add. + * @param value The value to add, possibly null + * @return The MapBuilder object, for easy chaining + */ + public MapBuilder setIfValueNotNull(K key, V value) { + if(value != null) { + put(key, value); + } + return this; + } + + + /** + * Unlike set, which returns the MapBuilder object, this is the original put from Map. + *

+ * {@inheritDoc} + * + * @param key + * @param value + * @return + */ + @Override + public V put(K key, V value) { + return map.put(key, value); + } + + @Override + public void putAll(Map m) { + map.putAll(m); + } + + @Override + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + @Override + @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") + public boolean equals(Object o) { + return map.equals(o); + } + + @Override + public V get(Object key) { + return map.get(key); + } + + @Override + public void clear() { + map.clear(); + } + + @Override + public int hashCode() { + return map.hashCode(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public Set keySet() { + return map.keySet(); + } + + @Override + public V remove(Object key) { + return map.remove(key); + } + + @Override + public boolean remove(Object key, Object value) { + return map.remove(key, value); + } + + @Override + public int size() { + return map.size(); + } + + @Override + public String toString() { + return map.toString(); + } + + @Override + public Collection values() { + return map.values(); + } + + @Override + public Set> entrySet() { + return map.entrySet(); + } + + @Override + public V getOrDefault(Object key, V defaultValue) { + return map.getOrDefault(key, defaultValue); + } + + @Override + public void forEach(BiConsumer action) { + map.forEach(action); + } + + @Override + public void replaceAll(BiFunction function) { + map.replaceAll(function); + } + + @Override + public V putIfAbsent(K key, V value) { + return map.putIfAbsent(key, value); + } + + @Override + public boolean replace(K key, V oldValue, V newValue) { + return map.replace(key, oldValue, newValue); + } + + @Override + public V replace(K key, V value) { + return map.replace(key, value); + } + + @Override + public V computeIfAbsent(K key, Function mappingFunction) { + return map.computeIfAbsent(key, mappingFunction); + } + + @Override + public V computeIfPresent(K key, BiFunction remappingFunction) { + return map.computeIfPresent(key, remappingFunction); + } + + @Override + public V compute(K key, BiFunction remappingFunction) { + return map.compute(key, remappingFunction); + } + + @Override + public V merge(K key, V value, BiFunction remappingFunction) { + return map.merge(key, value, remappingFunction); + } + + /** + * Returns a cloned version of the internal Map object. + * @return + */ + public Map build() { + return new HashMap<>(map); + } +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/Marquee.java b/src/main/java/com/laytonsmith/PureUtilities/Marquee.java index 39149dc642..fdd31d5623 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Marquee.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Marquee.java @@ -3,24 +3,26 @@ /** * A generic class for creating a text marquee. * - * + * */ public final class Marquee { public interface MarqueeCallback { + /** - * Sends the correct portion of the string, as well as a reference to this Marquee object, - * in case it needs to be stopped or otherwise changed. + * Sends the correct portion of the string, as well as a reference to this Marquee object, in case it needs to + * be stopped or otherwise changed. + * * @param portion - * @param self + * @param self */ void stringPortion(String portion, Marquee self); } private String text; - private int maxChars; - private int delay; + private final int maxChars; + private final int delay; private boolean run; - private MarqueeCallback callback; + private final MarqueeCallback callback; public Marquee(String string, int maxChars, int delay, MarqueeCallback callback) { this.maxChars = maxChars; @@ -30,16 +32,16 @@ public Marquee(String string, int maxChars, int delay, MarqueeCallback callback) } public void setText(String text) { - if (text == null) { + if(text == null) { text = ""; } - if (!text.endsWith(" ")) { + if(!text.endsWith(" ")) { text = text + " "; } - if(text.length() < maxChars){ + if(text.length() < maxChars) { //Pad with spaces, so we still get the marquee effect StringBuilder b = new StringBuilder(); - for(int i = 0; i < maxChars - text.length(); i++){ + for(int i = 0; i < maxChars - text.length(); i++) { b.append(" "); } text += b.toString(); @@ -48,32 +50,32 @@ public void setText(String text) { } public void start() { - if (run) { + if(run) { return; } run = true; String name = text; - if (name.length() > 10) { + if(name.length() > 10) { name = text.substring(0, 10); } - + new Thread(new Runnable() { @Override public void run() { int loopPointer = 0; try { - while (run) { + while(run) { final String composite; - String psuedoText = text + text + text; - composite = psuedoText.substring(loopPointer, maxChars + loopPointer); + String pseudoText = text + text + text; + composite = pseudoText.substring(loopPointer, maxChars + loopPointer); loopPointer++; - if (loopPointer > text.length()) { + if(loopPointer > text.length()) { //reset it once we go over the length loopPointer = 0; } - + callback.stringPortion(composite, Marquee.this); - + Thread.sleep(delay); } } catch (Exception ex) { //We want an exception to kill us, but we also want to rethrow it as a runtime exception. diff --git a/src/main/java/com/laytonsmith/PureUtilities/MathUtils.java b/src/main/java/com/laytonsmith/PureUtilities/MathUtils.java new file mode 100644 index 0000000000..87d8b1a1ff --- /dev/null +++ b/src/main/java/com/laytonsmith/PureUtilities/MathUtils.java @@ -0,0 +1,26 @@ +package com.laytonsmith.PureUtilities; + +/** + * This class contains methods that can assist with certain mathematical problems. + * @author P.J.S. Kools + */ +public abstract class MathUtils { + + /** + * Check a number's parity. + * @param number + * @return {@code true} if the number is even, {@code false} otherwise. + */ + public static boolean isEven(int number) { + return (number & 0x01) == 0x00; + } + + /** + * Check a number's parity. + * @param number + * @return {@code true} if the number is off, {@code false} otherwise. + */ + public static boolean isOdd(int number) { + return (number & 0x01) == 0x01; + } +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/MemoryMapFileUtil.java b/src/main/java/com/laytonsmith/PureUtilities/MemoryMapFileUtil.java index b74befcd80..dfbc4ec1d2 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/MemoryMapFileUtil.java +++ b/src/main/java/com/laytonsmith/PureUtilities/MemoryMapFileUtil.java @@ -1,4 +1,3 @@ - package com.laytonsmith.PureUtilities; import com.laytonsmith.PureUtilities.Common.FileUtil; @@ -16,32 +15,34 @@ /** * - * + * */ -public class MemoryMapFileUtil { - private static final Map instances = new HashMap<>(); +public final class MemoryMapFileUtil { + + private static final Map INSTANCES = new HashMap<>(); /** * The minimum delay between FS writes. In milliseconds. */ private static final int WRITE_DELAY = 250; - - public static synchronized MemoryMapFileUtil getInstance(File f, DataGrabber grabber) throws IOException{ + + public static synchronized MemoryMapFileUtil getInstance(File f, DataGrabber grabber) throws IOException { String s = f.getCanonicalPath(); MemoryMapFileUtil mem; - if(!instances.containsKey(s)){ + if(!INSTANCES.containsKey(s)) { mem = new MemoryMapFileUtil(f, grabber); - instances.put(s, mem); + INSTANCES.put(s, mem); } else { - mem = instances.get(s); - } + mem = INSTANCES.get(s); + } mem.grabber = grabber; return mem; } - - public static interface DataGrabber{ + + public static interface DataGrabber { + byte[] getData(); } - + private final String file; private DataGrabber grabber; private boolean modelDirty = false; @@ -49,42 +50,44 @@ public static interface DataGrabber{ private boolean running = false; private long lastWrite = 0; private ExecutorService service; - private MemoryMapFileUtil(File file, DataGrabber grabber) throws IOException{ + + private MemoryMapFileUtil(File file, DataGrabber grabber) throws IOException { this.file = file.getCanonicalPath(); this.grabber = grabber; } - - private void run(){ - try{ - synchronized(this){ + + private void run() { + try { + synchronized(this) { running = true; } - while(true){ + while(true) { //We don't want to write out files too frequently, so we want to check when our last write action was, //and delay some if it was too recent. long lastWriteDelta = System.currentTimeMillis() - lastWrite; - if(lastWriteDelta < WRITE_DELAY){ + if(lastWriteDelta < WRITE_DELAY) { try { Thread.sleep(lastWriteDelta); - } catch (InterruptedException ex) {} + } catch (InterruptedException ex) { + } } // File temp = null; try { - synchronized(this){ - if(!modelDirty && !fileDirty){ + synchronized(this) { + if(!modelDirty && !fileDirty) { return; } } // temp = File.createTempFile("MemoryMapFile", ".tmp"); File permanent = new File(file); - byte [] data; - synchronized(this){ + byte[] data; + synchronized(this) { data = grabber.getData(); modelDirty = false; fileDirty = true; } - - synchronized(this){ + + synchronized(this) { FileUtil.write(data, permanent, FileUtil.OVERWRITE, true); lastWrite = System.currentTimeMillis(); fileDirty = false; @@ -101,41 +104,42 @@ private void run(){ // } } catch (IOException ex) { Logger.getLogger(MemoryMapFileUtil.class.getName()).log(Level.SEVERE, null, ex); - } finally { + } +// finally { // if(temp != null){ // temp.delete(); // temp.deleteOnExit(); // } - } +// } } - } finally{ - synchronized(this){ + } finally { + synchronized(this) { running = false; } } } - + /** - * Marks the data as dirty. This also triggers the writer to start if it isn't already - * started. Multiple calls to mark do not necessarily cause the output to be written - * multiple times, it simply sets the flag + * Marks the data as dirty. This also triggers the writer to start if it isn't already started. Multiple calls to + * mark do not necessarily cause the output to be written multiple times, it simply sets the flag + * * @param dm The daemon manager. If null, ignored. */ - public void mark(final DaemonManager dm){ - synchronized(this){ + public void mark(final DaemonManager dm) { + synchronized(this) { modelDirty = fileDirty = true; - if(!running){ - if(dm != null){ + if(!running) { + if(dm != null) { dm.activateThread(null); } getService().submit(new Runnable() { @Override public void run() { - try{ + try { MemoryMapFileUtil.this.run(); } finally { - if(dm != null){ + if(dm != null) { dm.deactivateThread(null); } } @@ -144,20 +148,20 @@ public void run() { } } } - - private synchronized ExecutorService getService(){ - if(service == null){ + + private synchronized ExecutorService getService() { + if(service == null) { service = new ThreadPoolExecutor(1, 1, - 60L, TimeUnit.MILLISECONDS, - new LinkedBlockingQueue(), - new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - Thread t = new Thread(r, "MemoryMapWriter-" + file); - t.setPriority(Thread.MIN_PRIORITY); - return t; - } - }); + 60L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue(), + new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r, "MemoryMapWriter-" + file); + t.setPriority(Thread.MIN_PRIORITY); + return t; + } + }); // service = Executors.newSingleThreadExecutor(new ThreadFactory() { // // public Thread newThread(Runnable r) { diff --git a/src/main/java/com/laytonsmith/PureUtilities/ObjectHelpers.java b/src/main/java/com/laytonsmith/PureUtilities/ObjectHelpers.java new file mode 100644 index 0000000000..26d83899d3 --- /dev/null +++ b/src/main/java/com/laytonsmith/PureUtilities/ObjectHelpers.java @@ -0,0 +1,210 @@ +package com.laytonsmith.PureUtilities; + +import com.laytonsmith.PureUtilities.Common.ReflectionUtils; +import com.laytonsmith.PureUtilities.Common.StringUtils; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +/** + * This class contains methods for assisting in dealing with .equals, .hashCode, and .toString in objects, in a + * maintainable and scalable way. + *

+ * To use this class, implement .equals, .hashCode and .toString, and simply implement them as + * {@code return ObjectHelper.DoEquals(this, that);}, {@code return ObjectHelper.DoHashCode(this, that);}, and + * {@code return ObjectHelper.DoToString(this, that);} respectively. Then, for the fields that you wish to include in + * these calculations, add the {@code @Equals}, {@code @HashCode}, or {@code @ToString} annotations to them (or + * {@code @StandardField} if you wish to add all three.) You can also apply the annotation to the containing class, + * which is the same as placing the annotation on ALL the fields. + */ +@SuppressWarnings({"checkstyle:parametername", "checkstyle:localvariablename"}) +public class ObjectHelpers { + + /** + * Works the same as adding @Equals @HashCode and @ToString to this field/class. + */ + @Target({ElementType.FIELD, ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + public @interface StandardField {} + + /** + * When tagged with this annotation or {@link StandardField}, includes this field in the .equals calculation. + */ + @Target({ElementType.FIELD, ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + public @interface Equals {} + + /** + * When tagged with this annotation or {@link StandardField}, includes this field in the .hashCode calculation. + */ + @Target({ElementType.FIELD, ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + public @interface HashCode {} + + /** + * When tagged with this annotation or {@link StandardField}, includes this field in the .equals calculation. + */ + @Target({ElementType.FIELD, ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + public @interface ToString {} + + /** + * Implement the .equals method in the class as such: + *

+	 * @Override
+	 * @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
+	 * public boolean equals(Object obj) {
+	 * 	return ObjectHelpers.DoEquals(this, obj);
+	 * }
+	 * 
+ * Then add the {@code @Equals} or {@code @StandardField} annotation to the fields you wish to be used in the + * calculation, or to the class itself to use all fields. + * @param _this {@code this} object + * @param that The other object being compared, i.e. the one being passed in to .equals + * @return True if the specified fields are equal. + */ + public static boolean DoEquals(Object _this, Object that) { + if(_this == that) { + return true; + } + if(that == null) { + return false; + } + if(_this.getClass() != that.getClass()) { + return false; + } + for(Field f : _this.getClass().getDeclaredFields()) { + if( + f.getDeclaringClass().getAnnotation(Equals.class) == null + && f.getDeclaringClass().getAnnotation(StandardField.class) == null + && f.getAnnotation(Equals.class) == null + && f.getAnnotation(StandardField.class) == null + ) { + continue; + } + if(f.getName().startsWith("$")) { + // If the field starts with this name, it's a dynamic field that was inserted by some + // dynamic code. While it may be nice to know these in some cases, this is not the + // general use case supported by this code, so we skip these, so our results are + // deterministic. + continue; + } + Object _thisO = ReflectionUtils.get(_this.getClass(), _this, f.getName()); + Object thatO = ReflectionUtils.get(_this.getClass(), that, f.getName()); + if(!Objects.equals(_thisO, thatO)) { + return false; + } + } + return true; + } + + /** + * Implement the .hashCode method in the class as such: + *
+	 * @Override
+	 * public int hashCode() {
+	 * 	return ObjectHelpers.DoHashCode(this);
+	 * }
+	 * 
+ * Then add the {@code @HashCode} or {@code @StandardField} annotation to the fields you wish to be used in the + * calculation, or to the class itself to use all fields. + * @param _this {@code this} object + * @return The calculated hash code, which at its core, uses {@link Arrays#hashCode(java.lang.Object[])}. + */ + public static int DoHashCode(Object _this) { + List calculatedFields = new ArrayList<>(); + for(Field f : _this.getClass().getDeclaredFields()) { + if( + f.getDeclaringClass().getAnnotation(HashCode.class) == null + && f.getDeclaringClass().getAnnotation(StandardField.class) == null + && f.getAnnotation(HashCode.class) == null + && f.getAnnotation(StandardField.class) == null + ) { + continue; + } + if(f.getName().startsWith("$")) { + // If the field starts with this name, it's a dynamic field that was inserted by some + // dynamic code. While it may be nice to know these in some cases, this is not the + // general use case supported by this code, so we skip these, so our results are + // deterministic. + continue; + } + Object _thisO = ReflectionUtils.get(_this.getClass(), _this, f.getName()); + calculatedFields.add(_thisO); + } + if(calculatedFields.isEmpty()) { + return 0; + } + return Arrays.hashCode(calculatedFields.toArray(new Object[calculatedFields.size()])); + } + + /** + * Implement the .toString method in the class as such: + *
+	 * @Override
+	 * public String toString() {
+	 * 	return ObjectHelpers.DoToString(this);
+	 * }
+	 * 
+ * Then add the {@code @ToString} or {@code @StandardField} annotation to the fields you wish to be used in the + * calculation, or to the class itself to use all fields. + * @param _this {@code this} object + * @return The toString of the object, using all the provided field's toString as part of the resulting string. + */ + public static String DoToString(Object _this) { + if(_this == null) { + return "null"; + } + List values = new ArrayList<>(); + boolean hasAnnotations = false; + for(Field f : _this.getClass().getDeclaredFields()) { + if( + f.getDeclaringClass().getAnnotation(ToString.class) == null + && f.getDeclaringClass().getAnnotation(StandardField.class) == null + && f.getAnnotation(ToString.class) == null + && f.getAnnotation(StandardField.class) == null + ) { + continue; + } + if(f.getName().startsWith("$")) { + // If the field starts with this name, it's a dynamic field that was inserted by some + // dynamic code. While it may be nice to know these in some cases, this is not the + // general use case supported by this code, so we skip these, so our results are + // deterministic. + continue; + } + hasAnnotations = true; + Object _thisO = ReflectionUtils.get(_this.getClass(), _this, f.getName()); + values.add(f.getName() + "=" + DoToString(_thisO)); + } + if(_this.getClass().isArray() || _this.getClass().getDeclaredFields().length > 0 && !hasAnnotations) { + // Use the default toString on this object. This can happen, because we call ourselves recursively. + if(_this.getClass().isArray()) { + int length = Array.getLength(_this); + StringBuilder b = new StringBuilder(); + b.append(_this.getClass().getSimpleName()).append(" {"); + for(int i = 0; i < length; i++) { + if(i > 0) { + b.append(", "); + } + // Do not recurse further, as self referential arrays would cause a SOE, and to properly + // avoid that would require a much more complex system. + b.append(Objects.toString(Array.get(_this, i))); + } + b.append('}'); + return b.toString(); + } else { + return _this.toString(); + } + } + return _this.getClass().getSimpleName() + " {" + StringUtils.Join(values, ", ") + '}'; + } + +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/Pair.java b/src/main/java/com/laytonsmith/PureUtilities/Pair.java index 0b78ad293c..f964c92494 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Pair.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Pair.java @@ -1,35 +1,35 @@ - package com.laytonsmith.PureUtilities; import java.util.Map; import java.util.Objects; /** - * Creates an object pair. The hashcode and equals functions have been overridden - * to use the underlying object's hash code and equals combined. The underlying - * objects may be null. + * Creates an object pair. The hashcode and equals functions have been overridden to use the underlying object's hash + * code and equals combined. The underlying objects may be null. + * * @param The first object's type * @param The second object's type */ public class Pair implements Map.Entry { - private final A fst; - private B snd; + private final A fst; + private B snd; /** * Creates a new Pair with the specified values. + * * @param a - * @param b + * @param b */ - public Pair(A a, B b) { - fst = a; - snd = b; - } + public Pair(A a, B b) { + fst = a; + snd = b; + } - @Override - public String toString() { - return "<" + Objects.toString(fst) + ", " + Objects.toString(snd) + ">"; - } + @Override + public String toString() { + return "<" + Objects.toString(fst) + ", " + Objects.toString(snd) + ">"; + } @Override public int hashCode() { @@ -41,38 +41,36 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (obj == null) { + if(obj == null) { return false; } - if (getClass() != obj.getClass()) { + if(getClass() != obj.getClass()) { return false; } final Pair other = (Pair) obj; - if (!Objects.equals(this.fst, other.fst)) { + if(!Objects.equals(this.fst, other.fst)) { return false; } - if (!Objects.equals(this.snd, other.snd)) { + if(!Objects.equals(this.snd, other.snd)) { return false; } return true; } - - @Override - public A getKey() { - return fst; - } + public A getKey() { + return fst; + } @Override - public B getValue() { - return snd; - } + public B getValue() { + return snd; + } @Override - public B setValue(B value) { - B old = snd; - snd = (B)value; - return old; - } + public B setValue(B value) { + B old = snd; + snd = (B) value; + return old; + } } diff --git a/src/main/java/com/laytonsmith/PureUtilities/Point3D.java b/src/main/java/com/laytonsmith/PureUtilities/Point3D.java new file mode 100644 index 0000000000..ad4a209a3e --- /dev/null +++ b/src/main/java/com/laytonsmith/PureUtilities/Point3D.java @@ -0,0 +1,111 @@ +package com.laytonsmith.PureUtilities; + +/** + * Represents a point in 3D space. + */ +public class Point3D { + + /** + * A Point3D located at [0, 0, 0] (zero) + */ + public static final Point3D ZERO = new Point3D(0, 0, 0); + + protected final double x; + protected final double y; + protected final double z; + + /** + * Copy constructor. + * + * @param other the other point + */ + public Point3D(Point3D other) { + this.x = other.x; + this.y = other.y; + this.z = other.z; + } + + /** + * Initializes the X and Y values. Z is initialized to 0. + */ + public Point3D(double x, double y) { + this.x = x; + this.y = y; + this.z = 0; + } + + /** + * Initializes the X, Y, and Z values. + * + * @param x the x value + * @param y the y value + * @param z the z value + */ + public Point3D(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Gets the X value of this point. + * + * @return the x value + */ + public double X() { + return x; + } + + /** + * Gets the Y value of this point. + * + * @return the y value + */ + public double Y() { + return y; + } + + /** + * Gets the Z value of this point. + * + * @return the z value + */ + public double Z() { + return z; + } + + /** + * Returns a point of this point added to another point. + * + * @param other the other vector + * @return the translated point + */ + public Point3D translate(Point3D other) { + return new Point3D(x + other.x, y + other.y, z + other.z); + } + + /** + * Gets the distance squared between this point and another. + * + * @param other the other point + * @return the distance squared + */ + public double distanceSquared(Point3D other) { + double dX = x - other.x; + double dY = y - other.y; + double dZ = z - other.z; + + //for efficiency, we write this out a longer way + return dX * dX + dY * dY + dZ * dZ; + } + + /** + * Gets the distance between this point and another. + * + * @param other the other point + * @return the distance + */ + public double distance(Point3D other) { + return Math.sqrt(distanceSquared(other)); + } +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/Preferences.java b/src/main/java/com/laytonsmith/PureUtilities/Preferences.java index ea2d85529b..54bdf0e047 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Preferences.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Preferences.java @@ -1,4 +1,3 @@ - package com.laytonsmith.PureUtilities; import com.laytonsmith.PureUtilities.Common.FileUtil; @@ -9,6 +8,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Properties; import java.util.SortedSet; import java.util.TreeSet; @@ -16,241 +16,509 @@ import java.util.logging.Logger; /** - * This class allows an application to more easily manage user preferences. As - * an application grows, more preferences will likely be added, but if the application - * uses flat file storage, managing these preferences while adding new preferences - * can be difficult. This class manages, documents, and provides default values, all the - * while not interfering with changes the user has made, meaning that you are free - * to add new preferences, or change default values, without fear of changing - * values that the user has specifically set. For sample usage, see + * This class allows an application to more easily manage user preferences. As an application grows, more preferences + * will likely be added, but if the application uses flat file storage, managing these preferences while adding new + * preferences can be difficult. This class manages, documents, and provides default values, all the while not + * interfering with changes the user has made, meaning that you are free to add new preferences, or change default + * values, without fear of changing values that the user has specifically set. For sample usage, see * https://gist.github.com/1042094 */ public class Preferences { - private final Map prefs = new HashMap(); - private final String appName; - private final Logger logger; - - private File prefFile; - - private String header = ""; - + + private final Map> prefs = new HashMap<>(); + private final String appName; + @SuppressWarnings("NonConstantLogger") + private final Logger logger; + + private File prefFile; + + private String header = ""; + private int lineLength = 120; - - /** - * The type a particular preference can be. The value will be cast to the given type - * if possible. NUMBER and DOUBLE are guaranteed to be castable to a Double. NUMBER - * can also sometimes be cast to an int. BOOLEAN is cast to a boolean, and may be stored - * in the preferences file as either true/false, yes/no, on/off, or a number, which - * get parsed accordingly. STRING can be any value. - */ - public enum Type{ - NUMBER, BOOLEAN, STRING, INT, DOUBLE - } - - /** - * An object corresponding to a single preference - */ - public static class Preference{ - /** - * The name of the preference - */ - public String name; - /** - * The value of the preference, as a string - */ - public String value; - /** - * The allowed type of this value - */ - public Type allowed; - /** - * The description of this preference. Used to write out to file. - */ - public String description; - - /** - * The object representation of this value. Should not be used - * directly. - */ - public Object objectValue; - - public Preference(String name, String def, Type allowed, String description) { - this.name = name; - this.value = def; - this.allowed = allowed; - this.description = description; - } - } - - /** - * Provide the name of the app, and logger, for recording errors, and a list - * of defaults, in case the value is not provided by the user, or an invalid - * value is provided. It also writes a custom header at the top of the file. - * Newlines are supported, but only \n - */ - public Preferences(String appName, Logger logger, List defaults, String header){ - this.appName = appName; - this.logger = logger; - for(Preference p : defaults){ - prefs.put(p.name, p); - } - if(!header.trim().isEmpty()){ - this.header = "# " + header.replaceAll("\n", "\n# "); - } - } - - /** - * Provide the name of the app, and logger, for recording errors, and a list - * of defaults, in case the value is not provided by the user, or an invalid - * value is provided. - */ - public Preferences(String appName, Logger logger, List defaults){ - this(appName, logger, defaults, ""); - } - - /** - * Given a file that the preferences are supposedly stored in, this - * function will try to load the preferences. If the preferences don't exist, - * or they are incomplete, this will also fill in the missing values, and - * store the now complete preferences in the file location specified. - * @param prefFile - * @throws Exception - */ - public void init(File prefFile) throws IOException { - this.prefFile = prefFile; - if(prefFile != null && prefFile.exists()){ - Properties userProperties = new Properties(); - FileInputStream in = new FileInputStream(prefFile); - userProperties.load(in); - in.close(); - for(String key : userProperties.stringPropertyNames()){ - String val = userProperties.getProperty(key); - String value = getObject(val, ((Preference)prefs.get(key))).toString(); - Object ovalue = getObject(val, ((Preference)prefs.get(key))); - Preference p1 = prefs.get(key); - Preference p2; - if(p1 != null){ - p2 = new Preference(p1.name, value, p1.allowed, p1.description); - } else { - p2 = new Preference(key, val, Type.STRING, ""); - } - p2.objectValue = ovalue; - prefs.put(key, p2); - } - } - save(); - } - - private Object getObject(String value, Preference p){ - if(p == null){ - return value; - } - if(value.equalsIgnoreCase("null")){ - return getObject(p.value, p); - } - switch(p.allowed){ - case INT: - try{ - return Integer.parseInt(value); - } catch (NumberFormatException e){ - logger.log(Level.WARNING, "[" + appName + "] expects the value of " + p.name + " to be an integer. Using the default of " + p.value); - return Integer.parseInt(p.value); - } - case DOUBLE: - try{ - return Double.parseDouble(value); - } catch (NumberFormatException e){ - logger.log(Level.WARNING, "[" + appName + "] expects the value of " + p.name + " to be an double. Using the default of " + p.value); - return Double.parseDouble(p.value); - } - case BOOLEAN: - try{ - return getBoolean(value); - } catch (NumberFormatException e){ - logger.log(Level.WARNING, "[" + appName + "] expects the value of " + p.name + " to be an boolean. Using the default of " + p.value); - return getBoolean(p.value); - } - case NUMBER: - try{ - return Integer.parseInt(value); - } catch(NumberFormatException e){ - try{ - return Double.parseDouble(value); - } catch(NumberFormatException f){ - logger.log(Level.WARNING, "[" + appName + "] expects the value of " + p.name + " to be a number. Using the default of " + p.value); - try{ - return Integer.parseInt(p.value); - } catch(NumberFormatException g){ - return Double.parseDouble(p.value); - } - } - } - case STRING: - default: - return value; - } - - } - - private Boolean getBoolean(String value){ - if(value.equalsIgnoreCase("true")){ - return true; - } else if(value.equalsIgnoreCase("false")){ - return false; - } else if(value.equalsIgnoreCase("yes")){ - return true; - } else if(value.equalsIgnoreCase("no")){ - return false; - } else if(value.equalsIgnoreCase("on")){ - return true; - } else if(value.equalsIgnoreCase("off")){ - return false; - } else { - double d = Double.parseDouble(value); - if(d == 0){ - return false; - } else { - return true; - } - } - } - - /** - * Returns the value of a preference, cast to the appropriate type. - * @param name - * @return - */ - public Object getPreference(String name){ - if(prefs.get(name).objectValue == null){ - prefs.get(name).objectValue = getObject(prefs.get(name).value, prefs.get(name)); - } - return prefs.get(name).objectValue; - } - - private void save(){ - try { - StringBuilder b = new StringBuilder(); - String nl = System.getProperty("line.separator"); - - b.append("# This file is generated automatically. Changes made to the values of this file") - .append(nl) - .append("# will persist, but changes to comments will not.") - .append(nl).append(nl); - if(!header.trim().isEmpty()){ - b.append(header).append(nl).append(nl); - } - SortedSet keys = new TreeSet(prefs.keySet()) {}; - for (String key : keys) { - Preference p = prefs.get(key); + + /** + * The type a particular preference can be. The value will be cast to the given type if possible. NUMBER and DOUBLE + * are guaranteed to be castable to a Double. NUMBER can also sometimes be cast to an int. BOOLEAN is cast to a + * boolean, and may be stored in the preferences file as either true/false, yes/no, on/off, or a number, which get + * parsed accordingly. STRING can be any value. FILE is interpreted as a File object (whether or not it exists). + */ + public enum Type { + /** + * This is a number, either a double or an int, depending on the input + */ + NUMBER, + /** + * This is a true or false value. The following words mean true: true, yes, on. The following words mean false: + * false, no, off. Case does not matter. + */ + BOOLEAN, + /** + * This can be any value, and is returned as a string as is. + */ + STRING, + /** + * This must an integer. + */ + INT, + /** + * This must be a double. + */ + DOUBLE, + /** + * This represents a file on the file system. The existence of the file does not matter. If the input is an + * empty string, null is returned. + */ + FILE + } + + /** + * An object corresponding to a single preference + */ + public static class Preference { + + /** + * The name of the preference + */ + @ObjectHelpers.StandardField + public String name; + /** + * The value of the preference, as a string + */ + @ObjectHelpers.ToString + public String value; + /** + * The allowed type of this value + */ + public Type allowed; + /** + * The description of this preference. Used to write out to file. + */ + public String description; + + /** + * The object representation of this value. Should not be used directly. + */ + public Object objectValue; + + /** + * The group name, by default a group named "General" with no description, and a sort order of 0 + */ + @ObjectHelpers.ToString + public GroupData group = new GroupData("General").setSortOrder(0); + /** + * The preference sort order, by default 100. Sorting takes place within groups, with preferences + * with identical sort values sorted alphabetically. + */ + public int sort = 100; + + public Preference(String name, String def, Type allowed, String description) { + this.name = name; + this.value = def; + this.allowed = allowed; + this.description = description; + } + + public Preference(String name, String def, Type allowed, String description, GroupData group) { + this(name, def, allowed, description); + this.group = group; + } + + public Preference(String name, String def, Type allowed, String description, int sort) { + this(name, def, allowed, description); + this.sort = sort; + } + + public Preference(String name, String def, Type allowed, String description, GroupData group, int sort) { + this(name, def, allowed, description, group); + this.sort = sort; + } + + @Override + public String toString() { + return ObjectHelpers.DoToString(this); + } + + @Override + @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") + public boolean equals(Object o) { + return ObjectHelpers.DoEquals(this, o); + } + + @Override + public int hashCode() { + return ObjectHelpers.DoHashCode(this); + } + + } + + /** + * Provide the name of the app, and logger, for recording errors, and a list of defaults, in case the value is not + * provided by the user, or an invalid value is provided. It also writes a custom header at the top of the file. + * Newlines are supported, but only \n + * @param appName + * @param logger + * @param defaults + * @param header + */ + public Preferences(String appName, Logger logger, List defaults, String header) { + this.appName = appName; + this.logger = logger; + for(Preference p : defaults) { + if(!prefs.containsKey(p.group)) { + prefs.put(p.group, new HashMap<>()); + } + prefs.get(p.group).put(p.name, p); + } + if(!header.trim().isEmpty()) { + StringBuilder b = new StringBuilder(); + header = header.replace("\n", "\n# "); + for(String line2 : StringUtils.lineSplit(header, lineLength)) { + b.append("# ").append(line2).append(StringUtils.nl()); + } + this.header = b.toString(); + } + } + + /** + * A class that represents the groups that preferences can be in. The only required parameter is the name. + */ + public static class GroupData implements Comparable { + private final String name; + private int sort = 100; + private String description = null; + + public GroupData(String name) { + this.name = name; + } + + /** + * Sets the sort order. If the sort order is the same, it is alphabetical. Note that the default + * sort order is 100. + * @param sort + * @return + */ + public GroupData setSortOrder(int sort) { + this.sort = sort; + return this; + } + + /** + * A description of the category itself. If empty, no description is added. + * @param description + * @return + */ + public GroupData setDescription(String description) { + this.description = description; + return this; + } + + public String getName() { + return name; + } + + public int getSort() { + return sort; + } + + public String getDescription() { + return description; + } + + @Override + public int compareTo(GroupData o) { + if(this.sort < o.sort) { + return -1; + } else if(this.sort > o.sort) { + return 1; + } else { + return this.name.compareTo(o.name); + } + } + + } + + /** + * Provide the name of the app, and logger, for recording errors, and a list of defaults, in case the value is not + * provided by the user, or an invalid value is provided. + * @param appName + * @param logger + * @param defaults + */ + public Preferences(String appName, Logger logger, List defaults) { + this(appName, logger, defaults, ""); + } + + /** + * Searches through all preferences, regardless of group, and finds the Preference with the name. + * @param key + * @return + */ + private Preference getPrefFromKey(String key) { + for(Map m : prefs.values()) { + if(m.containsKey(key)) { + return m.get(key); + } + } + return null; + } + + /** + * Given a file that the preferences are supposedly stored in, this function will try to load the preferences. If + * the preferences don't exist, or they are incomplete, this will also fill in the missing values, and store the now + * complete preferences in the file location specified. + * + * @param prefFile + * @throws IOException + */ + public void init(File prefFile) throws IOException { + this.prefFile = prefFile; + if(prefFile != null && prefFile.exists()) { + Properties userProperties = new Properties(); + try(FileInputStream in = new FileInputStream(prefFile)) { + userProperties.load(in); + } + for(String key : userProperties.stringPropertyNames()) { + if(key.startsWith("[")) { + // group name, skip it. + continue; + } + Preference p = getPrefFromKey(key); + String val = userProperties.getProperty(key); + String value = Objects.toString(getObject(val, p), null); + Object ovalue = getObject(val, p); + Preference p2; + if(p != null) { + p2 = new Preference(p.name, value, p.allowed, p.description, p.group, p.sort); + } else { + p2 = new Preference(key, val, Type.STRING, ""); + } + p2.objectValue = ovalue; + if(!prefs.containsKey(p2.group)) { + prefs.put(p2.group, new HashMap<>()); + } + prefs.get(p2.group).put(key, p2); + } + } + save(); + } + + @SuppressWarnings("LoggerStringConcat") + private Object getObject(String value, Preference p) { + if(p == null) { + return value; + } + if("null".equalsIgnoreCase(value)) { + return getObject(p.value, p); + } + switch(p.allowed) { + case INT: + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + logger.log(Level.WARNING, "[" + appName + "] expects the value of " + p.name + " to be an integer. Using the default of " + p.value); + return Integer.parseInt(p.value); + } + case DOUBLE: + try { + return Double.parseDouble(value); + } catch (NumberFormatException e) { + logger.log(Level.WARNING, "[" + appName + "] expects the value of " + p.name + " to be an double. Using the default of " + p.value); + return Double.parseDouble(p.value); + } + case BOOLEAN: { + Boolean v = getBoolean(value); + if(v == null) { + logger.log(Level.WARNING, "[" + appName + "] expects the value of " + p.name + " to be an boolean. Using the default of " + p.value); + return getBoolean(p.value); + } + return v; + } + case NUMBER: + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + try { + return Double.parseDouble(value); + } catch (NumberFormatException f) { + logger.log(Level.WARNING, "[" + appName + "] expects the value of " + p.name + " to be a number. Using the default of " + p.value); + try { + return Integer.parseInt(p.value); + } catch (NumberFormatException g) { + return Double.parseDouble(p.value); + } + } + } + case FILE: + if(value == null || "".equals(value.trim())) { + return null; + } + return new File(value); + case STRING: + default: + return value; + } + + } + + /** + * Given a preference string, determines whether or not this is a Boolean object. + * The following string values are considered true: "true", "yes", "on", a non-zero double + * The following string values are considered false: "false", "no", "off", zero + * + * For any other value, this method returns null. + * @param value + * @return + */ + public static Boolean getBoolean(String value) { + if(value.equalsIgnoreCase("true")) { + return true; + } else if(value.equalsIgnoreCase("false")) { + return false; + } else if(value.equalsIgnoreCase("yes")) { + return true; + } else if(value.equalsIgnoreCase("no")) { + return false; + } else if(value.equalsIgnoreCase("on")) { + return true; + } else if(value.equalsIgnoreCase("off")) { + return false; + } else { + try { + double d = Double.parseDouble(value); + return d != 0; + } catch (NumberFormatException e) { + return null; + } + } + } + + private Object getSafePreference(String name, Type type) { + Preference p = getPrefFromKey(name); + if(p.allowed != type) { + throw new IllegalArgumentException("Expecting " + p.allowed + " but " + type + " was requested"); + } + if(p.objectValue == null) { + p.objectValue = getObject(p.value, p); + } + return p.objectValue; + } + + /** + * Returns the given boolean preference. + * + * @param name The name of the preference + * @return The preference value, as a boolean + * @throws IllegalArgumentException If the preference was not defined as being a boolean + */ + public Boolean getBooleanPreference(String name) { + return (Boolean) getSafePreference(name, Type.BOOLEAN); + } + + /** + * Returns the given double preference. + * + * @param name The name of the preference + * @return The preference value, as a double + * @throws IllegalArgumentException If the preference was not defined as being a double + */ + public Double getDoublePreference(String name) { + return (Double) getSafePreference(name, Type.DOUBLE); + } + + /** + * Returns the given File preference. If the preference was blank, then null is returned. + * + * @param name The name of the preference + * @return The preference value, as a File + * @throws IllegalArgumentException If the preference was not defined as being a File + */ + public File getFilePreference(String name) { + return (File) getSafePreference(name, Type.FILE); + } + + /** + * Returns the given integer preference. + * + * @param name The name of the preference + * @return The preference value, as an integer + * @throws IllegalArgumentException If the preference was not defined as being an integer + */ + public Integer getIntegerPreference(String name) { + return (Integer) getSafePreference(name, Type.INT); + } + + /** + * Returns the given number preference. + * + * @param name The name of the preference + * @return The preference value, as a number + * @throws IllegalArgumentException If the preference was not defined as being a number + */ + public Number getNumberPreference(String name) { + return (Number) getSafePreference(name, Type.NUMBER); + } + + /** + * Returns the given string preference. + * + * @param name The name of the preference + * @return The preference value, as a string + * @throws IllegalArgumentException If the preference was not defined as being a string + */ + public String getStringPreference(String name) { + return (String) getSafePreference(name, Type.STRING); + } + + @SuppressWarnings("UseSpecificCatch") + private void save() { + try { + StringBuilder b = new StringBuilder(); + String nl = System.getProperty("line.separator"); + + b.append("# This file is generated automatically. Changes made to the values of this file") + .append(nl) + .append("# will persist, but changes to comments will not. For windows file paths,") + .append(nl) + .append("# use either / or \\\\, but not a single \\.") + .append(nl).append(nl); + if(!header.trim().isEmpty()) { + b.append(header).append(nl).append(nl); + } + SortedSet prfs = new TreeSet<>((Preference t, Preference t1) -> { + int groupSort = t.group.compareTo(t1.group); + if(groupSort != 0) { + return groupSort; + } + if(t.sort == t1.sort) { + return t.name.compareTo(t1.name); + } + return t.sort < t1.sort ? -1 : 1; + }); + for(Map m : prefs.values()) { + prfs.addAll(m.values()); + } + GroupData currentGroup = null; + for(Preference p : prfs) { + if(!p.group.equals(currentGroup)) { + b.append("[").append(p.group.getName()).append("]\n"); + if(p.group.getDescription() != null && !p.group.getDescription().trim().equals("")) { + for(String line2 : StringUtils.lineSplit(p.group.getDescription(), lineLength)) { + b.append("# ").append(line2).append(nl); + } + b.append(nl); + } + currentGroup = p.group; + } +// Preference p = getPrefFromKey(key); String description = "This value is not used in " + appName; - if(!p.description.trim().isEmpty()){ + if(!p.description.trim().isEmpty()) { description = p.description; } StringBuilder c = new StringBuilder(); boolean first = true; - for(String line : description.split("\n|\r\n|\n\r")){ - for(String line2 : StringUtils.lineSplit(line, lineLength)){ - if(first){ + for(String line : description.split("\n|\r\n|\n\r")) { + for(String line2 : StringUtils.lineSplit(line, lineLength)) { + if(first) { c.append("# ").append(line2); first = false; } else { @@ -258,30 +526,37 @@ private void save(){ } } } - b.append(c).append(nl).append(p.name).append("=").append(p.value).append(nl).append(nl); + b.append(c).append(nl).append(p.name).append("="); + if(p.allowed == Type.FILE && p.value != null) { + b.append(p.value.replace("\\", "\\\\")); + } else { + b.append(p.value); + } + b.append(nl).append(nl); + } + if(prefFile != null && !prefFile.exists()) { + prefFile.getAbsoluteFile().getParentFile().mkdirs(); + prefFile.createNewFile(); } - if(prefFile != null && !prefFile.exists()){ - prefFile.getAbsoluteFile().getParentFile().mkdirs(); - prefFile.createNewFile(); - } - if(prefFile != null){ + if(prefFile != null) { FileUtil.write(b.toString(), prefFile); } - } catch (Exception ex) { - logger.log(Level.WARNING, "[" + appName + "] Could not write out preferences file: " + (prefFile!=null?prefFile.getAbsolutePath():"null"), ex); - } - } - + } catch (Exception ex) { + logger.log(Level.WARNING, "[" + appName + "] Could not write out preferences file: " + (prefFile != null ? prefFile.getAbsolutePath() : "null"), ex); + } + } + /** * Sets the comment line length. + * * @param lineLength The length, an integer greater than 0. * @throws IllegalArgumentException If {@code lineLength} is less than 1. */ - public void setLineLength(int lineLength){ - if(lineLength < 1){ + public void setLineLength(int lineLength) { + if(lineLength < 1) { throw new IllegalArgumentException(); } this.lineLength = lineLength; } - + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/ProgressIterator.java b/src/main/java/com/laytonsmith/PureUtilities/ProgressIterator.java index 74a272d031..cc2b673a4f 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/ProgressIterator.java +++ b/src/main/java/com/laytonsmith/PureUtilities/ProgressIterator.java @@ -1,17 +1,18 @@ package com.laytonsmith.PureUtilities; /** - * A progress iterator is an interface that can be passed in to any long running internal process - * that may want to provide progress updates to the controlling code. There is a method - * for updating the controller when a significant progress event happens. + * A progress iterator is an interface that can be passed in to any long running internal process that may want to + * provide progress updates to the controlling code. There is a method for updating the controller when a significant + * progress event happens. */ public interface ProgressIterator { + /** - * Called once a progress change is detected. This is the "current" value of the - * progress, which in combination with the total progress can be used to determine - * the progress percentage (by finding current/total). - * @param current The current progress, always less than or equal to total, which - * represents the current progress of the task. + * Called once a progress change is detected. This is the "current" value of the progress, which in combination with + * the total progress can be used to determine the progress percentage (by finding current/total). + * + * @param current The current progress, always less than or equal to total, which represents the current progress of + * the task. * @param total The total progress, which once reaches this value is "100% done" */ void progressChanged(double current, double total); diff --git a/src/main/java/com/laytonsmith/PureUtilities/PropertiesManager.java b/src/main/java/com/laytonsmith/PureUtilities/PropertiesManager.java index a747ddc5d9..718a61f2b3 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/PropertiesManager.java +++ b/src/main/java/com/laytonsmith/PureUtilities/PropertiesManager.java @@ -7,13 +7,11 @@ import java.util.LinkedHashMap; /** - * This class is meant to be a nearly drop in replacement to the - * {@link java.util.Properties} class, but it extends - * {@code LinkedHashMap} instead of - * {@code Hashtable}. It is intended for string - * based properties files, as opposed to object based properties. + * This class is meant to be a nearly drop in replacement to the {@link java.util.Properties} class, but it extends + * {@code LinkedHashMap} instead of {@code Hashtable}. It is intended for string based + * properties files, as opposed to object based properties. */ -public class PropertiesManager extends LinkedHashMap{ +public class PropertiesManager extends LinkedHashMap { public PropertiesManager() { this(""); @@ -22,146 +20,100 @@ public PropertiesManager() { /** *

* Properties are processed in terms of lines. There are two kinds of line, - * natural lines and logical lines. A natural line is defined - * as a line of characters that is terminated either by a set of line - * terminator characters ( - * \n or - * \r or - * \r\n) or by the end of the stream. A natural line may be - * either a blank line, a comment line, or hold all or some of a key-element - * pair. A logical line holds all the data of a key-element pair, which may - * be spread out across several adjacent natural lines by escaping the line - * terminator sequence with a backslash character - * \. Note that a comment line cannot be extended in this - * manner; every natural line that is a comment must have its own comment - * indicator, as described below. Lines are read from input until the end of - * the stream is reached. + * natural lines and logical lines. A natural line is defined as a line of characters that is + * terminated either by a set of line terminator characters ( \n or \r or + * \r\n) or by the end of the stream. A natural line may be either a blank line, a comment line, or + * hold all or some of a key-element pair. A logical line holds all the data of a key-element pair, which may be + * spread out across several adjacent natural lines by escaping the line terminator sequence with a backslash + * character \. Note that a comment line cannot be extended in this manner; every natural line that is + * a comment must have its own comment indicator, as described below. Lines are read from input until the end of the + * stream is reached. * *

- * A natural line that contains only white space characters is considered - * blank and is ignored. A comment line has an ASCII - * '#' or - * '!' as its first non-white space character; comment lines - * are also ignored and do not encode key-element information. In addition - * to line terminators, this format considers the characters space - * ( - * ' ', - * '\u0020'), tab - * ( - * '\t', - * '\u0009'), and form feed - * ( - * '\f', - * '\u000C') to be white space. + * A natural line that contains only white space characters is considered blank and is ignored. A comment line has + * an ASCII '#' or '!' as its first non-white space character; comment lines are also + * ignored and do not encode key-element information. In addition to line terminators, this format considers the + * characters space ( ' ', '\u0020'), tab ( '\t', + * '\u0009'), and form feed ( '\f', '\u000C') to be white space. * *

- * If a logical line is spread across several natural lines, the backslash - * escaping the line terminator sequence, the line terminator sequence, and - * any white space at the start of the following line have no affect on the - * key or element values. The remainder of the discussion of key and element - * parsing (when loading) will assume all the characters constituting the - * key and element appear on a single natural line after line continuation - * characters have been removed. Note that it is not sufficient to - * only examine the character preceding a line terminator sequence to decide - * if the line terminator is escaped; there must be an odd number of - * contiguous backslashes for the line terminator to be escaped. Since the - * input is processed from left to right, a non-zero even number of - * 2n contiguous backslashes before a line terminator (or elsewhere) - * encodes n + * If a logical line is spread across several natural lines, the backslash escaping the line terminator sequence, + * the line terminator sequence, and any white space at the start of the following line have no affect on the key or + * element values. The remainder of the discussion of key and element parsing (when loading) will assume all the + * characters constituting the key and element appear on a single natural line after line continuation characters + * have been removed. Note that it is not sufficient to only examine the character preceding a line + * terminator sequence to decide if the line terminator is escaped; there must be an odd number of contiguous + * backslashes for the line terminator to be escaped. Since the input is processed from left to right, a non-zero + * even number of 2n contiguous backslashes before a line terminator (or elsewhere) encodes n * backslashes after escape processing. * *

- * The key contains all of the characters in the line starting with the - * first non-white space character and up to, but not including, the first - * unescaped - * '=', - * ':', or white space character other than a line terminator. - * All of these key termination characters may be included in the key by - * escaping them with a preceding backslash character; for example,

+ * The key contains all of the characters in the line starting with the first non-white space character and up to, + * but not including, the first unescaped '=', ':', or white space character other than a + * line terminator. All of these key termination characters may be included in the key by escaping them with a + * preceding backslash character; for example, + *

* * \:\=

* - * would be the two-character key - * ":=". Line terminator characters can be included using - * \r and - * \n escape sequences. Any white space after the key is - * skipped; if the first non-white space character after the key is - * '=' or - * ':', then it is ignored and any white space characters after - * it are also skipped. All remaining characters on the line become part of - * the associated element string; if there are no remaining characters, the - * element is the empty string - * "". Once the raw character sequences constituting - * the key and element are identified, escape processing is performed as - * described above. + * would be the two-character key ":=". Line terminator characters can be included using + * \r and \n escape sequences. Any white space after the key is skipped; if the first + * non-white space character after the key is '=' or ':', then it is ignored and any white + * space characters after it are also skipped. All remaining characters on the line become part of the associated + * element string; if there are no remaining characters, the element is the empty string "". + * Once the raw character sequences constituting the key and element are identified, escape processing is performed + * as described above. * *

- * As an example, each of the following three lines specifies the key - * "Truth" and the associated element value - * "Beauty": + * As an example, each of the following three lines specifies the key "Truth" and the associated + * element value "Beauty": *

*

-	 * Truth = Beauty
-	 *  Truth:Beauty
-	 * Truth                    :Beauty
-	 * 
As another example, the following three lines specify a single - * property: + * Truth = Beauty Truth:Beauty Truth :Beauty + * As another example, the following three lines specify a single property: *

*

-	 * fruits                           apple, banana, pear, \
-	 *                                  cantaloupe, watermelon, \
-	 *                                  kiwi, mango
-	 * 
The key is - * "fruits" and the associated element is: + * fruits apple, banana, pear, \ cantaloupe, watermelon, \ kiwi, mango + * The key is "fruits" and the associated element is: *

- *

"apple, banana, pear, cantaloupe, watermelon, kiwi, mango"
- * Note that a space appears before each - * \ so that a space will appear after each comma in the final - * result; the - * \, line terminator, and leading white space on the - * continuation line are merely discarded and are not replaced by one - * or more other characters. + *
"apple, banana, pear, cantaloupe, watermelon, kiwi, mango"
Note that a space appears before each + * \ so that a space will appear after each comma in the final result; the \, line + * terminator, and leading white space on the continuation line are merely discarded and are not replaced by + * one or more other characters. *

* As a third example, the line: *

*

cheeses
-	 * 
specifies that the key is - * "cheeses" and the associated element is the empty string + * specifies that the key is "cheeses" and the associated element is the empty string * "".

*

    *
  • Octal escapes are not recognized. * - *
  • The character sequence - * \b does not + *
  • The character sequence \b does not * represent a backspace character. * - *
  • The method does not treat a backslash character, - * \, before a non-valid escape character as an error; the - * backslash is silently dropped. For example, in a Java string the sequence - * "\z" would cause a compile time error. In contrast, this - * method silently drops the backslash. Therefore, this method treats the - * two character sequence - * "\b" as equivalent to the single character - * 'b'. + *
  • The method does not treat a backslash character, \, before a non-valid escape character as an + * error; the backslash is silently dropped. For example, in a Java string the sequence "\z" would + * cause a compile time error. In contrast, this method silently drops the backslash. Therefore, this method treats + * the two character sequence "\b" as equivalent to the single character 'b'. * - *
  • Escapes are not necessary for single and double quotes; however, by - * the rule above, single and double quote characters preceded by a - * backslash still yield single and double quote characters, respectively. + *
  • Escapes are not necessary for single and double quotes; however, by the rule above, single and double quote + * characters preceded by a backslash still yield single and double quote characters, respectively. * *
  • Only a single 'u' character is allowed in a Uniocde escape sequence. + *
*/ public PropertiesManager(String properties) { try { @@ -172,253 +124,272 @@ public PropertiesManager(String properties) { throw new Error(ex); } } - - public PropertiesManager(InputStream stream) throws IOException{ + + public PropertiesManager(InputStream stream) throws IOException { load(new LineReader(stream)); } - + private void load(LineReader lr) throws IOException { char[] convtBuf = new char[1024]; - int limit; - int keyLen; - int valueStart; - char c; - boolean hasSep; - boolean precedingBackslash; + int limit; + int keyLen; + int valueStart; + char c; + boolean hasSep; + boolean precedingBackslash; - while ((limit = lr.readLine()) >= 0) { - c = 0; - keyLen = 0; - valueStart = limit; - hasSep = false; + while((limit = lr.readLine()) >= 0) { + c = 0; + keyLen = 0; + valueStart = limit; + hasSep = false; - //System.out.println("line=<" + new String(lineBuf, 0, limit) + ">"); - precedingBackslash = false; - while (keyLen < limit) { - c = lr.lineBuf[keyLen]; - //need check if escaped. - if ((c == '=' || c == ':') && !precedingBackslash) { - valueStart = keyLen + 1; - hasSep = true; - break; - } else if ((c == ' ' || c == '\t' || c == '\f') && !precedingBackslash) { - valueStart = keyLen + 1; - break; - } - if (c == '\\') { - precedingBackslash = !precedingBackslash; - } else { - precedingBackslash = false; - } - keyLen++; - } - while (valueStart < limit) { - c = lr.lineBuf[valueStart]; - if (c != ' ' && c != '\t' && c != '\f') { - if (!hasSep && (c == '=' || c == ':')) { - hasSep = true; - } else { - break; - } - } - valueStart++; - } - String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf); - String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf); - put(key, value); - } + //System.out.println("line=<" + new String(lineBuf, 0, limit) + ">"); + precedingBackslash = false; + while(keyLen < limit) { + c = lr.lineBuf[keyLen]; + //need check if escaped. + if((c == '=' || c == ':') && !precedingBackslash) { + valueStart = keyLen + 1; + hasSep = true; + break; + } else if((c == ' ' || c == '\t' || c == '\f') && !precedingBackslash) { + valueStart = keyLen + 1; + break; + } + if(c == '\\') { + precedingBackslash = !precedingBackslash; + } else { + precedingBackslash = false; + } + keyLen++; + } + while(valueStart < limit) { + c = lr.lineBuf[valueStart]; + if(c != ' ' && c != '\t' && c != '\f') { + if(!hasSep && (c == '=' || c == ':')) { + hasSep = true; + } else { + break; + } + } + valueStart++; + } + String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf); + String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf); + put(key, value); + } } - - - + /* Read in a "logical line" from an InputStream/Reader, skip all comment - * and blank lines and filter out those leading whitespace characters - * (\u0020, \u0009 and \u000c) from the beginning of a "natural line". - * Method returns the char length of the "logical line" and stores - * the line in "lineBuf". - */ - class LineReader { - public LineReader(InputStream inStream) { - this.inStream = inStream; - inByteBuf = new byte[8192]; - } + * and blank lines and filter out those leading whitespace characters + * (\u0020, \u0009 and \u000c) from the beginning of a "natural line". + * Method returns the char length of the "logical line" and stores + * the line in "lineBuf". + */ + class LineReader { + + public LineReader(InputStream inStream) { + this.inStream = inStream; + inByteBuf = new byte[8192]; + } - public LineReader(Reader reader) { - this.reader = reader; - inCharBuf = new char[8192]; - } + public LineReader(Reader reader) { + this.reader = reader; + inCharBuf = new char[8192]; + } + + byte[] inByteBuf; + char[] inCharBuf; + char[] lineBuf = new char[1024]; + int inLimit = 0; + int inOff = 0; + InputStream inStream; + Reader reader; - byte[] inByteBuf; - char[] inCharBuf; - char[] lineBuf = new char[1024]; - int inLimit = 0; - int inOff = 0; - InputStream inStream; - Reader reader; + int readLine() throws IOException { + int len = 0; + char c = 0; - int readLine() throws IOException { - int len = 0; - char c = 0; + boolean skipWhiteSpace = true; + boolean isCommentLine = false; + boolean isNewLine = true; + boolean appendedLineBegin = false; + boolean precedingBackslash = false; + boolean skipLF = false; - boolean skipWhiteSpace = true; - boolean isCommentLine = false; - boolean isNewLine = true; - boolean appendedLineBegin = false; - boolean precedingBackslash = false; - boolean skipLF = false; + while(true) { + if(inOff >= inLimit) { + inLimit = (inStream == null) ? reader.read(inCharBuf) + : inStream.read(inByteBuf); + inOff = 0; + if(inLimit <= 0) { + if(len == 0 || isCommentLine) { + return -1; + } + return len; + } + } + if(inStream != null) { + //The line below is equivalent to calling a + //ISO8859-1 decoder. + c = (char) (0xff & inByteBuf[inOff++]); + } else { + c = inCharBuf[inOff++]; + } + if(skipLF) { + skipLF = false; + if(c == '\n') { + continue; + } + } + if(skipWhiteSpace) { + if(c == ' ' || c == '\t' || c == '\f') { + continue; + } + if(!appendedLineBegin && (c == '\r' || c == '\n')) { + continue; + } + skipWhiteSpace = false; + appendedLineBegin = false; + } + if(isNewLine) { + isNewLine = false; + if(c == '#' || c == '!') { + isCommentLine = true; + continue; + } + } - while (true) { - if (inOff >= inLimit) { - inLimit = (inStream==null)?reader.read(inCharBuf) - :inStream.read(inByteBuf); - inOff = 0; - if (inLimit <= 0) { - if (len == 0 || isCommentLine) { - return -1; - } - return len; - } - } - if (inStream != null) { - //The line below is equivalent to calling a - //ISO8859-1 decoder. - c = (char) (0xff & inByteBuf[inOff++]); - } else { - c = inCharBuf[inOff++]; - } - if (skipLF) { - skipLF = false; - if (c == '\n') { - continue; - } - } - if (skipWhiteSpace) { - if (c == ' ' || c == '\t' || c == '\f') { - continue; - } - if (!appendedLineBegin && (c == '\r' || c == '\n')) { - continue; - } - skipWhiteSpace = false; - appendedLineBegin = false; - } - if (isNewLine) { - isNewLine = false; - if (c == '#' || c == '!') { - isCommentLine = true; - continue; - } - } + if(c != '\n' && c != '\r') { + lineBuf[len++] = c; + if(len == lineBuf.length) { + int newLength = lineBuf.length * 2; + if(newLength < 0) { + newLength = Integer.MAX_VALUE; + } + char[] buf = new char[newLength]; + System.arraycopy(lineBuf, 0, buf, 0, lineBuf.length); + lineBuf = buf; + } + //flip the preceding backslash flag + if(c == '\\') { + precedingBackslash = !precedingBackslash; + } else { + precedingBackslash = false; + } + } else { + // reached EOL + if(isCommentLine || len == 0) { + isCommentLine = false; + isNewLine = true; + skipWhiteSpace = true; + len = 0; + continue; + } + if(inOff >= inLimit) { + inLimit = (inStream == null) + ? reader.read(inCharBuf) + : inStream.read(inByteBuf); + inOff = 0; + if(inLimit <= 0) { + return len; + } + } + if(precedingBackslash) { + len -= 1; + //skip the leading whitespace characters in following line + skipWhiteSpace = true; + appendedLineBegin = true; + precedingBackslash = false; + if(c == '\r') { + skipLF = true; + } + } else { + return len; + } + } + } + } + } - if (c != '\n' && c != '\r') { - lineBuf[len++] = c; - if (len == lineBuf.length) { - int newLength = lineBuf.length * 2; - if (newLength < 0) { - newLength = Integer.MAX_VALUE; - } - char[] buf = new char[newLength]; - System.arraycopy(lineBuf, 0, buf, 0, lineBuf.length); - lineBuf = buf; - } - //flip the preceding backslash flag - if (c == '\\') { - precedingBackslash = !precedingBackslash; - } else { - precedingBackslash = false; - } - } - else { - // reached EOL - if (isCommentLine || len == 0) { - isCommentLine = false; - isNewLine = true; - skipWhiteSpace = true; - len = 0; - continue; - } - if (inOff >= inLimit) { - inLimit = (inStream==null) - ?reader.read(inCharBuf) - :inStream.read(inByteBuf); - inOff = 0; - if (inLimit <= 0) { - return len; - } - } - if (precedingBackslash) { - len -= 1; - //skip the leading whitespace characters in following line - skipWhiteSpace = true; - appendedLineBegin = true; - precedingBackslash = false; - if (c == '\r') { - skipLF = true; - } - } else { - return len; - } - } - } - } - } - /* - * Converts encoded \uxxxx to unicode chars - * and changes special saved chars to their original forms - */ - private String loadConvert (char[] in, int off, int len, char[] convtBuf) { - if (convtBuf.length < len) { - int newLen = len * 2; - if (newLen < 0) { - newLen = Integer.MAX_VALUE; - } - convtBuf = new char[newLen]; - } - char aChar; - char[] out = convtBuf; - int outLen = 0; - int end = off + len; + * Converts encoded \uxxxx to unicode chars + * and changes special saved chars to their original forms + */ + private String loadConvert(char[] in, int off, int len, char[] convtBuf) { + if(convtBuf.length < len) { + int newLen = len * 2; + if(newLen < 0) { + newLen = Integer.MAX_VALUE; + } + convtBuf = new char[newLen]; + } + char aChar; + char[] out = convtBuf; + int outLen = 0; + int end = off + len; - while (off < end) { - aChar = in[off++]; - if (aChar == '\\') { - aChar = in[off++]; - if(aChar == 'u') { - // Read the xxxx - int value=0; - for (int i=0; i<4; i++) { - aChar = in[off++]; - switch (aChar) { - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - value = (value << 4) + aChar - '0'; - break; - case 'a': case 'b': case 'c': - case 'd': case 'e': case 'f': - value = (value << 4) + 10 + aChar - 'a'; - break; - case 'A': case 'B': case 'C': - case 'D': case 'E': case 'F': - value = (value << 4) + 10 + aChar - 'A'; - break; - default: - throw new IllegalArgumentException( - "Malformed \\uxxxx encoding."); - } - } - out[outLen++] = (char)value; - } else { - if (aChar == 't') aChar = '\t'; - else if (aChar == 'r') aChar = '\r'; - else if (aChar == 'n') aChar = '\n'; - else if (aChar == 'f') aChar = '\f'; - out[outLen++] = aChar; - } - } else { - out[outLen++] = aChar; - } - } - return new String (out, 0, outLen); - } + while(off < end) { + aChar = in[off++]; + if(aChar == '\\') { + aChar = in[off++]; + if(aChar == 'u') { + // Read the xxxx + int value = 0; + for(int i = 0; i < 4; i++) { + aChar = in[off++]; + switch(aChar) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + value = (value << 4) + aChar - '0'; + break; + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + value = (value << 4) + 10 + aChar - 'a'; + break; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + value = (value << 4) + 10 + aChar - 'A'; + break; + default: + throw new IllegalArgumentException( + "Malformed \\uxxxx encoding."); + } + } + out[outLen++] = (char) value; + } else { + if(aChar == 't') { + aChar = '\t'; + } else if(aChar == 'r') { + aChar = '\r'; + } else if(aChar == 'n') { + aChar = '\n'; + } else if(aChar == 'f') { + aChar = '\f'; + } + out[outLen++] = aChar; + } + } else { + out[outLen++] = aChar; + } + } + return new String(out, 0, outLen); + } } diff --git a/src/main/java/com/laytonsmith/PureUtilities/PublicSuffix.java b/src/main/java/com/laytonsmith/PureUtilities/PublicSuffix.java index 3faba795e4..a7c76d191c 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/PublicSuffix.java +++ b/src/main/java/com/laytonsmith/PureUtilities/PublicSuffix.java @@ -23,44 +23,41 @@ import java.util.regex.Pattern; /** - * This Public Suffix Service (PSS) class reads a file of rules describing - * TLD-like domain names and makes rulings on passed hostnames/domain-names - * based of the data file content. For a complete description of the expected - * file format and parsing rules, see Effective TLD Service * and Public Suffix. - * - *

This class is a rough port of the c++ file, + * This class is a rough port of the c++ file, nsEffectiveTLDService.cpp - * originally developed by Pamela Greene <pamg.bugs ATGMAILDOTCOM>. The - * class uses the first effective_tld_names.dat found on - * the CLASSPATH. The bundled jar has a version of the file taken from + * originally developed by Pamela Greene <pamg.bugs ATGMAILDOTCOM>. The class uses the first + * effective_tld_names.dat found on the CLASSPATH. The bundled jar has a version of the file taken from * Public Suffix on 10/22/2007. - * - *

To use this class, instantiate an instance and then call - * {@link #getEffectiveTLDLength(String)} passing the hostname to interrogate.. - * - *

The following description of how the code works is copied from the head - * of the c++ file. - * + * + *

+ * To use this class, instantiate an instance and then call {@link #getEffectiveTLDLength(String)} passing the hostname + * to interrogate.. + * + *

+ * The following description of how the code works is copied from the head of the c++ file. + * *

- * The list of subdomain rules is stored as a wide tree of SubdomainNodes, - * primarily to facilitate multiple levels of wildcards. Each node represents - * one level of a particular rule in the list, and stores meta-information about - * the rule it represents as well as a list (hash) of all the subdomains beneath - * it. + * The list of subdomain rules is stored as a wide tree of SubdomainNodes, primarily to facilitate multiple levels of + * wildcards. Each node represents one level of a particular rule in the list, and stores meta-information about the + * rule it represents as well as a list (hash) of all the subdomains beneath it. *

*

  • stopOK: If true, this node marks the end of a rule.
  • *
  • exception: If true, this node marks the end of an exception rule.
  • *
- *

- *For example, if the effective-TLD list contains + *

+ * For example, if the effective-TLD list contains *

foo.com
  * *.bar.com
  * !baz.bar.com
- * 
- * then the subdomain tree will look like this (conceptually; the actual order - * of the nodes in the hashes is not guaranteed): + * then the subdomain tree will look like this (conceptually; the actual order of the nodes in the hashes is not + * guaranteed): *
  * +--------------+
  * | com          |
@@ -82,239 +79,242 @@
  *                                                 | stopOK: 1    |
  *                                                 | children     |
  *                                                 +--------------+
- *
- *

- *

TODO: Add support for IDN (See java6 java.net.IDN).

- *

TODO: Add support for multiple data files

- *@author stack + * + *

+ *

+ * TODO: Add support for IDN (See java6 java.net.IDN).

+ *

+ * TODO: Add support for multiple data files

+ * + * @author stack */ -public class PublicSuffix { - private final SubdomainNode root = new SubdomainNode(false, false); - private static final String WILDCARD = "*"; - private static final String EXCEPTION = "!"; - private static final Pattern WHITESPACE = Pattern.compile("\\s+"); - private static final char DOT = '.'; - - private static final String DATA_FILENAME = "public-suffix.txt"; - - private static PublicSuffix defaultInstance; - - /** - * Returns the PublicSuffix instance based on the default data source - */ - public static PublicSuffix get(){ - if(defaultInstance == null){ - try { - defaultInstance = new PublicSuffix(); - } catch (Exception ex) { - throw new RuntimeException(ex); - } - } - return defaultInstance; - } - - /** - * Loads data file and creates a tree of subdomain nodes in memory used - * finding the effective TLD. - * @throws IOException - * @throws UnsupportedEncodingException - */ - private PublicSuffix() throws UnsupportedEncodingException, IOException { - load(); - } +public final class PublicSuffix { + + private final SubdomainNode root = new SubdomainNode(false, false); + private static final String WILDCARD = "*"; + private static final String EXCEPTION = "!"; + private static final Pattern WHITESPACE = Pattern.compile("\\s+"); + private static final char DOT = '.'; + + private static final String DATA_FILENAME = "public-suffix.dat"; + + private static PublicSuffix defaultInstance; + + /** + * Returns the PublicSuffix instance based on the default data source + */ + public static PublicSuffix get() { + if(defaultInstance == null) { + try { + defaultInstance = new PublicSuffix(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + return defaultInstance; + } + + /** + * Loads data file and creates a tree of subdomain nodes in memory used finding the effective TLD. + * + * @throws IOException + * @throws UnsupportedEncodingException + */ + private PublicSuffix() throws UnsupportedEncodingException, IOException { + load(); + } + + /** + * Finds the length in bytes of the effective TLD for the given hostname + * + * @param hostname Hostname to check. + * @return length of effective-TLD portion in passed hostname. If passed string is all effective-TLD -- + * e.g. if you '.com' -- then we return -1. If no effective-TLD found, then returns hostname.length(). + */ + public int getEffectiveTLDLength(final String hostname) { + final String normalizedHostname = normalizeHostname(hostname); + int lastDot = normalizedHostname.length(); + for(SubdomainNode node = this.root; lastDot > 0;) { + int nextDotLoc = normalizedHostname.lastIndexOf('.', lastDot - 1); + node = findNode(node, + normalizedHostname.substring(nextDotLoc + 1, lastDot), false); + if(node == null) { + break; + } + lastDot = nextDotLoc; + if(node.isException()) { + // Exception rules use one fewer levels than were matched. + break; + } + if(node.isStopOK()) { + break; + } + } + return lastDot; + } + + /** + * Normalizes characters of hostname. ASCII names are lower-cased. TOOD: If names using other + * characters than ASCII need to be normalized with a IIDNService::Normalize, RFC 3454. + * + * @param hostname + * @return normalized hostname. + */ + private String normalizeHostname(final String hostname) { + boolean isLowercase = true; + boolean isAscii = true; + for(int i = 0; i < hostname.length(); i++) { + char c = hostname.charAt(i); + if(c >= 128) { + isAscii = false; + break; + } + if(!Character.isLowerCase(c)) { + isLowercase = false; + } + } + if(!isAscii) { + // TODO: If java 6, then there is java.net.IDN#toAscii(hostname). + throw new UnsupportedOperationException("No support yet for IDN: TODO"); + } + return isLowercase ? hostname : hostname.toLowerCase(); + } + + /** + * Load effective TLD file from the CLASSPATH. + * + * @throws IOException + * @throws UnsupportedEncodingException + */ + private void load() + throws UnsupportedEncodingException, IOException { + URL u = this.getClass().getResource("/" + DATA_FILENAME); + if(u == null) { + throw new FileNotFoundException(DATA_FILENAME + " not on CLASSPATH"); + } + BufferedReader br + = new BufferedReader(new InputStreamReader(u.openStream(), "UTF-8")); + for(String line = null; (line = br.readLine()) != null;) { + if(line.length() <= 0 || line.startsWith("//")) { + continue; + } + addEffectiveTLDEntry(this.root, line); + } + } + + /** + * Given a parent node and a candidate subdomain, searches the parent's children for a matching + * subdomain and returns a pointer to the matching node if one was found. If no exact match was found and + * create is true, creates a new child node for the given subdomain and returns that. If + * no exact match was found an create is false, looks for a wildcard node (*) instead. If no wildcard + * node is found either, returns null. + * + * @param node + * @param subdomain + * @param create Typically true when the subdomain tree is being built, and false when it is being searched to + * determine a hostname's effective TLD. + * @return + */ + private SubdomainNode findNode(final SubdomainNode node, + final String subdomain, final boolean create) { + boolean exception = subdomain != null && subdomain.startsWith(EXCEPTION); + String key = exception ? subdomain.substring(1) : subdomain; + SubdomainNode newNode = node.getChildren().get(key); + if(newNode != null) { + return newNode; + } + if(create) { + // Create it and add to parent. + SubdomainNode subNode = new SubdomainNode(exception, false); + node.getChildren().put(key, subNode); + return subNode; + } + return node.getChildren().get(WILDCARD); + } + + /** + * Adds the given domain name rule to the effective-TLD tree. + * + * @param m Map to add too. + * @param line Line that starts with a hostname. + */ + private void addEffectiveTLDEntry(SubdomainNode node, + final String line) { + String hostname = WHITESPACE.split(line, 2)[0]; + for(int dotLoc = hostname.length(); dotLoc >= 0;) { + int nextDocLoc = hostname.lastIndexOf(DOT, dotLoc - 1); + String subdomain = hostname.substring(nextDocLoc + 1, dotLoc); + dotLoc = nextDocLoc; + node = findNode(node, subdomain, true); + } + + // The last node in an entry is by definition a stop-OK node. + node.setStopOK(); + } + + private void dump() { + dump(this.root.getChildren(), 0); + } - /** - * Finds the length in bytes of the effective TLD for the given - * hostname - * @param hostname Hostname to check. - * @return length of effective-TLD portion in passed hostname. - * If passed string is all effective-TLD -- e.g. if you '.com' -- then we - * return -1. If no effective-TLD found, then returns - * hostname.length(). - */ - public int getEffectiveTLDLength(final String hostname) { - final String normalizedHostname = normalizeHostname(hostname); - int lastDot = normalizedHostname.length(); - for (SubdomainNode node = this.root; lastDot > 0;) { - int nextDotLoc = normalizedHostname.lastIndexOf('.', lastDot - 1); - node = findNode(node, - normalizedHostname.substring(nextDotLoc + 1, lastDot), false); - if (node == null) { - break; - } - lastDot = nextDotLoc; - if (node.isException()) { - // Exception rules use one fewer levels than were matched. - break; - } - if (node.isStopOK()) { - break; - } - } - return lastDot; - } + private void dump(final Map node, final int offset) { + if(node == null || node.isEmpty()) { + return; + } + for(Map.Entry e : node.entrySet()) { + for(int i = 0; i < offset; i++) { + System.out.print(" "); + } + System.out.println(e.getKey() + ": " + e.getValue()); + dump(e.getValue().getChildren(), offset + 1); + } + } - /** - * Normalizes characters of hostname. ASCII names are - * lower-cased. TOOD: If names using other characters than ASCII - * need to be normalized with a IIDNService::Normalize, RFC 3454. - * @param hostname - * @return normalized hostname. - */ - private String normalizeHostname(final String hostname) { - boolean isLowercase = true; - boolean isAscii = true; - for (int i = 0; i < hostname.length(); i++) { - char c = hostname.charAt(i); - if (c >= 128) { - isAscii = false; - break; - } - if (!Character.isLowerCase(c)) { - isLowercase = false; - } - } - if (!isAscii) { - // TODO: If java 6, then there is java.net.IDN#toAscii(hostname). - throw new UnsupportedOperationException("No support yet for IDN: TODO"); - } - return isLowercase? hostname: hostname.toLowerCase(); - } + /** + * Immutable subdomain node. + */ + private static class SubdomainNode { - /** - * Load effective TLD file from the CLASSPATH. - * @throws IOException - * @throws UnsupportedEncodingException - */ - private void load() - throws UnsupportedEncodingException, IOException { - URL u = this.getClass().getResource("/" + DATA_FILENAME); - if (u == null) { - throw new FileNotFoundException(DATA_FILENAME + " not on CLASSPATH"); - } - BufferedReader br = - new BufferedReader(new InputStreamReader(u.openStream(), "UTF-8")); - for (String line = null; (line = br.readLine()) != null;) { - if (line.length() <= 0 || line.startsWith("//")) { - continue; - } - addEffectiveTLDEntry(this.root, line); - } - } - - /** - * Given a parent node and a candidate subdomain, searches the - * parent's children for a matching subdomain and returns a pointer to the - * matching node if one was found. If no exact match was found and - * create is true, creates a new child node for the given - * subdomain and returns that. - * If no exact match was found an create is false, looks for - * a wildcard node (*) instead. If no wildcard node is found either, - * returns null. - * @param node - * @param subdomain - * @param create Typically true when the subdomain tree is being - * built, and false when it is being searched to determine a hostname's - * effective TLD. - * @return - */ - private SubdomainNode findNode(final SubdomainNode node, - final String subdomain, final boolean create) { - boolean exception = subdomain != null && subdomain.startsWith(EXCEPTION); - String key = exception? subdomain.substring(1): subdomain; - SubdomainNode newNode = node.getChildren().get(key); - if (newNode != null) { - return newNode; - } - if (create) { - // Create it and add to parent. - SubdomainNode subNode = new SubdomainNode(exception, false); - node.getChildren().put(key, subNode); - return subNode; - } - return node.getChildren().get(WILDCARD); - } + final boolean exception; + boolean stopOK; + final Map children; - /** - * Adds the given domain name rule to the effective-TLD tree. - * @param m Map to add too. - * @param line Line that starts with a hostname. - */ - private void addEffectiveTLDEntry(SubdomainNode node, - final String line) { - String hostname = WHITESPACE.split(line, 2)[0]; - for (int dotLoc = hostname.length(); dotLoc >= 0;) { - int nextDocLoc = hostname.lastIndexOf(DOT, dotLoc - 1); - String subdomain = hostname.substring(nextDocLoc + 1, dotLoc); - dotLoc = nextDocLoc; - node = findNode(node, subdomain, true); - } - - // The last node in an entry is by definition a stop-OK node. - node.setStopOK(); - } - - private void dump() { - dump(this.root.getChildren(), 0); - } - - private void dump(final Map node, final int offset) { - if (node == null || node.size() == 0) { - return; - } - for (Map.Entry e: node.entrySet()) { - for (int i = 0; i < offset; i++) { - System.out.print(" "); - } - System.out.println(e.getKey() + ": " + e.getValue()); - dump(e.getValue().getChildren(), offset + 1); - } - } + /** + * Create node with no children. + * + * @param ex + * @param stop + */ + public SubdomainNode(final boolean ex, final boolean stop) { + this(ex, stop, new HashMap()); + } - /** - * Immmutable subdomain node. - */ - private static class SubdomainNode { - final boolean exception; - boolean stopOK; - final Map children; + public SubdomainNode(final boolean ex, final boolean stop, + final Map c) { + this.exception = ex; + this.children = c; + this.stopOK = stop; + } - /** - * Create node with no children. - * @param ex - * @param stop - */ - public SubdomainNode(final boolean ex, final boolean stop) { - this(ex, stop, new HashMap()); - } + public boolean isException() { + return this.exception; + } - public SubdomainNode(final boolean ex, final boolean stop, - final Map c) { - this.exception = ex; - this.children = c; - this.stopOK = stop; - } + public boolean isStopOK() { + return this.stopOK; + } - public boolean isException() { - return this.exception; - } + public void setStopOK() { + this.stopOK = true; + } - public boolean isStopOK() { - return this.stopOK; - } - - public void setStopOK() { - this.stopOK = true; - } + public Map getChildren() { + return this.children; + } - public Map getChildren() { - return this.children; - } - - @Override - public String toString() { - return "exception: " + this.exception + ", stopOK: " + this.stopOK + - ", children: " + (this.children == null? 0: this.children.size()); - } - } + @Override + public String toString() { + return "exception: " + this.exception + ", stopOK: " + this.stopOK + + ", children: " + (this.children == null ? 0 : this.children.size()); + } + } } diff --git a/src/main/java/com/laytonsmith/PureUtilities/Quadruplet.java b/src/main/java/com/laytonsmith/PureUtilities/Quadruplet.java new file mode 100644 index 0000000000..fa81d60149 --- /dev/null +++ b/src/main/java/com/laytonsmith/PureUtilities/Quadruplet.java @@ -0,0 +1,94 @@ +package com.laytonsmith.PureUtilities; + +import java.util.Objects; + +/** + * Creates an object quadruplet. The hashcode and equals functions have been overridden to use the underlying object's hash + * code and equals combined. The underlying objects may be null. + * + * @param The first object's type. + * @param The second object's type. + * @param The third object's type. + * @param The fourth object's type. + */ +public class Quadruplet { + + private final A fst; + private final B snd; + private final C trd; + private final D frth; + + /** + * Creates a new Quadruplet with the specified values. + * + * @param a + * @param b + * @param c + * @param d + */ + public Quadruplet(A a, B b, C c, D d) { + fst = a; + snd = b; + trd = c; + frth = d; + } + + @Override + public String toString() { + return "<" + + Objects.toString(fst) + ", " + + Objects.toString(snd) + ", " + + Objects.toString(trd) + ", " + + Objects.toString(frth) + ">"; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 47 * hash + Objects.hashCode(this.fst); + hash = 47 * hash + Objects.hashCode(this.snd); + hash = 47 * hash + Objects.hashCode(this.trd); + hash = 47 * hash + Objects.hashCode(this.frth); + return hash; + } + + @Override + public boolean equals(Object obj) { + if(obj == null) { + return false; + } + if(getClass() != obj.getClass()) { + return false; + } + final Quadruplet other = (Quadruplet) obj; + if(!Objects.equals(this.fst, other.fst)) { + return false; + } + if(!Objects.equals(this.snd, other.snd)) { + return false; + } + if(!Objects.equals(this.trd, other.trd)) { + return false; + } + if(!Objects.equals(this.frth, other.frth)) { + return false; + } + return true; + } + + public A getFirst() { + return fst; + } + + public B getSecond() { + return snd; + } + + public C getThird() { + return trd; + } + + public D getFourth() { + return frth; + } +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/RollingAverage.java b/src/main/java/com/laytonsmith/PureUtilities/RollingAverage.java index 7cdf43aca5..9d227989e1 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/RollingAverage.java +++ b/src/main/java/com/laytonsmith/PureUtilities/RollingAverage.java @@ -3,61 +3,63 @@ import java.util.Arrays; /** - * A RollingAverage class allows you to keep track of X number of past results, and - * keep a rolling average across them. If at any point a number is not set, it is not - * used in the average, so initial results may be statistically skewed in favor of the - * earlier results. - * + * A RollingAverage class allows you to keep track of X number of past results, and keep a rolling average across them. + * If at any point a number is not set, it is not used in the average, so initial results may be statistically skewed in + * favor of the earlier results. + * */ public class RollingAverage { - private Double[] data; + + private final Double[] data; private int insertionIndex; - private int dataSize; - private double initialValue; - + private final int dataSize; + private final double initialValue; + /** * Creates a new rolling average object. + * * @param datasetSize The size of the queue - * @param initialValue The initial value to add to the - * dataset. If average() is called before addData, this will + * @param initialValue The initial value to add to the dataset. If average() is called before addData, this will * allow an actual number to be returned. */ - public RollingAverage(int datasetSize, double initialValue){ + public RollingAverage(int datasetSize, double initialValue) { data = new Double[datasetSize]; Arrays.fill(data, null); dataSize = datasetSize; insertionIndex = 0; this.initialValue = initialValue; } - + /** * Adds a new data point - * @param d + * + * @param d */ - public void addData(double d){ + public void addData(double d) { data[insertionIndex] = d; insertionIndex++; - if(insertionIndex == dataSize){ + if(insertionIndex == dataSize) { insertionIndex = 0; //rollover } } - + /** * Returns the rolling average. - * @return + * + * @return */ - public double getAverage(){ + public double getAverage() { double sum = 0; int count = 0; boolean hasValue = false; - for(Double d : data){ - if(d != null){ + for(Double d : data) { + if(d != null) { hasValue = true; sum += d; count++; } } - if(!hasValue){ + if(!hasValue) { return initialValue; } return sum / count; diff --git a/src/main/java/com/laytonsmith/PureUtilities/RunnableQueue.java b/src/main/java/com/laytonsmith/PureUtilities/RunnableQueue.java index d7cb42358e..f6784b4ccd 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/RunnableQueue.java +++ b/src/main/java/com/laytonsmith/PureUtilities/RunnableQueue.java @@ -1,6 +1,6 @@ - package com.laytonsmith.PureUtilities; +import com.laytonsmith.PureUtilities.Common.StreamUtils; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -9,19 +9,20 @@ /** * Creates a new queue - * + * */ public class RunnableQueue { + private ExecutorService service; private static int threadCount = 0; private ThreadFactory threadFactory; - - public RunnableQueue(String threadPrefix){ + + public RunnableQueue(String threadPrefix) { this(threadPrefix, null); } - - public RunnableQueue(final String threadPrefix, final Thread.UncaughtExceptionHandler exceptionHandler){ - if(threadPrefix == null){ + + public RunnableQueue(final String threadPrefix, final Thread.UncaughtExceptionHandler exceptionHandler) { + if(threadPrefix == null) { throw new NullPointerException(); } threadFactory = new ThreadFactory() { @@ -30,36 +31,36 @@ public RunnableQueue(final String threadPrefix, final Thread.UncaughtExceptionHa public Thread newThread(Runnable r) { Thread t = new Thread(r, threadPrefix + "-" + (++threadCount)); t.setDaemon(false); - if(exceptionHandler != null){ + if(exceptionHandler != null) { t.setUncaughtExceptionHandler(exceptionHandler); } else { t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { - System.err.println("The thread " + t.getName() + " threw an exception, and it was not handled."); - e.printStackTrace(System.err); + StreamUtils.GetSystemErr().println("The thread " + t.getName() + " threw an exception, and it was not handled."); + e.printStackTrace(StreamUtils.GetSystemErr()); } }); - } + } return t; } }; } - - private void activate(){ - if(service == null){ + + private void activate() { + if(service == null) { service = Executors.newSingleThreadExecutor(threadFactory); } } - + /** - * Schedules a runnable to run whenever the queue pump can get to it. Returns - * immediately. - * @param r + * Schedules a runnable to run whenever the queue pump can get to it. Returns immediately. + * + * @param r */ - public void invokeLater(final DaemonManager dm, final Runnable r){ - if(dm != null){ + public void invokeLater(final DaemonManager dm, final Runnable r) { + if(dm != null) { dm.activateThread(null); } activate(); @@ -67,18 +68,18 @@ public void invokeLater(final DaemonManager dm, final Runnable r){ @Override public void run() { - try{ + try { r.run(); } finally { - if(dm != null){ + if(dm != null) { dm.deactivateThread(null); } } } }); } - - public T invokeAndWait(Callable callable) throws InterruptedException, ExecutionException{ + + public T invokeAndWait(Callable callable) throws InterruptedException, ExecutionException { activate(); return service.submit(callable).get(); } diff --git a/src/main/java/com/laytonsmith/PureUtilities/SAXDocument.java b/src/main/java/com/laytonsmith/PureUtilities/SAXDocument.java index 2b5ceb8155..4bf5e3c474 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/SAXDocument.java +++ b/src/main/java/com/laytonsmith/PureUtilities/SAXDocument.java @@ -1,4 +1,3 @@ - package com.laytonsmith.PureUtilities; import java.io.ByteArrayInputStream; @@ -12,6 +11,7 @@ import java.util.Map; import java.util.Stack; import java.util.concurrent.atomic.AtomicInteger; +import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.Attributes; @@ -19,78 +19,79 @@ import org.xml.sax.helpers.DefaultHandler; /** - * Works similarly to {@link XMLDocument}, however, this uses a SAX based - * parser, which is useful in cases where you are reading (not writing) data - * from a large XML document, or streaming document. It uses XPath expressions - * to identify segments of a document that are of interest, and triggers once the - * element is fully read in. This should be mostly possible to replace SAX parsers - * in all cases. + * Works similarly to {@link XMLDocument}, however, this uses a SAX based parser, which is useful in cases where you are + * reading (not writing) data from a large XML document, or streaming document. It uses XPath expressions to identify + * segments of a document that are of interest, and triggers once the element is fully read in. This should be mostly + * possible to replace SAX parsers in all cases. */ public class SAXDocument { - + private final InputStream stream; - private final Map> callbacks = new HashMap>(); - + private final Map> callbacks = new HashMap<>(); + /** * Creates a new SAXDocument, based on the input stream provided. - * @param stream + * + * @param stream */ - public SAXDocument(InputStream stream){ + public SAXDocument(InputStream stream) { this.stream = stream; } - + /** - * Creates a new SAXDocument. If you already have the document in memory - * though, you may consider using {@link XMLDocument} instead, since you've - * obviously already got the whole document in memory. + * Creates a new SAXDocument. If you already have the document in memory though, you may consider using + * {@link XMLDocument} instead, since you've obviously already got the whole document in memory. + * * @param document The XML document in a string * @param encoding The encoding of the stream. If null, UTF-8 is used. + * @throws java.io.UnsupportedEncodingException */ - public SAXDocument(String document, String encoding) throws UnsupportedEncodingException{ - this(new ByteArrayInputStream(document.getBytes(encoding==null?"UTF-8":encoding))); + public SAXDocument(String document, String encoding) throws UnsupportedEncodingException { + this(new ByteArrayInputStream(document.getBytes(encoding == null ? "UTF-8" : encoding))); } - + /** - * Parses the XML document. As elements are loaded, if they match, they are - * sent to the listeners that match the element. + * Parses the XML document. As elements are loaded, if they match, they are sent to the listeners that match the + * element. + * * @throws SAXException - * @throws IOException + * @throws IOException */ - public void parse() throws SAXException, IOException{ + public void parse() throws SAXException, IOException { SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser saxParser; try { saxParser = factory.newSAXParser(); - } catch (Exception ex) { + } catch (ParserConfigurationException | SAXException ex) { throw new RuntimeException(ex); } - saxParser.parse(stream, new DefaultHandler(){ - Stack nodeNames = new Stack(); - Stack> nodeCount = new Stack>(); + saxParser.parse(stream, new DefaultHandler() { + Stack nodeNames = new Stack<>(); + Stack> nodeCount = new Stack<>(); String lastElement = ""; - Map contents = new HashMap(); - Stack attributeStack = new Stack(); + Map contents = new HashMap<>(); + Stack attributeStack = new Stack<>(); @Override public void startDocument() throws SAXException { - nodeCount.push(new HashMap()); + nodeCount.push(new HashMap<>()); } - + @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { nodeNames.push(qName); Map c = nodeCount.peek(); - if(!c.containsKey(qName)){ + if(!c.containsKey(qName)) { c.put(qName, new AtomicInteger(1)); } else { c.get(qName).incrementAndGet(); } - Map counts = new HashMap(); + Map counts = new HashMap<>(); nodeCount.push(counts); - if(!contents.isEmpty()){ + if(!contents.isEmpty()) { StringBuilder b = new StringBuilder(); b.append("<").append(qName).append(""); - for(int i = 0; i < attributes.getLength(); i++){ + for(int i = 0; i < attributes.getLength(); i++) { b.append(" ").append(attributes.getQName(i)) .append("=\"").append(attributes.getValue(i).replace("\"", """)).append("\""); } @@ -98,7 +99,7 @@ public void startElement(String uri, String localName, String qName, Attributes appendAll(b.toString()); } String path = pathFromMarkers(nodeNames, nodeCount); - if(hasListener(path)){ + if(hasListener(path)) { contents.put(getListenerPath(path), new StringBuilder()); attributeStack.push(attributes); } @@ -106,7 +107,7 @@ public void startElement(String uri, String localName, String qName, Attributes @Override public void characters(char[] ch, int start, int length) throws SAXException { - if(!contents.isEmpty()){ + if(!contents.isEmpty()) { String s = fromChars(ch, start, length); appendAll(s); } @@ -115,38 +116,38 @@ public void characters(char[] ch, int start, int length) throws SAXException { @Override public void endElement(String uri, String localName, String qName) throws SAXException { String path = pathFromMarkers(nodeNames, nodeCount); - if(hasListener(path)){ + if(hasListener(path)) { String key = getListenerPath(path); StringBuilder b = contents.remove(key); Attributes attr = attributeStack.pop(); - Map attributes = new LinkedHashMap(); - for(int i = 0; i < attr.getLength(); i++){ + Map attributes = new LinkedHashMap<>(); + for(int i = 0; i < attr.getLength(); i++) { attributes.put(attr.getQName(i), attr.getValue(i)); } notifyListeners(path, qName, attributes, b.toString()); } - if(!contents.isEmpty()){ + if(!contents.isEmpty()) { appendAll(""); } nodeNames.pop(); nodeCount.pop(); } - - private void appendAll(String s){ - for(StringBuilder b : contents.values()){ + + private void appendAll(String s) { + for(StringBuilder b : contents.values()) { b.append(s); } } - + }); } - - private String pathFromMarkers(Stack elementStack, Stack> nodeCounts){ - List elementList = new ArrayList(elementStack); - List> nodeList = new ArrayList>(nodeCounts); + + private String pathFromMarkers(Stack elementStack, Stack> nodeCounts) { + List elementList = new ArrayList<>(elementStack); + List> nodeList = new ArrayList<>(nodeCounts); StringBuilder b = new StringBuilder(); - for(int i = 0; i < elementList.size(); i++){ + for(int i = 0; i < elementList.size(); i++) { b.append("/") .append(elementList.get(i)) .append("[") @@ -155,86 +156,90 @@ private String pathFromMarkers(Stack elementStack, Stack attr, String contents){ - for(String key : callbacks.keySet()){ - if(xpath.matches(key)){ - for(ElementCallback c : callbacks.get(key)){ + + private void notifyListeners(String xpath, String tag, Map attr, String contents) { + for(String key : callbacks.keySet()) { + if(xpath.matches(key)) { + for(ElementCallback c : callbacks.get(key)) { c.handleElement(xpath, tag, attr, contents); } } } } - - private boolean hasListener(String xpath){ - for(String key : callbacks.keySet()){ - if(xpath.matches(key)){ + + private boolean hasListener(String xpath) { + for(String key : callbacks.keySet()) { + if(xpath.matches(key)) { return true; } } return false; } - - private String getListenerPath(String xpath){ - for(String key : callbacks.keySet()){ - if(xpath.matches(key)){ + + private String getListenerPath(String xpath) { + for(String key : callbacks.keySet()) { + if(xpath.matches(key)) { return key; } } return null; } - + /** - * Adds a new listener. When you call parse(), any elements that match - * these listeners are triggered. Any number of listeners can be added per - * xpath. - * @param xpath The XPath to match against. This can only be a simple XPath, - * and cannot include functions or attributes, for instance /path/to/node. The exception is that - * wildcard matching is allowed for element indexes, (which is implied for elements with - * no index ascribed). For instance: /path/to[*]/node would match all "to" sub elements within - * "path", however /path/to[1]/node would only match the first one. Leaving off [*] is acceptable, + * Adds a new listener. When you call parse(), any elements that match these listeners are triggered. Any number of + * listeners can be added per xpath. + * + * @param xpath The XPath to match against. This can only be a simple XPath, and cannot include functions or + * attributes, for instance /path/to/node. The exception is that wildcard matching is allowed for element indexes, + * (which is implied for elements with no index ascribed). For instance: /path/to[*]/node would match all "to" sub + * elements within "path", however /path/to[1]/node would only match the first one. Leaving off [*] is acceptable, * as this is implied if there is no index given. * @param callback The callback to run when an element is matched */ - public void addListener(String xpath, ElementCallback callback){ - if(xpath.contains(" ")){ + public void addListener(String xpath, ElementCallback callback) { + if(xpath.contains(" ")) { throw new IllegalArgumentException("The xpath may not contain spaces"); } - if(xpath.startsWith("//") || !xpath.startsWith("/")){ + if(xpath.startsWith("//") || !xpath.startsWith("/")) { throw new IllegalArgumentException("The xpath must be absolute, meaning it must start with exactly 1 forward slash"); } //Standardize our xpath - String [] parts = xpath.substring(1).split("/"); + xpath = standardizeXpath(xpath); + if(!callbacks.containsKey(xpath)) { + callbacks.put(xpath, new ArrayList<>()); + } + callbacks.get(xpath).add(callback); + } + + private String standardizeXpath(String xpath) { + String[] parts = xpath.substring(1).split("/"); StringBuilder b = new StringBuilder(); - for(int i = 0; i < parts.length; i++){ - String part = parts[i]; - if(!part.matches(".*\\[(?:\\d+|\\*)\\]$")){ + for(String part : parts) { + if(!part.matches(".*\\[(?:\\d+|\\*)\\]$")) { //Add the identifier. Implied * part += "[*]"; } b.append("/").append(part); } xpath = "^" + b.toString().replace("[*]", "[\\d+]").replace("[", "\\[").replace("]", "\\]") + "$"; - if(!callbacks.containsKey(xpath)){ - callbacks.put(xpath, new ArrayList()); - } - callbacks.get(xpath).add(callback); + return xpath; } - public static interface ElementCallback { + /** * Called when a matched element is fully read in. - * @param path The XPath of this element. This will be a canonical reference to the element, - * so will not necessarily match the XPath you passed in to tag this element with. + * + * @param xpath The XPath of this element. This will be a canonical reference to the element, so will not + * necessarily match the XPath you passed in to tag this element with. * @param tag The element name * @param attr The attributes on the element * @param contents The contents of the element diff --git a/src/main/java/com/laytonsmith/PureUtilities/SSHWrapper.java b/src/main/java/com/laytonsmith/PureUtilities/SSHWrapper.java index a011371967..a8b8d28177 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/SSHWrapper.java +++ b/src/main/java/com/laytonsmith/PureUtilities/SSHWrapper.java @@ -2,54 +2,90 @@ import com.jcraft.jsch.Channel; import com.jcraft.jsch.ChannelExec; +import com.jcraft.jsch.ChannelSftp; import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Session; +import com.jcraft.jsch.SftpException; import com.jcraft.jsch.UserInfo; import com.laytonsmith.PureUtilities.Common.StreamUtils; +import com.laytonsmith.PureUtilities.Common.StringUtils; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * This class wraps the JSch library, to make atomic operations easier to do. * - * + * */ -public class SSHWrapper { +public final class SSHWrapper { private SSHWrapper() { } + private static final Map SESSION_LIST = new HashMap<>(); + + /** + * Sessions are cached, and should be closed after use. + */ + public static void closeSessions() { + for(Session s : SESSION_LIST.values()) { + s.disconnect(); + } + SESSION_LIST.clear(); + } + + /** + * Copies a file from/to a remote host, via ssh. Currently, both paths being remote is not supported. A path can + * look like the following: user@remote[:port[:password]]:path/to/remote/file If the password is not specified, then + * public key authentication will be assumed. The port must be specified if the password is specified, but setting + * it to 0 will use the default (22), allowing it to be bypassed. + * + * @param from + * @param to + * @return false, if the file is being pushed to the remote, yet it was already the same, thus no changes were made, + * true otherwise + */ + public static boolean SCP(String from, String to) throws IOException { + return SCP(from, to, null); + } + /** - * Copies a file from/to a remote host, via ssh. Currently, both paths - * being remote is not supported. A path can look like the following: - * user@remote[:port[:password]]:path/to/remote/file If the password is - * not specified, then public key authentication will be assumed. The - * port must be specified if the password is specified, but setting it - * to 0 will use the default (22), allowing it to be bypassed. + * Copies a file from/to a remote host, via ssh. Currently, both paths being remote is not supported. A path can + * look like the following: user@remote[:port[:password]]:path/to/remote/file If the password is not specified, then + * public key authentication will be assumed. The port must be specified if the password is specified, but setting + * it to 0 will use the default (22), allowing it to be bypassed. + * * @param from * @param to + * @param privateKeyLocation If the private key is not in the default location, it can be provided here + * @return false, if the file is being pushed to the remote, yet it was already the same, thus no changes were made, + * true otherwise */ - public static void SCP(String from, String to) throws IOException { - if ((from.contains("@") && to.contains("@")) || (!from.contains("@") && !to.contains("@"))) { + public static boolean SCP(String from, String to, String privateKeyLocation) throws IOException { + if((from.contains("@") && to.contains("@")) || (!from.contains("@") && !to.contains("@"))) { throw new IOException("Paths cannot be both remote, or both local."); } //Now that we've handled the case where both paths are remote, we //can determine which one is the remote path, and proceed from there. String remote = to; - if (from.contains("@")) { + if(from.contains("@")) { remote = from; } //Now, parse the remote connection for information Matcher m = Pattern.compile("(.+?)@(.+?)(?:\\:(.+?)(?:\\:(.+?))?)?\\:(.+)").matcher(remote); String syntaxErrorMsg = "Remote host connection must match the following syntax: user@host[:port[:password]]:path/to/file"; - if (m.find()) { + if(m.find()) { String user = m.group(1); String host = m.group(2); String sport = m.group(3); @@ -58,91 +94,105 @@ public static void SCP(String from, String to) throws IOException { String file = m.group(5); try { - if (sport != null) { + if(sport != null) { port = Integer.parseInt(sport); } - if (port == 0) { + if(port == 0) { port = 22; } } catch (NumberFormatException e) { //They may have been trying this: //user@host:password:/file/path //If that's the case, password will - //be null, so let's give them a better error message. - if (password == null) { + //be null, so let's give them a better error message. + if(password == null) { throw new IOException(syntaxErrorMsg + " (It appears as though you may have been trying a password" - + " in place of the port. You may specify the port to be 0 if you want it to use the default," - + " to bypass the port parameter.)"); + + " in place of the port. You may specify the port to be 0 if you want it to use the default," + + " to bypass the port parameter.)"); } } - if (port < 1 || port > 65535) { + if(port < 1 || port > 65535) { throw new IOException("Port numbers must be between 1 and 65535"); } try { JSch jsch = new JSch(); Session sshSession = null; - File known_hosts = new File(System.getProperty("user.home") + "/.ssh/known_hosts"); - if (!known_hosts.exists()) { - throw new IOException("No known hosts file exists at " + known_hosts.getAbsolutePath()); + File knownHosts = new File(System.getProperty("user.home") + "/.ssh/known_hosts"); + if(!knownHosts.exists()) { + if(password == null) { + throw new IOException("No known hosts file exists at " + knownHosts.getAbsolutePath() + ", and no password was provided"); + } + } else { + jsch.setKnownHosts(knownHosts.getAbsolutePath()); } - jsch.setKnownHosts(known_hosts.getAbsolutePath()); - if (password == null) { - //We need to try public key authentication - File privKey = new File(System.getProperty("user.home") + "/.ssh/id_rsa"); - if (privKey.exists()) { + if(password == null) { + //We need to try public key authentication + String idRsa = System.getProperty("user.home") + "/.ssh/id_rsa"; + if(privateKeyLocation != null) { + idRsa = privateKeyLocation; + } + File privKey = new File(idRsa); + if(privKey.exists()) { jsch.addIdentity(privKey.getAbsolutePath()); } else { throw new IOException("No password provided, and no private key exists at " + privKey.getAbsolutePath()); } } - sshSession = jsch.getSession(user, host, port); - sshSession.setUserInfo(new UserInfo() { - @Override - public String getPassphrase() { - //This may need to be made more granular later - return password; - } - - @Override - public String getPassword() { - return password; - } - - @Override - public boolean promptPassword(String message) { - return true; - } - - @Override - public boolean promptPassphrase(String message) { - return true; - } - - @Override - public boolean promptYesNo(String message) { - System.out.println(message + " (Automatically responding with 'Yes')"); - return true; - } - - @Override - public void showMessage(String message) { - System.out.println(message); - } - }); - //10 second timeout - sshSession.connect(10 * 1000); + if(!SESSION_LIST.containsKey(user + host + port)) { + sshSession = jsch.getSession(user, host, port); + sshSession.setUserInfo(new UserInfo() { + @Override + public String getPassphrase() { + //This may need to be made more granular later + return password; + } + + @Override + public String getPassword() { + return password; + } + + @Override + public boolean promptPassword(String message) { + return true; + } + + @Override + public boolean promptPassphrase(String message) { + return true; + } + + @Override + public boolean promptYesNo(String message) { + StreamUtils.GetSystemOut().println(message + " (Automatically responding with 'Yes')"); + return true; + } + + @Override + public void showMessage(String message) { + StreamUtils.GetSystemOut().println(message); + } + }); + //15 second timeout + sshSession.connect(10 * 1500); + SESSION_LIST.put(user + host + port, sshSession); + } else { + sshSession = SESSION_LIST.get(user + host + port); + } // http://www.jcraft.com/jsch/examples/ - if (from.contains("@")) { + if(from.contains("@")) { //We are pulling a remote file here, so we need to use SCPFrom File localFile = new File(to); SCPFrom(file, localFile, sshSession); } else { //We are pushing a local file to a remote, so we need to use SCPTo File localFile = new File(from); - SCPTo(localFile, file, sshSession); + return SCPTo(localFile, file, sshSession); } - sshSession.disconnect(); - } catch (JSchException ex) { + + return true; + + } catch (JSchException | SftpException ex) { throw new IOException(ex); } } else { @@ -150,65 +200,131 @@ public void showMessage(String message) { } } - private static void SCPTo(File lfile, String rfile, Session session) throws JSchException, IOException { - boolean ptimestamp = true; - - // exec 'scp -t rfile' remotely - String command = "scp " + (ptimestamp ? "-p" : "") + " -t " + rfile; - Channel channel = session.openChannel("exec"); - ((ChannelExec) channel).setCommand(command); - - // get I/O streams for remote scp - OutputStream out = channel.getOutputStream(); - InputStream in = channel.getInputStream(); - - channel.connect(); - - checkAck(in); + /** + * + * @param lfile + * @param rfile + * @param session + * @return true, if the file was uploaded, false if the file is the same on the remote, thus the file is not + * uploaded + * @throws JSchException + * @throws IOException + * @throws SftpException + */ + private static boolean SCPTo(File lfile, String rfile, Session session) throws JSchException, IOException, SftpException { + ChannelSftp channel = null; + try { + channel = (ChannelSftp) session.openChannel("sftp"); - if (ptimestamp) { - command = "T " + (lfile.lastModified() / 1000) + " 0"; - // The access time should be sent here, - // but it is not accessible with JavaAPI ;-< - command += (" " + (lfile.lastModified() / 1000) + " 0\n"); - out.write(command.getBytes()); - out.flush(); - checkAck(in); + channel.connect(); + String[] folders = rfile.split("/"); + File frfile = new File(rfile); + try { + // Try to cd to the parent folder + channel.cd(frfile.getParent()); + } catch (SftpException ex) { + // But if that doesn't work, we need to create one or more of the folders, so we start at the beginning + channel.cd("/"); + for(int i = 0; i < folders.length - 1; i++) { + String folder = folders[i]; + if(folder.length() > 0) { + try { + channel.cd(folder); + } catch (SftpException e) { + channel.mkdir(folder); + channel.cd(folder); + } + } + } + } + String remote = getRemoteMD5(rfile, session); + String local = getLocalMD5(lfile); + if(!remote.equals(local)) { + // only upload if it's different + channel.put(new FileInputStream(lfile), frfile.getName()); + return true; + } + return false; + } finally { + if(channel != null) { + channel.exit(); + channel.disconnect(); + } } + } - // send "C0644 filesize filename", where filename should not include '/' - long filesize = lfile.length(); - command = "C0644 " + filesize + " "; - if (lfile.getPath().lastIndexOf('/') > 0) { - command += lfile.getPath().substring(lfile.getPath().lastIndexOf('/') + 1); - } else { - command += lfile; + /** + * Returns the md5 sum of a local file + * + * @param localFile + * @return + * @throws IOException + */ + public static String getLocalMD5(File localFile) throws IOException { + try { + byte[] f = StreamUtils.GetBytes(new FileInputStream(localFile)); + MessageDigest digest = java.security.MessageDigest.getInstance("MD5"); + digest.update(f); + String hash = StringUtils.toHex(digest.digest()).toLowerCase(); + return hash; + } catch (NoSuchAlgorithmException ex) { + throw new RuntimeException(ex); } - command += "\n"; - out.write(command.getBytes()); - out.flush(); - checkAck(in); + } - // send a content of lfile - FileInputStream fis = new FileInputStream(lfile); - byte[] buf = new byte[1024]; - while (true) { - int len = fis.read(buf, 0, buf.length); - if (len <= 0) { - break; + private static String getRemoteMD5(String remoteFile, Session session) throws JSchException, IOException { + ChannelExec channel = null; + final StringBuilder sb = new StringBuilder(); + try { + channel = (ChannelExec) session.openChannel("exec"); + channel.setCommand("openssl md5 " + remoteFile); + channel.setInputStream(null); + channel.setOutputStream(null); + channel.setErrStream(System.err); + + InputStream in = channel.getInputStream(); + channel.connect(); + + byte[] tmp = new byte[1024]; + while(true) { + while(in.available() > 0) { + int i = in.read(tmp, 0, 1024); + if(i < 0) { + break; + } + sb.append(new String(tmp, 0, i)); + } + if(channel.isClosed()) { + if(in.available() > 0) { + continue; + } + if(channel.getExitStatus() != 0) { + // Something went wrong, fail the comparison + return "invalidMD5sum"; + } + break; + } + try { + Thread.sleep(1); + } catch (Exception ee) { + } + } + } finally { + if(channel != null) { + channel.disconnect(); } - out.write(buf, 0, len); //out.flush(); } - fis.close(); - fis = null; - // send '\0' - buf[0] = 0; - out.write(buf, 0, 1); - out.flush(); - checkAck(in); - out.close(); - - channel.disconnect(); + if("".equals(sb.toString())) { + // Something went wrong, and so we're going to be forced to re-upload anyways. Using a nonsense value + // ensures that the comparison will fail. This can happen if openssl is not installed on the remote, + // or if the file simply doesn't exist. + return "invalidMD5sum"; + } + String opensslReturn = sb.toString(); + // This will be something like MD5(/path/to/file)= 798ffb41da648e405ca160fb547e3a09 + // and we just need 798ffb41da648e405ca160fb547e3a09 + opensslReturn = opensslReturn.replaceAll("MD5\\(.*\\)= ", ""); + return opensslReturn.replaceAll("\n|\r", ""); } private static void SCPFrom(String remote, File local, Session session) throws IOException, JSchException { @@ -230,9 +346,9 @@ private static void SCPFrom(String remote, File local, Session session) throws I out.write(buf, 0, 1); out.flush(); - while (true) { + while(true) { int c = checkAckFrom(in); - if (c != 'C') { + if(c != 'C') { break; } @@ -240,53 +356,52 @@ private static void SCPFrom(String remote, File local, Session session) throws I in.read(buf, 0, 5); long filesize = 0L; - while (true) { - if (in.read(buf, 0, 1) < 0) { + while(true) { + if(in.read(buf, 0, 1) < 0) { // error break; } - if (buf[0] == ' ') { + if(buf[0] == ' ') { break; } filesize = filesize * 10L + (long) (buf[0] - '0'); } String file = null; - for (int i = 0;; i++) { + for(int i = 0;; i++) { in.read(buf, i, 1); - if (buf[i] == (byte) 0x0a) { + if(buf[i] == (byte) 0x0a) { file = new String(buf, 0, i); break; } } - // send '\0' buf[0] = 0; out.write(buf, 0, 1); out.flush(); String prefix = null; - if (local.isDirectory()) { + if(local.isDirectory()) { prefix = local.getPath() + File.separator; } // read a content of lfile FileOutputStream fos = new FileOutputStream(prefix == null ? local.getPath() : prefix + file); int foo; - while (true) { - if (buf.length < filesize) { + while(true) { + if(buf.length < filesize) { foo = buf.length; } else { foo = (int) filesize; } foo = in.read(buf, 0, foo); - if (foo < 0) { + if(foo < 0) { // error break; } fos.write(buf, 0, foo); filesize -= foo; - if (filesize == 0L) { + if(filesize == 0L) { break; } } @@ -311,17 +426,17 @@ private static void checkAck(InputStream in) throws IOException { // 2 for fatal error, // -1 - if (b == 1 || b == 2) { - StringBuffer sb = new StringBuffer(); + if(b == 1 || b == 2) { + StringBuilder sb = new StringBuilder(); int c; do { c = in.read(); sb.append((char) c); - } while (c != '\n'); - if (b == 1) { // error + } while(c != '\n'); + if(b == 1) { // error throw new IOException(sb.toString()); } - if (b == 2) { // fatal error + if(b == 2) { // fatal error throw new IOException(sb.toString()); } } @@ -333,24 +448,24 @@ static int checkAckFrom(InputStream in) throws IOException { // 1 for error, // 2 for fatal error, // -1 - if (b == 0) { + if(b == 0) { return b; } - if (b == -1) { + if(b == -1) { return b; } - if (b == 1 || b == 2) { - StringBuffer sb = new StringBuffer(); + if(b == 1 || b == 2) { + StringBuilder sb = new StringBuilder(); int c; do { c = in.read(); sb.append((char) c); - } while (c != '\n'); - if (b == 1) { // error + } while(c != '\n'); + if(b == 1) { // error throw new IOException(sb.toString()); } - if (b == 2) { // fatal error + if(b == 2) { // fatal error throw new IOException(sb.toString()); } } @@ -358,24 +473,46 @@ static int checkAckFrom(InputStream in) throws IOException { } /** - * Given an input stream, writes it out to a remote file system. The - * path given (to) must be a remote path. + * Given an input stream, writes it out to a remote file system. The path given (to) must be a remote path. + * + * @param is + * @return true, if the file on the remote was changed, false, if it was already at this version, thus no changes + * were made + */ + public static boolean SCPWrite(InputStream is, String to) throws IOException { + return SCPWrite(is, to, null); + } + + /** + * Given an input stream, writes it out to a remote file system. The path given (to) must be a remote path. * * @param is + * @return true, if the file on the remote was changed, false, if it was already at this version, thus no changes + * were made */ - public static void SCPWrite(InputStream is, String to) throws IOException { + public static boolean SCPWrite(InputStream is, String to, String idRsa) throws IOException { File temp = File.createTempFile("methodscript-temp-file", ".tmp"); FileOutputStream fos = new FileOutputStream(temp); StreamUtils.Copy(is, fos); fos.close(); try { - SCP(temp.getAbsolutePath(), to); + return SCP(temp.getAbsolutePath(), to, idRsa); } finally { temp.delete(); temp.deleteOnExit(); } } + /** + * Writes some textual contents to a remote file. + * + * @param contents + * @param to + */ + public static void SCPWrite(String contents, String to) throws IOException { + SCPWrite(StreamUtils.GetInputStream(contents), to); + } + /** * Returns an InputStream to a file on a remote file system. * @@ -390,16 +527,6 @@ public static InputStream SCPRead(String from) throws IOException { return fis; } - /** - * Writes some textual contents to a remote file. - * - * @param contents - * @param to - */ - public static void SCPWrite(String contents, String to) throws IOException { - SCPWrite(StreamUtils.GetInputStream(contents), to); - } - public static String SCPReadString(String from) throws IOException { return StreamUtils.GetString(SCPRead(from)); } @@ -407,6 +534,6 @@ public static String SCPReadString(String from) throws IOException { // * Executes a command over ssh. // */ // public static void SSHExec(){ -// +// // } } diff --git a/src/main/java/com/laytonsmith/PureUtilities/SemVer2.java b/src/main/java/com/laytonsmith/PureUtilities/SemVer2.java new file mode 100644 index 0000000000..e4bcab33fd --- /dev/null +++ b/src/main/java/com/laytonsmith/PureUtilities/SemVer2.java @@ -0,0 +1,348 @@ +package com.laytonsmith.PureUtilities; + +import static com.laytonsmith.PureUtilities.SimpleVersion.checkGT; +import static com.laytonsmith.PureUtilities.SimpleVersion.checkLT; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * An implementation of the Version interface that conforms precisely to the + * Semantic Versioning 2.0.0 specification. While this is + * only a small part of what makes SemVer useful, properly representing the format is part of it. For more details, + * particularly about what should cause version changes, and how the version number should change over time, + * see the full spec. + */ +public class SemVer2 implements Version, Comparable { + private final int major; + private final int minor; + private final int patch; + private final String prerelease; + private final String buildMetaData; + + private static final Pattern PATTERN + = Pattern.compile("(0|[1-9]\\d*)" + + "\\.(0|[1-9]\\d*)" + + "\\.(0|[1-9]\\d*)" + + "(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)" + + "(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?" + + "(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"); + + /** + * Creates a new Semantic Versioning object from a string version number. The prerelease and buildMetaData + * is optional, but all other parameters are required. + * + * @param version The version, as a string + * @throws IllegalArgumentException If the input string is invalid. An invalid input string is one which + * does not conform to the Semantic Versioning 2.0.0 standard. + */ + public SemVer2(String version) throws IllegalArgumentException { + Matcher m = PATTERN.matcher(version); + if(m.find()) { + try { + major = Integer.parseInt(m.group(1)); + minor = Integer.parseInt(m.group(2)); + patch = Integer.parseInt(m.group(3)); + prerelease = m.group(4) == null ? "" : m.group(4); + buildMetaData = m.group(5) == null ? "" : m.group(5); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Version numbers must be integers", e); + } + } else { + throw new IllegalArgumentException("Invalid version string provided"); + } + } + + /** + * Creates a new version with programmatic parameters. + * + * @param major MAJOR version when you make incompatible API changes + * @param minor MINOR version when you add functionality in a backwards compatible manner + * @param patch PATCH version when you make backwards compatible bug fixes + * @param prerelease Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-]. + * Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes. Pre-release versions have + * a lower precedence than the associated normal version. A pre-release version indicates that the version is + * unstable and might not satisfy the intended compatibility requirements as denoted by its associated normal + * version. + */ + public SemVer2(int major, int minor, int patch, String prerelease) { + this(major, minor, patch, prerelease, ""); + } + + /** + * Creates a new version with programmatic parameters. + * + * @param major MAJOR version when you make incompatible API changes + * @param minor MINOR version when you add functionality in a backwards compatible manner + * @param patch PATCH version when you make backwards compatible bug fixes + * @param prerelease Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-]. + * Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes. Pre-release versions have + * a lower precedence than the associated normal version. A pre-release version indicates that the version is + * unstable and might not satisfy the intended compatibility requirements as denoted by its associated normal + * version. + * @param buildMetaData Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers + * MUST NOT be empty. Build metadata MUST be ignored when determining version precedence. Thus two versions that + * differ only in the build metadata, have the same precedence. + */ + public SemVer2(int major, int minor, int patch, String prerelease, String buildMetaData) { + this.major = major; + this.minor = minor; + this.patch = patch; + this.prerelease = prerelease; + this.buildMetaData = buildMetaData; + } + + /** + * Creates a new version with programmatic parameters, and an empty prerelease and buildMetaData values. + * + * @param major MAJOR version when you make incompatible API changes + * @param minor MINOR version when you add functionality in a backwards compatible manner + * @param patch PATCH version when you make backwards compatible bug fixes + */ + public SemVer2(int major, int minor, int patch) { + this(major, minor, patch, "", ""); + } + + /** + * Returns the major version. + * + * @return + */ + @Override + public int getMajor() { + return major; + } + + /** + * Returns the minor version. + * + * @return + */ + @Override + public int getMinor() { + return minor; + } + + /** + * Returns the patch version. This is merely implemented to conform to the {@link Version} interface, prefer + * {@link #getPatch()}. + * + * @return + */ + @Override + public int getSupplemental() { + return patch; + } + + /** + * Returns the patch version. + * @return + */ + public int getPatch() { + return patch; + } + + /** + * Returns the prerelease label in this version. + * + * @return + */ + public String getPrerelease() { + return prerelease; + } + + /** + * Returns the build meta data label in this version. + * @return + */ + public String getBuildMetaData() { + return buildMetaData; + } + + @Override + public String toString() { + String s = major + "." + minor + "." + patch; + if(!"".equals(prerelease)) { + s += "-" + prerelease; + } + if(!"".equals(buildMetaData)) { + s += "+" + buildMetaData; + } + return s; + } + + private int compareIdentifiers(String a, String b) { + boolean anum = false; + try { + Long.parseLong(a); + anum = true; + } catch (NumberFormatException e) { + // + } + boolean bnum = false; + try { + Long.parseLong(b); + bnum = true; + } catch (NumberFormatException e) { + // + } + + Integer r = a.equals(b) ? 0 + : (anum && !bnum) ? -1 + : (bnum && !anum) ? 1 + : Integer.MAX_VALUE; + if(r != Integer.MAX_VALUE) { + return r; + } + // Need to do a comparison, but the comparison varies if this is a string or not. + if(anum && bnum) { + return Long.valueOf(a).compareTo(Long.valueOf(b)); + } else { + return a.compareTo(b); + } + } + + private int comparePre(SemVer2 other) { + String[] thisParts = this.prerelease.split("\\.", 0); + if(this.prerelease.equals("")) { + thisParts = new String[0]; + } + String[] thatParts = other.prerelease.split("\\.", 0); + if(other.prerelease.equals("")) { + thatParts = new String[0]; + } + // NOT having a prerelease is > having one + if(thisParts.length > 0 && thatParts.length == 0) { + return -1; + } else if(thisParts.length == 0 && thatParts.length > 0) { + return 1; + } else if(thisParts.length == 0 && thatParts.length == 0) { + return 0; + } + + int i = 0; + + do { + if(thisParts.length <= i && thatParts.length <= i) { + return 0; + } else if(thatParts.length <= i) { + return 1; + } else if(thisParts.length <= i) { + return -1; + } + String a = thisParts[i]; + String b = thatParts[i]; + if(!a.equals(b)) { + return compareIdentifiers(a, b); + } + ++i; + } while(true); + } + + /** + * {@inheritDoc} + * @param o + * @return + */ + @Override + public int compareTo(SemVer2 o) { + { + int[] thisParts = new int[]{major, minor, patch}; + int[] otherParts = new int[]{o.getMajor(), o.getMinor(), o.getSupplemental()}; + for(int i = 0; i < thisParts.length; i++) { + int n1 = thisParts[i]; + int n2 = otherParts[i]; + if(n1 < n2) { + return -1; + } + if(n1 > n2) { + return 1; + } + } + } + // They're the same version, but the prerelease version needs to be considered also. (buildMetaData is + // completely ignored for precedence purposes). The rules for priority of + // this is as follows: + /* + Precedence for two pre-release versions with the same major, minor, and patch version MUST be determined by + comparing each dot separated identifier from left to right until a difference is found as follows: identifiers + consisting of only digits are compared numerically and identifiers with letters or hyphens are compared + lexically in ASCII sort order. Numeric identifiers always have lower precedence than non-numeric identifiers. + A larger set of pre-release fields has a higher precedence than a smaller set, if all of the preceding + identifiers are equal. + Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 + < 1.0.0-rc.1 < 1.0.0. + */ + return comparePre(o); + } + + @Override + public boolean equals(Object obj) { + if(obj instanceof SemVer2) { + SemVer2 v = (SemVer2) obj; + if(major == v.getMajor() && minor == v.getMinor() && patch == v.getSupplemental() && comparePre(v) == 0) { + return true; + } else { + return false; + } + } + if(obj instanceof Version) { + Version v = (Version) obj; + if(major == v.getMajor() && minor == v.getMinor() && patch == v.getSupplemental()) { + return true; + } else { + return false; + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int hash = 5; + hash = 97 * hash + this.major; + hash = 97 * hash + this.minor; + hash = 97 * hash + this.patch; + return hash; + } + + @Override + public boolean lt(Version other) { + if(other instanceof SemVer2) { + return compareTo((SemVer2) other) < 0; + } + return checkLT(this, other); + } + + @Override + public boolean lte(Version other) { + if(other instanceof SemVer2) { + if(this.compareTo((SemVer2) other) == 0) { + return true; + } + } else if(this.equals(other)) { + return true; + } + return lt(other); + } + + @Override + public boolean gt(Version other) { + if(other instanceof SemVer2) { + return compareTo((SemVer2) other) > 0; + } + return checkGT(this, other); + } + + @Override + public boolean gte(Version other) { + if(other instanceof SemVer2) { + if(this.compareTo((SemVer2) other) == 0) { + return true; + } + } else if(this.equals(other)) { + return true; + } + return gt(other); + } +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/SignalHandler.java b/src/main/java/com/laytonsmith/PureUtilities/SignalHandler.java index 45bda8201b..04aaecb659 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/SignalHandler.java +++ b/src/main/java/com/laytonsmith/PureUtilities/SignalHandler.java @@ -1,4 +1,3 @@ - package com.laytonsmith.PureUtilities; import java.util.HashMap; @@ -11,39 +10,38 @@ * Adds a generic way to handle signals, if the VM was started with the -Xrs option. */ public class SignalHandler { - - private static final Map handlers = new HashMap<>(); - - private static final Set setup = new HashSet<>(); - + + private static final Map HANDLERS = new HashMap<>(); + + private static final Set SETUP = new HashSet<>(); + /** - * Registers a new signal handler, and returns the last one + * Registers a new signal handler, and returns the last one + * * @param type The signal type to register for * @param handler The handler, which will be called when the signal occurs. - * @return The last handler that was registered for this signal, or null if - * no previous handler was registered. - * @throws IllegalArgumentException If the signal cannot be registered, for instance, - * if the signal is already registered by the VM or the OS, it won't be possible to - * register for this signal. Also, if the signal type itself is uncatchable, this - * is also thrown. + * @return The last handler that was registered for this signal, or null if no previous handler was registered. + * @throws IllegalArgumentException If the signal cannot be registered, for instance, if the signal is already + * registered by the VM or the OS, it won't be possible to register for this signal. Also, if the signal type itself + * is uncatchable, this is also thrown. */ - public static SignalCallback addHandler(final SignalType type, SignalCallback handler){ - if(!type.isCatchable()){ + public static SignalCallback addHandler(final SignalType type, SignalCallback handler) { + if(!type.isCatchable()) { throw new IllegalArgumentException(type.getSignalName() + " cannot be caught, and therefore cannot be registered."); } SignalCallback last = null; - if(handlers.containsKey(type)){ - last = handlers.get(type); + if(HANDLERS.containsKey(type)) { + last = HANDLERS.get(type); } - handlers.put(type, handler); - if(!setup.contains(type)){ + HANDLERS.put(type, handler); + if(!SETUP.contains(type)) { sun.misc.Signal.handle(new sun.misc.Signal(type.getSignalName()), new sun.misc.SignalHandler() { @Override public void handle(Signal sig) { - boolean handled = handlers.get(type).handle(type); - if(!handled){ - if(type.getDefaultAction() == SignalType.DefaultAction.IGNORE){ + boolean handled = HANDLERS.get(type).handle(type); + if(!handled) { + if(type.getDefaultAction() == SignalType.DefaultAction.IGNORE) { sun.misc.SignalHandler.SIG_IGN.handle(sig); } else { sun.misc.SignalHandler.SIG_DFL.handle(sig); @@ -51,27 +49,53 @@ public void handle(Signal sig) { } } }); - setup.add(type); + SETUP.add(type); } return last; } - + /** * Raises the specified signal in the process. - * @param type + * + * @param type */ - public static void raise(SignalType type){ + public static void raise(SignalType type) { sun.misc.Signal.raise(new sun.misc.Signal(type.getSignalName())); } - + public static interface SignalCallback { + /** * When the signal this was registered with occurs, this method is called. + * * @param type The type that activated this. - * @return If the signal should be ignored, return true. If false is returned, - * the default action will occur. + * @return If the signal should be ignored, return true. If false is returned, the default action will occur. */ boolean handle(SignalType type); } - + + /** + * In many cases, you just want to register for the signals that are meant to terminate the program, so you can + * do a graceful shutdown. In those cases, you can simply call this method, and provide the handler, and it will + * register it with the SIGINT and SIGTERM signals. + *

+ * Unlike {@link #addHandler}, this provides no way to obtain previously registered handlers. Also unlike + * addHandler, no {@link IllegalArgumentException} will be thrown if the signals can't be bound. Thus, this is + * a best effort attempt, and if you need to positively know if a signal couldn't be bound, don't use this + * method. + * @param handler + */ + public static void addStopHandlers(SignalCallback handler) { + try { + addHandler(Signals.SIGTERM, handler); + } catch (IllegalArgumentException ex) { + // + } + try { + addHandler(Signals.SIGINT, handler); + } catch (IllegalArgumentException ex) { + // + } + } + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/SignalType.java b/src/main/java/com/laytonsmith/PureUtilities/SignalType.java index aec216dfd2..2e03b50c5b 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/SignalType.java +++ b/src/main/java/com/laytonsmith/PureUtilities/SignalType.java @@ -1,9 +1,10 @@ package com.laytonsmith.PureUtilities; /** - * + * The interface for various signal types. See {@link Signals} for various default implementations. */ public interface SignalType { + /** * Returns the default action of the signal. * @@ -12,8 +13,7 @@ public interface SignalType { DefaultAction getDefaultAction(); /** - * Some signals are not catchable. If this returns true, this is one of - * those signals. + * Some signals are not catchable. If this returns true, this is one of those signals. * * @return */ @@ -31,16 +31,14 @@ public interface SignalType { */ public static enum DefaultAction { /** - * Abnormal termination of the process. The process is terminated with - * all the consequences of _exit() except that the status made available - * to wait() and waitpid() indicates abnormal termination by the - * specified signal. + * Abnormal termination of the process. The process is terminated with all the consequences of _exit() except + * that the status made available to wait() and waitpid() indicates abnormal termination by the specified + * signal. */ TERMINATE, /** - * Abnormal termination of the process. Additionally, - * implementation-defined abnormal termination actions, such as creation - * of a core file, may occur. + * Abnormal termination of the process. Additionally, implementation-defined abnormal termination actions, such + * as creation of a core file, may occur. */ ACTION_TERMINATE, /** diff --git a/src/main/java/com/laytonsmith/PureUtilities/Signals.java b/src/main/java/com/laytonsmith/PureUtilities/Signals.java index d78e3430ea..3479b1d3c7 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Signals.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Signals.java @@ -1,4 +1,3 @@ - package com.laytonsmith.PureUtilities; /** @@ -6,192 +5,159 @@ */ public enum Signals implements SignalType { /** - * The SIGABRT signal is sent to a process to tell it to abort, i.e. to - * terminate. The signal is usually initiated by the process itself when - * it calls abort function of the C Standard Library, but it can be sent - * to the process from outside as well as any other signal. + * The SIGABRT signal is sent to a process to tell it to abort, i.e. to terminate. The signal is usually initiated + * by the process itself when it calls abort function of the C Standard Library, but it can be sent to the process + * from outside as well as any other signal. */ SIGABRT("ABRT", DefaultAction.ACTION_TERMINATE), /** - * The SIGALRM, SIGVTALRM and SIGPROF signal is sent to a process when the - * time limit specified in a call to a preceding alarm setting function - * (such as setitimer) elapses. SIGALRM is sent when real or clock time - * elapses. SIGVTALRM is sent when CPU time used by the process elapses. - * SIGPROF is sent when CPU time used by the process and by the system on - * behalf of the process elapses. + * The SIGALRM, SIGVTALRM and SIGPROF signal is sent to a process when the time limit specified in a call to a + * preceding alarm setting function (such as setitimer) elapses. SIGALRM is sent when real or clock time elapses. + * SIGVTALRM is sent when CPU time used by the process elapses. SIGPROF is sent when CPU time used by the process + * and by the system on behalf of the process elapses. */ SIGALRM("ALRM", DefaultAction.TERMINATE), /** - * The SIGBUS signal is sent to a process when it causes a bus error. The - * conditions that lead to the signal being raised are, for example, - * incorrect memory access alignment or non-existent physical address. + * The SIGBUS signal is sent to a process when it causes a bus error. The conditions that lead to the signal being + * raised are, for example, incorrect memory access alignment or non-existent physical address. */ SIGBUS("BUS", DefaultAction.ACTION_TERMINATE), /** - * The SIGCHLD signal is sent to a process when a child process terminates, - * is interrupted, or resumes after being interrupted. One common usage of - * the signal is to instruct the operating system to clean up the resources - * used by a child process after its termination without an explicit call to - * the wait system call. + * The SIGCHLD signal is sent to a process when a child process terminates, is interrupted, or resumes after being + * interrupted. One common usage of the signal is to instruct the operating system to clean up the resources used by + * a child process after its termination without an explicit call to the wait system call. */ SIGCHLD("CHLD", DefaultAction.IGNORE), /** - * The SIGCONT signal instructs the operating system to continue (restart) a - * process previously paused by the SIGSTOP or SIGTSTP signal. One important - * use of this signal is in job control in the Unix shell. + * The SIGCONT signal instructs the operating system to continue (restart) a process previously paused by the + * SIGSTOP or SIGTSTP signal. One important use of this signal is in job control in the Unix shell. */ SIGCONT("CONT", DefaultAction.CONTINUE), /** - * The SIGFPE signal is sent to a process when it executes an erroneous - * arithmetic operation, such as division by zero (the FPE stands for - * floating point exception). + * The SIGFPE signal is sent to a process when it executes an erroneous arithmetic operation, such as division by + * zero (the FPE stands for floating point exception). */ SIGFPE("FPE", DefaultAction.ACTION_TERMINATE), /** - * The SIGHUP signal is sent to a process when its controlling terminal is - * closed. It was originally designed to notify the process of a serial line - * drop (a hangup). In modern systems, this signal usually means that the - * controlling pseudo or virtual terminal has been closed. Many daemons will - * reload their configuration files and reopen their logfiles instead of - * exiting when receiving this signal. + * The SIGHUP signal is sent to a process when its controlling terminal is closed. It was originally designed to + * notify the process of a serial line drop (a hangup). In modern systems, this signal usually means that the + * controlling pseudo or virtual terminal has been closed. Many daemons will reload their configuration files and + * reopen their logfiles instead of exiting when receiving this signal. */ SIGHUP("HUP", DefaultAction.TERMINATE), /** - * The SIGILL signal is sent to a process when it attempts to execute an - * illegal, malformed, unknown, or privileged instruction. + * The SIGILL signal is sent to a process when it attempts to execute an illegal, malformed, unknown, or privileged + * instruction. */ SIGILL("ILL", DefaultAction.ACTION_TERMINATE), /** - * The SIGINT signal is sent to a process by its controlling terminal when a - * user wishes to interrupt the process. This is typically initiated by - * pressing Control-C, but on some systems, the "delete" character or - * "break" key can be used. + * The SIGINT signal is sent to a process by its controlling terminal when a user wishes to interrupt the process. + * This is typically initiated by pressing Control-C, but on some systems, the "delete" character or "break" key can + * be used. */ SIGINT("INT", DefaultAction.TERMINATE), /** - * The SIGKILL signal is sent to a process to cause it to terminate - * immediately (kill). In contrast to SIGTERM and SIGINT, this signal cannot - * be caught or ignored, and the receiving process cannot perform any - * clean-up upon receiving this signal. + * The SIGKILL signal is sent to a process to cause it to terminate immediately (kill). In contrast to SIGTERM and + * SIGINT, this signal cannot be caught or ignored, and the receiving process cannot perform any clean-up upon + * receiving this signal. */ SIGKILL("KILL", DefaultAction.TERMINATE, false), /** - * The SIGPIPE signal is sent to a process when it attempts to write to a - * pipe without a process connected to the other end. + * The SIGPIPE signal is sent to a process when it attempts to write to a pipe without a process connected to the + * other end. */ SIGPIPE("PIPE", DefaultAction.TERMINATE), /** - * The SIGQUIT signal is sent to a process by its controlling terminal when - * the user requests that the process quit and perform a core dump. + * The SIGQUIT signal is sent to a process by its controlling terminal when the user requests that the process quit + * and perform a core dump. */ SIGQUIT("QUIT", DefaultAction.ACTION_TERMINATE), /** - * The SIGSEGV signal is sent to a process when it makes an invalid virtual - * memory reference, or segmentation fault, i.e. when it performs a - * segmentation violation. + * The SIGSEGV signal is sent to a process when it makes an invalid virtual memory reference, or segmentation fault, + * i.e. when it performs a segmentation violation. */ SIGSEGV("SEGV", DefaultAction.ACTION_TERMINATE), /** - * The SIGSTOP signal instructs the operating system to stop a process for - * later resumption. Cannot be caught or ignored. + * The SIGSTOP signal instructs the operating system to stop a process for later resumption. Cannot be caught or + * ignored. */ SIGSTOP("STOP", DefaultAction.STOP, false), /** - * The SIGTERM signal is sent to a process to request its termination. - * Unlike the SIGKILL signal, it can be caught and interpreted or ignored - * by the process. This allows the process to perform nice termination - * releasing resources and saving state if appropriate. It should be noted - * that SIGINT is nearly identical to SIGTERM. + * The SIGTERM signal is sent to a process to request its termination. Unlike the SIGKILL signal, it can be caught + * and interpreted or ignored by the process. This allows the process to perform nice termination releasing + * resources and saving state if appropriate. It should be noted that SIGINT is nearly identical to SIGTERM. */ SIGTERM("TERM", DefaultAction.TERMINATE), /** - * The SIGTSTP signal is sent to a process by its controlling terminal to - * request it to stop temporarily. It is commonly initiated by the user - * pressing Control-Z. Unlike SIGSTOP, the process can register a signal - * handler for or ignore the signal. + * The SIGTSTP signal is sent to a process by its controlling terminal to request it to stop temporarily. It is + * commonly initiated by the user pressing Control-Z. Unlike SIGSTOP, the process can register a signal handler for + * or ignore the signal. */ SIGTSTP("STP", DefaultAction.STOP), /** - * The SIGTTIN and SIGTTOU signals are sent to a process when it attempts to - * read in or write out respectively from the tty while in the background. - * Typically, this signal can be received only by processes under job - * control; daemons do not have controlling terminals and should never - * receive this signal. + * The SIGTTIN and SIGTTOU signals are sent to a process when it attempts to read in or write out respectively from + * the tty while in the background. Typically, this signal can be received only by processes under job control; + * daemons do not have controlling terminals and should never receive this signal. */ SIGTTIN("TTIN", DefaultAction.STOP), /** - * The SIGTTIN and SIGTTOU signals are sent to a process when it attempts to - * read in or write out respectively from the tty while in the background. - * Typically, this signal can be received only by processes under job - * control; daemons do not have controlling terminals and should never - * receive this signal. + * The SIGTTIN and SIGTTOU signals are sent to a process when it attempts to read in or write out respectively from + * the tty while in the background. Typically, this signal can be received only by processes under job control; + * daemons do not have controlling terminals and should never receive this signal. */ SIGTTOU("TTOU", DefaultAction.STOP), /** - * The SIGUSR1 and SIGUSR2 signals are sent to a process to indicate - * user-defined conditions. + * The SIGUSR1 and SIGUSR2 signals are sent to a process to indicate user-defined conditions. */ SIGUSR1("USR1", DefaultAction.TERMINATE), /** - * The SIGUSR1 and SIGUSR2 signals are sent to a process to indicate - * user-defined conditions. + * The SIGUSR1 and SIGUSR2 signals are sent to a process to indicate user-defined conditions. */ SIGUSR2("USR2", DefaultAction.TERMINATE), /** - * The SIGPOLL signal is sent to a process when an asynchronous I/O event - * occurs (meaning it has been polled). + * The SIGPOLL signal is sent to a process when an asynchronous I/O event occurs (meaning it has been polled). */ SIGPOLL("POLL", DefaultAction.TERMINATE), /** - * The SIGALRM, SIGVTALRM and SIGPROF signal is sent to a process when the - * time limit specified in a call to a preceding alarm setting function - * (such as setitimer) elapses. SIGALRM is sent when real or clock time - * elapses. SIGVTALRM is sent when CPU time used by the process elapses. - * SIGPROF is sent when CPU time used by the process and by the system on - * behalf of the process elapses. + * The SIGALRM, SIGVTALRM and SIGPROF signal is sent to a process when the time limit specified in a call to a + * preceding alarm setting function (such as setitimer) elapses. SIGALRM is sent when real or clock time elapses. + * SIGVTALRM is sent when CPU time used by the process elapses. SIGPROF is sent when CPU time used by the process + * and by the system on behalf of the process elapses. */ SIGPROF("PROF", DefaultAction.TERMINATE), /** - * The SIGSYS signal is sent to a process when it passes a bad argument to a - * system call. + * The SIGSYS signal is sent to a process when it passes a bad argument to a system call. */ SIGSYS("SYS", DefaultAction.ACTION_TERMINATE), /** - * The SIGTRAP signal is sent to a process when an exception (or trap) - * occurs: a condition that a debugger has requested to be informed of — for - * example, when a particular function is executed, or when a particular - * variable changes value. + * The SIGTRAP signal is sent to a process when an exception (or trap) occurs: a condition that a debugger has + * requested to be informed of — for example, when a particular function is executed, or when a particular variable + * changes value. */ SIGTRAP("TRAP", DefaultAction.ACTION_TERMINATE), /** - * The SIGURG signal is sent to a process when a socket has urgent or - * out-of-band data available to read. + * The SIGURG signal is sent to a process when a socket has urgent or out-of-band data available to read. */ SIGURG("URG", DefaultAction.IGNORE), /** - * The SIGALRM, SIGVTALRM and SIGPROF signal is sent to a process when the - * time limit specified in a call to a preceding alarm setting function - * (such as setitimer) elapses. SIGALRM is sent when real or clock time - * elapses. SIGVTALRM is sent when CPU time used by the process elapses. - * SIGPROF is sent when CPU time used by the process and by the system on - * behalf of the process elapses. + * The SIGALRM, SIGVTALRM and SIGPROF signal is sent to a process when the time limit specified in a call to a + * preceding alarm setting function (such as setitimer) elapses. SIGALRM is sent when real or clock time elapses. + * SIGVTALRM is sent when CPU time used by the process elapses. SIGPROF is sent when CPU time used by the process + * and by the system on behalf of the process elapses. */ SIGVTALRM("VTALRM", DefaultAction.TERMINATE), /** - * The SIGXCPU signal is sent to a process when it has used up the CPU for a - * duration that exceeds a certain predetermined user-settable value. The - * arrival of a SIGXCPU signal provides the receiving process a chance to - * quickly save any intermediate results and to exit gracefully, before it - * is terminated by the operating system using the SIGKILL signal. + * The SIGXCPU signal is sent to a process when it has used up the CPU for a duration that exceeds a certain + * predetermined user-settable value. The arrival of a SIGXCPU signal provides the receiving process a chance to + * quickly save any intermediate results and to exit gracefully, before it is terminated by the operating system + * using the SIGKILL signal. */ SIGXCPU("XCPU", DefaultAction.ACTION_TERMINATE), /** - * The SIGXFSZ signal is sent to a process when it grows a file larger than - * the maximum allowed size. + * The SIGXFSZ signal is sent to a process when it grows a file larger than the maximum allowed size. */ - SIGXFSZ("XFSZ", DefaultAction.ACTION_TERMINATE), - ; - + SIGXFSZ("XFSZ", DefaultAction.ACTION_TERMINATE),; + private final String signalName; /** * The default action of a signal @@ -201,44 +167,45 @@ public enum Signals implements SignalType { * Whether or not a signal is catchable */ private final boolean catchable; - - private Signals(String signalName, DefaultAction defaultAction){ + + private Signals(String signalName, DefaultAction defaultAction) { this(signalName, defaultAction, true); } - - private Signals(String signalName, DefaultAction defaultAction, boolean catchable){ + + private Signals(String signalName, DefaultAction defaultAction, boolean catchable) { this.signalName = signalName; this.defaultAction = defaultAction; this.catchable = catchable; } - + /** * Returns the default action of the signal. - * @return + * + * @return */ @Override - public DefaultAction getDefaultAction(){ + public DefaultAction getDefaultAction() { return this.defaultAction; } - + /** - * Some signals are not catchable. If this returns true, this is one of - * those signals. - * @return + * Some signals are not catchable. If this returns true, this is one of those signals. + * + * @return */ @Override - public boolean isCatchable(){ + public boolean isCatchable() { return this.catchable; } - + /** * Returns the signal name, as required by the JVM. - * @return + * + * @return */ @Override - public String getSignalName(){ + public String getSignalName() { return this.signalName; } - - + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/SimpleVersion.java b/src/main/java/com/laytonsmith/PureUtilities/SimpleVersion.java index f6da4d9f6c..ff03949a1c 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/SimpleVersion.java +++ b/src/main/java/com/laytonsmith/PureUtilities/SimpleVersion.java @@ -1,144 +1,155 @@ - - package com.laytonsmith.PureUtilities; import java.util.regex.Matcher; import java.util.regex.Pattern; /** - * A version is formatted as such: 1.2.10 beta-1 where 1 is the major version, - * 2 is the minor version, 10 is the supplemental version, and beta-1 is the tag. - * When comparing two versions, the tag is not considered. + * A version is formatted as such: 1.2.10 beta-1 where 1 is the major version, 2 is the minor version, 10 is the + * supplemental version, and beta-1 is the tag. When comparing two versions, the tag is not considered. + * + * Generally speaking, this shouldn't be used in favor of {@link SemVer2}, but it is not deprecated, since third + * party version numbers may not conform to the SemVer2 standard, which is quite strict. However, new code should + * use SemVer2 where possible. */ public class SimpleVersion implements Version { - - private int major; - private int minor; - private int supplemental; - private String tag; - - /** - * Creates a new SimpleVersion object from a string version number. The tag is - * optional, but all other parameters are required. If left off, each version - * part is set to 0. - * @param version The version, as a string - */ - Pattern p = Pattern.compile("(\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))?(?:\\s+(.*))?"); - public SimpleVersion(String version){ - Matcher m = p.matcher(version); - if(m.find()){ - major = Integer.parseInt(m.group(1)==null?"0":m.group(1)); - minor = Integer.parseInt(m.group(2)==null?"0":m.group(2)); - supplemental = Integer.parseInt(m.group(3)==null?"0":m.group(3)); - tag = m.group(4)==null?"":m.group(4); - } else { - major = minor = supplemental = 0; - tag = ""; - } - } - + + private int major; + private int minor; + private int supplemental; + private String tag; + + private static final Pattern PATTERN = Pattern.compile("(\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))?(?:\\s+(.*))?"); + + /** + * Creates a new SimpleVersion object from a string version number. The tag is optional, but all other parameters + * are required. If left off, each version part is set to 0. + * + * @param version The version, as a string + */ + public SimpleVersion(String version) { + Matcher m = PATTERN.matcher(version); + if(m.find()) { + try { + major = Integer.parseInt(m.group(1) == null ? "0" : m.group(1)); + minor = Integer.parseInt(m.group(2) == null ? "0" : m.group(2)); + supplemental = Integer.parseInt(m.group(3) == null ? "0" : m.group(3)); + tag = m.group(4) == null ? "" : m.group(4); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Version numbers must be integers", e); + } + } else { + throw new IllegalArgumentException("Invalid version string provided"); + } + } + /** * Creates a new version with programmatic parameters. + * * @param major * @param minor * @param supplemental - * @param tag + * @param tag */ - public SimpleVersion(int major, int minor, int supplemental, String tag){ - this.major = major; - this.minor = minor; - this.supplemental = supplemental; - this.tag = tag; - } - + public SimpleVersion(int major, int minor, int supplemental, String tag) { + this.major = major; + this.minor = minor; + this.supplemental = supplemental; + this.tag = tag; + } + /** - * Creates a new version with programmatic parameters, and an emtpy tag. + * Creates a new version with programmatic parameters, and an empty tag. + * * @param major * @param minor - * @param supplemental + * @param supplemental */ - public SimpleVersion(int major, int minor, int supplemental){ - this(major, minor, supplemental, ""); - } - + public SimpleVersion(int major, int minor, int supplemental) { + this(major, minor, supplemental, ""); + } + /** * Returns the major version. - * @return + * + * @return */ @Override public int getMajor() { return major; } - + /** * Returns the minor version. - * @return + * + * @return */ @Override public int getMinor() { return minor; } - + /** * Returns the supplemental version. - * @return + * + * @return */ @Override public int getSupplemental() { return supplemental; } - + /** * Returns the tag in this version. - * @return + * + * @return */ - public String getTag(){ + public String getTag() { return tag; } - - @Override - public String toString(){ - return (major + "." + minor + "." + supplemental + " " + tag).trim(); - } - public int compareTo(Version o) { - int [] thisParts = new int[]{major, minor, supplemental}; - int [] otherParts = new int[]{o.getMajor(), o.getMinor(), o.getSupplemental()}; - for(int i = 0; i < thisParts.length; i++){ + @Override + public String toString() { + return (major + "." + minor + "." + supplemental + " " + tag).trim(); + } + + public int compareTo(Version o) { + int[] thisParts = new int[]{major, minor, supplemental}; + int[] otherParts = new int[]{o.getMajor(), o.getMinor(), o.getSupplemental()}; + for(int i = 0; i < thisParts.length; i++) { int n1 = thisParts[i]; int n2 = otherParts[i]; - if(n1 < n2){ + if(n1 < n2) { return -1; } - if(n1 > n2){ + if(n1 > n2) { return 1; } } return 0; - } - - @Override - public boolean equals(Object obj) { - if(obj instanceof Version){ - Version v = (Version) obj; - if(major == v.getMajor() && minor == v.getMinor() && supplemental == v.getSupplemental()){ - return true; - } else { - return false; - } - } else { - return false; - } - } - - @Override - public int hashCode() { - int hash = 5; - hash = 97 * hash + this.major; - hash = 97 * hash + this.minor; - hash = 97 * hash + this.supplemental; - return hash; - } + } + + @Override + public boolean equals(Object obj) { + if(obj instanceof Version) { + Version v = (Version) obj; + if(major == v.getMajor() && minor == v.getMinor() && supplemental == v.getSupplemental()) { + return true; + } else { + return false; + } + } else { + return false; + } + } + + @Override + public int hashCode() { + int hash = 5; + hash = 97 * hash + this.major; + hash = 97 * hash + this.minor; + hash = 97 * hash + this.supplemental; + return hash; + } @Override public boolean lt(Version other) { @@ -159,62 +170,62 @@ public boolean gt(Version other) { public boolean gte(Version other) { return checkGTE(this, other); } - - public static boolean checkLT(Version lhs, Version rhs){ - if(lhs == null || rhs == null){ + + public static boolean checkLT(Version lhs, Version rhs) { + if(lhs == null || rhs == null) { throw new NullPointerException(); } - if(lhs.getMajor() == rhs.getMajor()){ - if(lhs.getMinor() == rhs.getMinor()){ - if(lhs.getSupplemental() == rhs.getSupplemental()){ + if(lhs.getMajor() == rhs.getMajor()) { + if(lhs.getMinor() == rhs.getMinor()) { + if(lhs.getSupplemental() == rhs.getSupplemental()) { return false; - } else if(lhs.getSupplemental() < rhs.getSupplemental()){ + } else if(lhs.getSupplemental() < rhs.getSupplemental()) { return true; } - } else if(lhs.getMinor() < rhs.getMinor()){ + } else if(lhs.getMinor() < rhs.getMinor()) { return true; } - } else if(lhs.getMajor() < rhs.getMajor()){ + } else if(lhs.getMajor() < rhs.getMajor()) { return true; } return false; } - - public static boolean checkLTE(Version lhs, Version rhs){ - if(lhs == null || rhs == null){ + + public static boolean checkLTE(Version lhs, Version rhs) { + if(lhs == null || rhs == null) { throw new NullPointerException(); } - if(lhs.equals(rhs)){ + if(lhs.equals(rhs)) { return true; } return checkLT(lhs, rhs); } - - public static boolean checkGT(Version lhs, Version rhs){ - if(lhs == null || rhs == null){ + + public static boolean checkGT(Version lhs, Version rhs) { + if(lhs == null || rhs == null) { throw new NullPointerException(); } - if(lhs.getMajor() == rhs.getMajor()){ - if(lhs.getMinor() == rhs.getMinor()){ - if(lhs.getSupplemental() == rhs.getSupplemental()){ + if(lhs.getMajor() == rhs.getMajor()) { + if(lhs.getMinor() == rhs.getMinor()) { + if(lhs.getSupplemental() == rhs.getSupplemental()) { return false; - } else if(lhs.getSupplemental() > rhs.getSupplemental()){ + } else if(lhs.getSupplemental() > rhs.getSupplemental()) { return true; } - } else if(lhs.getMinor() > rhs.getMinor()){ + } else if(lhs.getMinor() > rhs.getMinor()) { return true; } - } else if(lhs.getMajor() > rhs.getMajor()){ + } else if(lhs.getMajor() > rhs.getMajor()) { return true; } return false; } - - public static boolean checkGTE(Version lhs, Version rhs){ - if(lhs == null || rhs == null){ + + public static boolean checkGTE(Version lhs, Version rhs) { + if(lhs == null || rhs == null) { throw new NullPointerException(); } - if(lhs.equals(rhs)){ + if(lhs.equals(rhs)) { return true; } return checkGT(lhs, rhs); diff --git a/src/main/java/com/laytonsmith/PureUtilities/Sizes.java b/src/main/java/com/laytonsmith/PureUtilities/Sizes.java index 9722f4e066..62c1017e1d 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Sizes.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Sizes.java @@ -1,194 +1,194 @@ package com.laytonsmith.PureUtilities; /** - * This class simply provides a method to getting the size of java primitives, - * without having magic numbers everywhere. + * This class simply provides a method to getting the size of java primitives, without having magic numbers everywhere. + * * - * */ public final class Sizes { - public static final int booleanSizeBits = 1; - public static final int byteSize = 1; - public static final int byteSizeBits = byteSize * 8; - public static final int shortSize = 2; - public static final int shortSizeBits = shortSize * byteSizeBits; - public static final int intSize = 4; - public static final int intSizeBits = intSize * byteSizeBits; - public static final int longSize = 8; - public static final int longSizeBits = longSize * byteSizeBits; - public static final int floatSize = 4; - public static final int floatSizeBits = floatSize * byteSizeBits; - public static final int doubleSize = 8; - public static final int doubleSizeBits = doubleSize * byteSizeBits; - public static final int charSize = 2; - public static final int charSizeBits = charSize * byteSizeBits; + public static final int BOOLEAN_SIZE_BITS = 1; + public static final int BYTE_SIZE = 1; + public static final int BYTE_SIZE_BITS = BYTE_SIZE * 8; + public static final int SHORT_SIZE = 2; + public static final int SHORT_SIZE_BITS = SHORT_SIZE * BYTE_SIZE_BITS; + public static final int INT_SIZE = 4; + public static final int INT_SIZE_BITS = INT_SIZE * BYTE_SIZE_BITS; + public static final int LONG_SIZE = 8; + public static final int LONG_SIZE_BITS = LONG_SIZE * BYTE_SIZE_BITS; + public static final int FLOAT_SIZE = 4; + public static final int FLOAT_SIZE_BITS = FLOAT_SIZE * BYTE_SIZE_BITS; + public static final int DOUBLE_SIZE = 8; + public static final int DOUBLE_SIZE_BITS = DOUBLE_SIZE * BYTE_SIZE_BITS; + public static final int CHAR_SIZE = 2; + public static final int CHAR_SIZE_BITS = CHAR_SIZE * BYTE_SIZE_BITS; // bytes public static int sizeof(byte b) { - return byteSize; + return BYTE_SIZE; } public static int sizeof(short s) { - return shortSize; + return SHORT_SIZE; } public static int sizeof(int i) { - return intSize; + return INT_SIZE; } public static int sizeof(long l) { - return longSize; + return LONG_SIZE; } public static int sizeof(float f) { - return floatSize; + return FLOAT_SIZE; } public static int sizeof(double d) { - return doubleSize; + return DOUBLE_SIZE; } public static int sizeof(char c) { - return charSize; + return CHAR_SIZE; } - // NOTE: no sizeof for boolean, only sizeofBits - // bits - public static int sizeofBits(byte b) { - return byteSizeBits; + // array bytes + public static long sizeof(byte[] b) { + return BYTE_SIZE * b.length; } - public static int sizeofBits(short s) { - return shortSizeBits; + public static long sizeof(short[] s) { + return SHORT_SIZE * s.length; } - public static int sizeofBits(int i) { - return intSizeBits; + public static long sizeof(int[] i) { + return INT_SIZE * i.length; } - public static int sizeofBits(long l) { - return longSizeBits; + public static long sizeof(long[] l) { + return LONG_SIZE * l.length; } - public static int sizeofBits(float f) { - return floatSizeBits; + public static long sizeof(float[] f) { + return FLOAT_SIZE * f.length; } - public static int sizeofBits(double d) { - return doubleSizeBits; + public static long sizeof(double[] d) { + return DOUBLE_SIZE * d.length; } - public static int sizeofBits(char c) { - return charSizeBits; + public static long sizeof(char[] c) { + return CHAR_SIZE * c.length; } - public static int sizeofBits(boolean b) { - return booleanSizeBits; + // Class types + public static int sizeof(Class c) { + if(c.isPrimitive()) { + if(c == byte.class) { + return BYTE_SIZE; + } else if(c == short.class) { + return SHORT_SIZE; + } else if(c == int.class) { + return INT_SIZE; + } else if(c == long.class) { + return LONG_SIZE; + } else if(c == float.class) { + return FLOAT_SIZE; + } else if(c == double.class) { + return DOUBLE_SIZE; + } else if(c == char.class) { + return CHAR_SIZE; + } + } + throw new RuntimeException("Only non-boolean primitives are supported"); } - // array bytes - public static long sizeof(byte[] b) { - return byteSize * b.length; + // NOTE: no sizeof for boolean, only sizeofBits + // bits + public static int sizeofBits(byte b) { + return BYTE_SIZE_BITS; } - public static long sizeof(short[] s) { - return shortSize * s.length; + public static int sizeofBits(short s) { + return SHORT_SIZE_BITS; } - public static long sizeof(int[] i) { - return intSize * i.length; + public static int sizeofBits(int i) { + return INT_SIZE_BITS; } - public static long sizeof(long[] l) { - return longSize * l.length; + public static int sizeofBits(long l) { + return LONG_SIZE_BITS; } - public static long sizeof(float[] f) { - return floatSize * f.length; + public static int sizeofBits(float f) { + return FLOAT_SIZE_BITS; } - public static long sizeof(double[] d) { - return doubleSize * d.length; + public static int sizeofBits(double d) { + return DOUBLE_SIZE_BITS; } - public static long sizeof(char[] c) { - return charSize * c.length; + public static int sizeofBits(char c) { + return CHAR_SIZE_BITS; + } + + public static int sizeofBits(boolean b) { + return BOOLEAN_SIZE_BITS; } // array bits public static long sizeofBits(byte[] b) { - return byteSizeBits * b.length; + return BYTE_SIZE_BITS * b.length; } public static long sizeofBits(short[] s) { - return shortSizeBits * s.length; + return SHORT_SIZE_BITS * s.length; } public static long sizeofBits(int[] i) { - return intSizeBits * i.length; + return INT_SIZE_BITS * i.length; } public static long sizeofBits(long[] l) { - return longSizeBits * l.length; + return LONG_SIZE_BITS * l.length; } public static long sizeofBits(float[] f) { - return floatSizeBits * f.length; + return FLOAT_SIZE_BITS * f.length; } public static long sizeofBits(double[] d) { - return doubleSizeBits * d.length; + return DOUBLE_SIZE_BITS * d.length; } public static long sizeofBits(char[] c) { - return charSizeBits * c.length; + return CHAR_SIZE_BITS * c.length; } public static long sizeofBits(boolean[] b) { - return booleanSizeBits * b.length; - } - - //Class types - public static int sizeof(Class c) { - if (c.isPrimitive()) { - if (c == byte.class) { - return byteSize; - } else if (c == short.class) { - return shortSize; - } else if (c == int.class) { - return intSize; - } else if (c == long.class) { - return longSize; - } else if (c == float.class) { - return floatSize; - } else if (c == double.class) { - return doubleSize; - } else if (c == char.class) { - return charSize; - } - } - throw new RuntimeException("Only non-boolean primitives are supported"); + return BOOLEAN_SIZE_BITS * b.length; } + // Class types public static int sizeofBits(Class c) { - if (c.isPrimitive()) { - if (c == byte.class) { - return byteSizeBits; - } else if (c == short.class) { - return shortSizeBits; - } else if (c == int.class) { - return intSizeBits; - } else if (c == long.class) { - return longSizeBits; - } else if (c == float.class) { - return floatSizeBits; - } else if (c == double.class) { - return doubleSizeBits; - } else if (c == char.class) { - return charSizeBits; - } else if (c == boolean.class) { - return booleanSizeBits; + if(c.isPrimitive()) { + if(c == byte.class) { + return BYTE_SIZE_BITS; + } else if(c == short.class) { + return SHORT_SIZE_BITS; + } else if(c == int.class) { + return INT_SIZE_BITS; + } else if(c == long.class) { + return LONG_SIZE_BITS; + } else if(c == float.class) { + return FLOAT_SIZE_BITS; + } else if(c == double.class) { + return DOUBLE_SIZE_BITS; + } else if(c == char.class) { + return CHAR_SIZE_BITS; + } else if(c == boolean.class) { + return BOOLEAN_SIZE_BITS; } } throw new RuntimeException("Only primitives are supported"); diff --git a/src/main/java/com/laytonsmith/PureUtilities/SmartComment.java b/src/main/java/com/laytonsmith/PureUtilities/SmartComment.java index b2f6e8405c..0a3bdbff40 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/SmartComment.java +++ b/src/main/java/com/laytonsmith/PureUtilities/SmartComment.java @@ -8,77 +8,89 @@ import java.util.regex.Pattern; /** - * Represents a javadoc style comment block, at its lowest level. The rules of smart comment - * are that it must start with /** and end with */. Within the comment, the first - * whitespace characters, * and optionally a single space after will be removed, so the line " * Text" - * would be simply "Text". Annotations are supported, there are two types of annotations, embedded, and normal. - * An embedded annotation is a data transformation construct, and a normal annotation is stored separately - * from the "body" of the comment, and multiple of the same annotation are allowed. Embedded annotations - * are transformed at parse time, and you provide the callback to do the transformation. For instance, the - * embedded annotation {@ code myCode} can be configured to return "<code>myCode</code>" - * Newlines and spaces are preserved in the body of the comment, but newlines are not stored with annotation - * parameters. - * + * Represents a javadoc style comment block, at its lowest level. The rules of smart comment are that it must start with + * /** and end with */. Within the comment, the first whitespace characters, * and optionally a single space after + * will be removed, so the line " * Text" would be simply "Text". Annotations are supported, there are two types of + * annotations, embedded, and normal. An embedded annotation is a data transformation construct, and a normal annotation + * is stored separately from the "body" of the comment, and multiple of the same annotation are allowed. Embedded + * annotations are transformed at parse time, and you provide the callback to do the transformation. For instance, the + * embedded annotation {@code myCode} can be configured to return "<code>myCode</code>" Newlines and + * spaces are preserved in the body of the comment, but newlines are not stored with annotation parameters. + * */ public class SmartComment { - + private static final Pattern ANNOTATION = Pattern.compile("@[a-zA-Z][a-zA-Z0-9]*"); private static final Pattern EMBEDDED_ANNOTATION = Pattern.compile("\\{@([a-zA-Z][a-zA-Z0-9]*) +(.*?)\\}"); private static final String LINE_START = "[\\t ]*\\* ?"; + private String unprocessed; private String raw; private String body; - private Map> annotations = new HashMap>(); - private Map rplcmnt = new HashMap(); - + private final Map> annotations = new HashMap<>(); + private final Map rplcmnt = new HashMap<>(); + /** * Creates a new smart comment. + * * @param comment The comment to be parsed */ - public SmartComment(String comment){ + public SmartComment(String comment) { this(comment, null); } - + + /** + * Creates a new SmartComment object, with additional embedded annotation processors. + * + * @param comment + * @param replacements + */ + public SmartComment(SmartComment comment, Map replacements) { + this(comment.unprocessed, replacements); + } + /** - * Creates a new smart comment. + * Creates a new smart comment. + * * @param comment The comment to be parsed - * @param replacements This is used to replace embedded annotations with some - * other text. For instance, if the comment contained { @ code myCode }, (minus spaces) it may be used to - * return "<code>myCode</code>". By default, if a particular embedded annotation - * has no handler, the embedded text is simply used as is. + * @param replacements This is used to replace embedded annotations with some other text. For instance, if the + * comment contained {@code {@code myCode}}, it may be used to return "<code>myCode</code>". By default, if a + * particular embedded annotation has no handler, the embedded text is simply used as is. */ - public SmartComment(String comment, Map replacements){ - if(replacements == null){ - replacements = new HashMap(); + public SmartComment(String comment, Map replacements) { + if(replacements == null) { + replacements = new HashMap<>(); } - + + unprocessed = comment; + //Remove the @ at the beginning, if present. - for(String key : replacements.keySet()){ + for(String key : replacements.keySet()) { rplcmnt.put(key.replaceFirst("@", ""), replacements.get(key)); } - + comment = comment.trim(); - if(comment.startsWith("/**")){ + if(comment.startsWith("/**")) { comment = comment.substring(3); } - if(comment.endsWith("*/")){ + if(comment.endsWith("*/")) { comment = comment.substring(0, comment.length() - 2); } - String [] lines = comment.split("\n|\r\n|\n\r"); + String[] lines = comment.split("\n|\r\n|\n\r"); StringBuilder b = new StringBuilder(); - for(String line : lines){ + for(String line : lines) { line = line.replaceFirst(LINE_START, ""); b.append("\n").append(line); } - + raw = replaceEmbedded(b.toString().trim()); - - String [] words = raw.split(" |\n"); + + String[] words = raw.split(" |\n"); StringBuilder buffer = new StringBuilder(); String lastAnnotation = null; int annotationIndex = -1; - for(String word : words){ - if(ANNOTATION.matcher(word).matches()){ - if(annotationIndex == -1){ + for(String word : words) { + if(ANNOTATION.matcher(word).matches()) { + if(annotationIndex == -1) { Matcher m = ANNOTATION.matcher(raw); m.find(); annotationIndex = m.start(); @@ -91,20 +103,20 @@ public SmartComment(String comment, Map replacements){ } } processBuffer(lastAnnotation, buffer.toString()); - if(annotationIndex == -1){ + if(annotationIndex == -1) { body = raw; } else { body = raw.substring(0, annotationIndex).trim(); } } - - private String replaceEmbedded(String string){ + + private String replaceEmbedded(String string) { //Replace embedded annotations Matcher embedded = EMBEDDED_ANNOTATION.matcher(string); - while(embedded.find()){ + while(embedded.find()) { String key = embedded.group(1); String data = embedded.group(2); - if(rplcmnt.containsKey(key)){ + if(rplcmnt.containsKey(key)) { string = string.replaceAll(Pattern.quote(embedded.group(0)), rplcmnt.get(key).replace(data)); } else { string = string.replaceAll(Pattern.quote(embedded.group(0)), data); @@ -112,48 +124,73 @@ private String replaceEmbedded(String string){ } return string; } - - private void processBuffer(String lastAnnotation, String buffer){ - if(lastAnnotation != null){ + + private void processBuffer(String lastAnnotation, String buffer) { + if(lastAnnotation != null) { addAnnotation(lastAnnotation, buffer.trim()); } } - - private void addAnnotation(String name, String value){ - if(!annotations.containsKey(name)){ - annotations.put(name, new ArrayList()); + + private void addAnnotation(String name, String value) { + if(!annotations.containsKey(name)) { + annotations.put(name, new ArrayList<>()); } annotations.get(name).add(value); } - + /** - * Gets the body of the comment block. - * @return + * Gets the body of the comment block. This will never be null. + * + * @return */ - public String getBody(){ + public String getBody() { return body; } - + /** - * Gets a list of annotation values for the comment block. + * Gets a list of annotation values for the comment block. If the annotation doesn't exist, an empty list + * is returned. + * * @param annotation - * @return + * @return */ - public List getAnnotations(String annotation){ - if(!annotation.startsWith("@")){ + public List getAnnotations(String annotation) { + if(!annotation.startsWith("@")) { annotation = "@" + annotation; } - return new ArrayList(annotations.get(annotation)); + List ann = annotations.get(annotation); + if(ann == null) { + return new ArrayList<>(); + } + return new ArrayList<>(ann); } - + + /** + * Returns a complete set of annotations in this comment. Note that this doesn't include + * embedded annotations, only the standalone ones. Further, note that the keys in the map + * will start with the at symbol, a fact which is optional to consider when using the + * {@link #getAnnotations(java.lang.String)} method, so consider using that if you aren't + * iterating through the list dynamically. + * @return + */ + public Map> getAnnotations() { + Map> ret = new HashMap<>(); + for(String key : annotations.keySet()) { + ret.put(key, getAnnotations(key)); + } + return ret; + } + public static interface Replacement { + /** - * Given the matched data in an embedded annotation, returns the transformed data, which - * is replaced in the text. + * Given the matched data in an embedded annotation, returns the transformed data, which is replaced in the + * text. + * * @param data - * @return + * @return */ public String replace(String data); } - + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/TermColors.java b/src/main/java/com/laytonsmith/PureUtilities/TermColors.java index 661d030ee6..0854e02e95 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/TermColors.java +++ b/src/main/java/com/laytonsmith/PureUtilities/TermColors.java @@ -1,7 +1,6 @@ - package com.laytonsmith.PureUtilities; -import java.awt.Color; +import com.laytonsmith.PureUtilities.Common.StreamUtils; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -17,263 +16,306 @@ /** * - * + * */ +// Variables can be set through DisableColors(), but should be handled as if they were final. +@SuppressWarnings("checkstyle:staticvariablename") public final class TermColors { - - private TermColors(){} - - public enum SYS { - - WINDOWS, - UNIX - } - public static final SYS SYSTEM; - static { - String os = System.getProperty("os.name"); - if (os.contains("Windows")) { - SYSTEM = SYS.WINDOWS; - } else { - SYSTEM = SYS.UNIX; - } - } - - - public static void cls(){ - if(SYSTEM.equals(SYS.WINDOWS)){ - //Fuck you windows. - for(int i = 0; i < 50; i++){ - System.out.println(); - } - } else { - System.out.print("\u001b[2J"); - System.out.flush(); - } - } - - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.FIELD) - private @interface color{} - - /* - * Standard foreground colors - */ - @color public static String RED = color(Color.RED).toString(); - @color public static String GREEN = color(Color.GREEN).toString(); - @color public static String BLUE = color(Color.BLUE).toString(); - @color public static String YELLOW = color(Color.YELLOW).toString(); - @color public static String CYAN = color(Color.CYAN).toString(); - @color public static String MAGENTA = color(Color.MAGENTA).toString(); - @color public static String BLACK = color(Color.BLACK).toString(); - @color public static String WHITE = color(Color.WHITE).toString(); - - /* - * Bright foreground colors - */ - @color public static String BRIGHT_RED = color(Color.RED, true, true); - @color public static String BRIGHT_GREEN = color(Color.GREEN, true, true); - @color public static String BRIGHT_BLUE = color(Color.BLUE, true, true); - @color public static String BRIGHT_YELLOW = color(Color.YELLOW, true, true); - @color public static String BRIGHT_CYAN = color(Color.CYAN, true, true); - @color public static String BRIGHT_MAGENTA = color(Color.MAGENTA, true, true); - @color public static String BRIGHT_BLACK = color(Color.BLACK, true, true); - @color public static String BRIGHT_WHITE = color(Color.WHITE, true, true); - - /* - * Standard background colors - */ - @color public static String BG_RED = color(Color.RED, false, false).toString(); - @color public static String BG_GREEN = color(Color.GREEN, false, false).toString(); - @color public static String BG_BLUE = color(Color.BLUE, false, false).toString(); - @color public static String BG_YELLOW = color(Color.YELLOW, false, false).toString(); - @color public static String BG_CYAN = color(Color.CYAN, false, false).toString(); - @color public static String BG_MAGENTA = color(Color.MAGENTA, false, false).toString(); - @color public static String BG_BLACK = color(Color.BLACK, false, false).toString(); - @color public static String BG_WHITE = color(Color.WHITE, false, false).toString(); - - /* - * Bright background colors - */ - @color public static String BG_BRIGHT_RED = color(Color.RED, true, false); - @color public static String BG_BRIGHT_GREEN = color(Color.GREEN, true, false); - @color public static String BG_BRIGHT_BLUE = color(Color.BLUE, true, false); - @color public static String BG_BRIGHT_YELLOW = color(Color.YELLOW, true, false); - @color public static String BG_BRIGHT_CYAN = color(Color.CYAN, true, false); - @color public static String BG_BRIGHT_MAGENTA = color(Color.MAGENTA, true, false); - @color public static String BG_BRIGHT_BLACK = color(Color.BLACK, true, false); - @color public static String BG_BRIGHT_WHITE = color(Color.WHITE, true, false); - - @color public static String BLINKON = special("blinkon"); - @color public static String BLINKOFF = special("blinkoff"); - - @color public static String BOLD = special("bold"); - @color public static String STRIKE = special("strike"); - @color public static String UNDERLINE = special("underline"); - @color public static String ITALIC = special("italic"); - - @color public static String RESET = special("reset"); - - private static Map defaults = new HashMap(); - private static List fields = null; - - private static List fields(){ - if(fields == null){ - fields = new ArrayList(); - for(Field f : TermColors.class.getFields()){ - if(f.getAnnotation(color.class) != null){ - fields.add(f); - try { - defaults.put(f.getName(), (String)f.get(null)); - } catch (IllegalArgumentException ex) { - Logger.getLogger(TermColors.class.getName()).log(Level.SEVERE, null, ex); - } catch (IllegalAccessException ex) { - Logger.getLogger(TermColors.class.getName()).log(Level.SEVERE, null, ex); - } - } - } - } - return fields; - } - + + private TermColors() { + } + + public enum SYS { + + WINDOWS, + UNIX + } + public static final SYS SYSTEM; + + static { + String os = System.getProperty("os.name"); + if(os.contains("Windows")) { + SYSTEM = SYS.WINDOWS; + } else { + SYSTEM = SYS.UNIX; + } + } + + public static void cls() { + if(SYSTEM.equals(SYS.WINDOWS)) { + //Fuck you windows. + for(int i = 0; i < 50; i++) { + StreamUtils.GetSystemOut().println(); + } + } else { + StreamUtils.GetSystemOut().print("\u001b[2J"); + StreamUtils.GetSystemOut().flush(); + } + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + private @interface TermColor { + } + + /* + * Standard foreground colors + */ + @TermColor + public static String RED = color(Color.RED); + @TermColor + public static String GREEN = color(Color.GREEN); + @TermColor + public static String BLUE = color(Color.BLUE); + @TermColor + public static String YELLOW = color(Color.YELLOW); + @TermColor + public static String CYAN = color(Color.CYAN); + @TermColor + public static String MAGENTA = color(Color.MAGENTA); + @TermColor + public static String BLACK = color(Color.BLACK); + @TermColor + public static String WHITE = color(Color.WHITE); + + /* + * Bright foreground colors + */ + @TermColor + public static String BRIGHT_RED = color(Color.RED, true, true, true); + @TermColor + public static String BRIGHT_GREEN = color(Color.GREEN, true, true, true); + @TermColor + public static String BRIGHT_BLUE = color(Color.BLUE, true, true, true); + @TermColor + public static String BRIGHT_YELLOW = color(Color.YELLOW, true, true, true); + @TermColor + public static String BRIGHT_CYAN = color(Color.CYAN, true, true, true); + @TermColor + public static String BRIGHT_MAGENTA = color(Color.MAGENTA, true, true, true); + @TermColor + public static String BRIGHT_BLACK = color(Color.BLACK, true, true, true); + @TermColor + public static String BRIGHT_WHITE = color(Color.WHITE, true, true, true); + + /* + * Standard background colors + */ + @TermColor + public static String BG_RED = color(Color.RED, false, false, false); + @TermColor + public static String BG_GREEN = color(Color.GREEN, false, false, false); + @TermColor + public static String BG_BLUE = color(Color.BLUE, false, false, false); + @TermColor + public static String BG_YELLOW = color(Color.YELLOW, false, false, false); + @TermColor + public static String BG_CYAN = color(Color.CYAN, false, false, false); + @TermColor + public static String BG_MAGENTA = color(Color.MAGENTA, false, false, false); + @TermColor + public static String BG_BLACK = color(Color.BLACK, false, false, false); + @TermColor + public static String BG_WHITE = color(Color.WHITE, false, false, false); + + /* + * Bright background colors + */ + @TermColor + public static String BG_BRIGHT_RED = color(Color.RED, true, false, false); + @TermColor + public static String BG_BRIGHT_GREEN = color(Color.GREEN, true, false, false); + @TermColor + public static String BG_BRIGHT_BLUE = color(Color.BLUE, true, false, false); + @TermColor + public static String BG_BRIGHT_YELLOW = color(Color.YELLOW, true, false, false); + @TermColor + public static String BG_BRIGHT_CYAN = color(Color.CYAN, true, false, false); + @TermColor + public static String BG_BRIGHT_MAGENTA = color(Color.MAGENTA, true, false, false); + @TermColor + public static String BG_BRIGHT_BLACK = color(Color.BLACK, true, false, false); + @TermColor + public static String BG_BRIGHT_WHITE = color(Color.WHITE, true, false, false); + + @TermColor + public static String BLINKON = special("blinkon"); + @TermColor + public static String BLINKOFF = special("blinkoff"); + + @TermColor + public static String BOLD = special("bold"); + @TermColor + public static String STRIKE = special("strike"); + @TermColor + public static String UNDERLINE = special("underline"); + @TermColor + public static String ITALIC = special("italic"); + + @TermColor + public static String RESET = special("reset"); + + private static final Map DEFAULTS = new HashMap(); + private static List fields = null; + + private static List fields() { + if(fields == null) { + fields = new ArrayList(); + for(Field f : TermColors.class.getFields()) { + if(f.getAnnotation(TermColor.class) != null) { + fields.add(f); + try { + DEFAULTS.put(f.getName(), (String) f.get(null)); + } catch (IllegalArgumentException | IllegalAccessException ex) { + Logger.getLogger(TermColors.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + } + return fields; + } + /** * Enables colors. */ - public static void EnableColors(){ - for(Field f : fields()){ - try { - f.set(null, defaults.get(f.getName())); - } catch (IllegalArgumentException ex) { - Logger.getLogger(TermColors.class.getName()).log(Level.SEVERE, null, ex); - } catch (IllegalAccessException ex) { - Logger.getLogger(TermColors.class.getName()).log(Level.SEVERE, null, ex); - } - } - } - + public static void EnableColors() { + for(Field f : fields()) { + try { + f.set(null, DEFAULTS.get(f.getName())); + } catch (IllegalArgumentException | IllegalAccessException ex) { + Logger.getLogger(TermColors.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + /** * Disables colors. */ - public static void DisableColors(){ - for(Field f : fields()){ - try { - f.set(null, ""); - } catch (IllegalArgumentException ex) { - Logger.getLogger(TermColors.class.getName()).log(Level.SEVERE, null, ex); - } catch (IllegalAccessException ex) { - Logger.getLogger(TermColors.class.getName()).log(Level.SEVERE, null, ex); - } - } - } - + public static void DisableColors() { + for(Field f : fields()) { + try { + f.set(null, ""); + } catch (IllegalArgumentException | IllegalAccessException ex) { + Logger.getLogger(TermColors.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + /** * Returns true or false if colors are enabled or not. - * @return + * + * @return */ - public static boolean ColorsDisabled(){ + public static boolean ColorsDisabled() { return RED == null; } - - private static String special(String type){ - if(SYSTEM.equals(SYS.UNIX)){ - if(type.equals("blinkon")){ - return "\033[5m"; - } - if(type.equals("blinkoff")){ - return "\033[25m"; - } - if(type.equals("bold")){ - return "\033[1m"; - } - if(type.equals("strike")){ - return "\033[9m"; - } - if(type.equals("underline")){ - return "\033[4m"; - } - if(type.equals("italic")){ - return "\033[3m"; - } - } - if(type.equals("reset")){ - return "\033[0m"; + + private static String special(String type) { + + // On windows, these effects (except for reset) get printed as a changed background color. + // This is consistent with how broadcast() messages are printed to the console. + switch(type) { + case "blinkon": + return "\033[5m"; + case "blinkoff": + return "\033[25m"; + case "bold": + return "\033[1m"; + case "strike": + return "\033[9m"; + case "underline": + return "\033[4m"; + case "italic": + return "\033[3m"; + case "reset": + return "\033[m"; + default: + return ""; } - return ""; - } - - public static String reset(){ + } + + public static String reset() { return RESET; - } - /** - * Returns the specified color code, foreground, and dark. - * @param c - * @return - */ - public static String color(Color c){ - return color(c, false, true); - } - /** - * This is not the preferred method, however, if you must, you - * can use this function to get the specified colors, given an awt Color. - * Not all colors are supported, and bad colors will just return white. - * @param c - * @return - */ - private static String color(Color c, boolean bright, boolean foreground) { - - int color = 37; - if (c.equals(Color.RED)) { - color = 31; - } else if (c.equals(Color.GREEN)) { - color = 32; - } else if (c.equals(Color.BLUE)) { - color = 34; - } else if (c.equals(Color.YELLOW)) { - color = 33; - } else if (c.equals(Color.CYAN)) { - color = 36; - } else if (c.equals(Color.MAGENTA)) { - color = 35; - } else if (c.equals(Color.BLACK)) { - color = 30; - } else if (c.equals(Color.WHITE)) { - color = 37; - } - if(!foreground){ - color += 10; - } - return "\033[" + (bright?"1;":"") + color + "m"; - } - - public static void p(CharSequence c) { - System.out.print(c); - System.out.flush(); - } - - public static void pl() { - pl(""); - } - - public static String prompt(){ - if(scanner == null){ - scanner = new Scanner(System.in); - } - p(">" + MAGENTA); - System.out.flush(); - String ret = scanner.nextLine(); - p(WHITE); - return ret; - } - - private static Scanner scanner; - public static void pl(CharSequence c) { - System.out.println(c + WHITE); - } - + } + + /** + * Returns the specified color code, foreground, dark and prefixed with an ANSI reset. + * + * @param c + * @return + */ + public static String color(Color c) { + return color(c, false, true, true); + } + + /** + * This is not the preferred method, however, if you must, you can use this function to get the specified colors, + * given an awt Color. Not all colors are supported, and bad colors will just return white. + * + * @param c The color to set. + * @param bright + * @param foreground True to set the color of the foreground, false to set the color of the background. + * @param resetCurrent Resets ANSI modifiers before this ANSI color. + * @return + */ + private static String color(Color c, boolean bright, boolean foreground, boolean resetCurrent) { + + int color = 37; + if(c.equals(Color.RED)) { + color = 31; + } else if(c.equals(Color.GREEN)) { + color = 32; + } else if(c.equals(Color.BLUE)) { + color = 34; + } else if(c.equals(Color.YELLOW)) { + color = 33; + } else if(c.equals(Color.CYAN)) { + color = 36; + } else if(c.equals(Color.MAGENTA)) { + color = 35; + } else if(c.equals(Color.BLACK)) { + color = 30; + } else if(c.equals(Color.WHITE)) { + color = 37; + } + if(!foreground) { + color += 10; + } + // ANSI: 0 = reset, 1 = bright_intensity, 22 = normal_intensity. + return "\033[" + (resetCurrent ? "0;" : "") + color + ";" + (bright ? "1" : "22") + "m"; + } + + public static void p(CharSequence c) { + StreamUtils.GetSystemOut().print(c); + StreamUtils.GetSystemOut().flush(); + } + + private static Scanner scanner; + + public static String prompt() { + if(scanner == null) { + scanner = new Scanner(System.in); + } + p(">" + MAGENTA); + StreamUtils.GetSystemOut().flush(); + String ret = scanner.nextLine(); + p(WHITE); + return ret; + } + + public static void pl(CharSequence c) { + StreamUtils.GetSystemOut().println(c + WHITE); + } + + public static void pl() { + pl(""); + } + /** * THIS BLOCK MUST REMAIN AT THE BOTTOM */ - static{ - if(SYSTEM == SYS.WINDOWS){ + static { + if(SYSTEM == SYS.WINDOWS) { DisableColors(); } else { EnableColors(); diff --git a/src/main/java/com/laytonsmith/PureUtilities/ThreadPump.java b/src/main/java/com/laytonsmith/PureUtilities/ThreadPump.java index 1104363dd4..f10e0e5792 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/ThreadPump.java +++ b/src/main/java/com/laytonsmith/PureUtilities/ThreadPump.java @@ -5,43 +5,42 @@ import java.util.concurrent.Callable; /** - * This class provides a framework of simple hooks to run critical code on the main thread - * for code that is running off the main thread. - * - * The main operations for code that uses this class are invokeNow and invokeLater, and - * the main operations for code that implements this class are the abstract methods, - * and the configuration methods. - * - * + * This class provides a framework of simple hooks to run critical code on the main thread for code that is running off + * the main thread. + * + * The main operations for code that uses this class are invokeNow and invokeLater, and the main operations for code + * that implements this class are the abstract methods, and the configuration methods. + * + * */ public abstract class ThreadPump { - - private int minHoldTime; - private int maxHoldTime; - private int waitTime; - private Queue eventPump; + + private final int minHoldTime; + private final int maxHoldTime; + private final int waitTime; + private final Queue eventPump; private int startStack; private boolean pumpStarted; private final String threadName; - + private final Object waitForStopLock; private final Object waitForTaskLock; - + private long lockTime; private long idleTime; - + /** * Creates a new ThreadPump object. All times are in milliseconds. - * @param minHoldTime The minimum amount of time to wait for the next operation - * before giving up control of the main thread again. This should be set to 0 if - * there is no or negligible penalty for regaining the main thread once given - * up. - * @param maxHoldTime The max time that the main thread should be monopolized - * before it is given up. This is useful to prevent starvation. - * @param waitTime The amount of time to wait before resuming if the maxHoldTime - * was met, and thread control was returned. + * + * @param minHoldTime The minimum amount of time to wait for the next operation before giving up control of the main + * thread again. This should be set to 0 if there is no or negligible penalty for regaining the main thread once + * given up. + * @param maxHoldTime The max time that the main thread should be monopolized before it is given up. This is useful + * to prevent starvation. + * @param waitTime The amount of time to wait before resuming if the maxHoldTime was met, and thread control was + * returned. */ - protected ThreadPump(int minHoldTime, int maxHoldTime, int waitTime, String threadName){ + protected ThreadPump(int minHoldTime, int maxHoldTime, int waitTime, String threadName) { eventPump = new LinkedList(); startStack = 0; waitForStopLock = new Object(); @@ -52,62 +51,57 @@ protected ThreadPump(int minHoldTime, int maxHoldTime, int waitTime, String thre this.waitTime = waitTime; this.threadName = threadName; } - + /** - * Starts a transaction. This resets all the mechanisms for wait times. - * For each start() call, there must be exactly one stop() call, however, - * they may be called multiple times, and only when the last stop() is called - * will it actually take effect. + * Starts a transaction. This resets all the mechanisms for wait times. For each start() call, there must be exactly + * one stop() call, however, they may be called multiple times, and only when the last stop() is called will it + * actually take effect. */ - public synchronized void start(){ + public synchronized void start() { startStack++; } - + /** - * Stops a transaction, and immediately returns control to the main thread, if - * this is the last stop() method to be called. + * Stops a transaction, and immediately returns control to the main thread, if this is the last stop() method to be + * called. */ - public synchronized void stop(){ + public synchronized void stop() { startStack--; - if(startStack < 0){ + if(startStack < 0) { throw new RuntimeException("stop() called too many times!"); } } - + /** - * Stops a transaction, but waits for all tasks to complete before returning. - * This should ONLY be called if this is the top level stop, and an exception will - * be thrown if the start stack is not 1. + * Stops a transaction, but waits for all tasks to complete before returning. This should ONLY be called if this is + * the top level stop, and an exception will be thrown if the start stack is not 1. */ public void waitForStop() throws InterruptedException { - if(startStack != 1){ + if(startStack != 1) { throw new RuntimeException("waitForStop called from an inner invocation"); } - synchronized(waitForStopLock){ + synchronized(waitForStopLock) { waitForStopLock.wait(); } } - - - + /** - * Runs a task on the main thread at some point. This returns - * immediately. Tasks queued up will be run in order, but at some - * indeterminate time in the future. - * @param runnable + * Runs a task on the main thread at some point. This returns immediately. Tasks queued up will be run in order, but + * at some indeterminate time in the future. + * + * @param runnable */ - public void invokeLater(Runnable runnable){ + public void invokeLater(Runnable runnable) { eventPump.add(runnable); startPump(); } - + /** - * Runs a task on the main thread and waits for it to complete. Tasks - * submitted are queued up in order, so the task may not get run immediately, - * and the task completion time is still dependant on the thread starvation - * parameters. + * Runs a task on the main thread and waits for it to complete. Tasks submitted are queued up in order, so the task + * may not get run immediately, and the task completion time is still dependant on the thread starvation parameters. + * * @param callable - * @return + * @return */ public Object invokeNow(final Callable callable) throws InterruptedException { final Object myLock = new Object(); @@ -116,43 +110,43 @@ public Object invokeNow(final Callable callable) throws InterruptedException @Override public void run() { - try{ + try { ret[0] = callable.call(); - synchronized(myLock){ + synchronized(myLock) { myLock.notifyAll(); } - } catch(Exception e){ + } catch (Exception e) { throw new RuntimeException(e); } } }); - synchronized(myLock){ + synchronized(myLock) { myLock.wait(); } return ret[0]; } - - private void startPump(){ - if(!pumpStarted){ - synchronized(this){ + + private void startPump() { + if(!pumpStarted) { + synchronized(this) { lockTime = System.currentTimeMillis(); pumpStarted = true; - new Thread(new Runnable(){ + new Thread(new Runnable() { @Override - public void run(){ + public void run() { doPump(); } }, threadName).start(); } } - synchronized(waitForTaskLock){ + synchronized(waitForTaskLock) { waitForTaskLock.notifyAll(); } } - + @SuppressWarnings("SleepWhileInLoop") - private void doPump(){ - while(pumpStarted){ + private void doPump() { + while(pumpStarted) { runOnMainThread(new Runnable() { @SuppressWarnings("NestedSynchronizedStatement") @@ -160,24 +154,24 @@ private void doPump(){ public void run() { //This condition happens when we are done with the thread //AND there are no pending events. - if(eventPump.isEmpty() && startStack == 0){ - synchronized(ThreadPump.this){ + if(eventPump.isEmpty() && startStack == 0) { + synchronized(ThreadPump.this) { pumpStarted = false; return; } } //This condition happens when we need to wait - if(eventPump.isEmpty() && startStack > 0){ + if(eventPump.isEmpty() && startStack > 0) { long now = System.currentTimeMillis(); //Our wait time is the maximum time left before we reach //the min hold time. long waitTime = lockTime + minHoldTime - now; - synchronized(waitForTaskLock){ + synchronized(waitForTaskLock) { try { waitForTaskLock.wait(waitTime); //Well, no tasks were given, so let's return control //until further notice. - synchronized(ThreadPump.this){ + synchronized(ThreadPump.this) { pumpStarted = false; return; } @@ -187,22 +181,22 @@ public void run() { } } } - if(!eventPump.isEmpty()){ + if(!eventPump.isEmpty()) { Runnable task = eventPump.poll(); - if(task != null){ + if(task != null) { task.run(); } //Now check against the max time. If this is greater than that, let's //return control to the main thread for waitTime. long now = System.currentTimeMillis(); - if(now > lockTime + maxHoldTime){ + if(now > lockTime + maxHoldTime) { idleTime = waitTime; return; } } } }); - if(idleTime > 0){ + if(idleTime > 0) { try { Thread.sleep(idleTime); idleTime = 0; @@ -212,12 +206,13 @@ public void run() { } } } - + /** - * Runs a task on the main thread, immediately. This task will be an - * encapsulation of the "blocking calls" that this class provides. - * @param r + * Runs a task on the main thread, immediately. This task will be an encapsulation of the "blocking calls" that this + * class provides. + * + * @param r */ protected abstract void runOnMainThread(Runnable r); - + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/Triplet.java b/src/main/java/com/laytonsmith/PureUtilities/Triplet.java new file mode 100644 index 0000000000..d436d52735 --- /dev/null +++ b/src/main/java/com/laytonsmith/PureUtilities/Triplet.java @@ -0,0 +1,78 @@ +package com.laytonsmith.PureUtilities; + +import java.util.Objects; + +/** + * Creates an object triplet. The hashcode and equals functions have been overridden to use the underlying object's hash + * code and equals combined. The underlying objects may be null. + * + * @param The first object's type + * @param The second object's type + * @param The third object's type + */ +public class Triplet { + + private final A fst; + private final B snd; + private final C trd; + + /** + * Creates a new Triplet with the specified values. + * + * @param a + * @param b + * @param c + */ + public Triplet(A a, B b, C c) { + fst = a; + snd = b; + trd = c; + } + + @Override + public String toString() { + return "<" + Objects.toString(fst) + ", " + Objects.toString(snd) + ", " + Objects.toString(trd) + ">"; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 47 * hash + Objects.hashCode(this.fst); + hash = 47 * hash + Objects.hashCode(this.snd); + hash = 47 * hash + Objects.hashCode(this.trd); + return hash; + } + + @Override + public boolean equals(Object obj) { + if(obj == null) { + return false; + } + if(getClass() != obj.getClass()) { + return false; + } + final Triplet other = (Triplet) obj; + if(!Objects.equals(this.fst, other.fst)) { + return false; + } + if(!Objects.equals(this.snd, other.snd)) { + return false; + } + if(!Objects.equals(this.trd, other.trd)) { + return false; + } + return true; + } + + public A getFirst() { + return fst; + } + + public B getSecond() { + return snd; + } + + public C getThird() { + return trd; + } +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/UI/TextDialog.java b/src/main/java/com/laytonsmith/PureUtilities/UI/TextDialog.java index 5e8d04d8cd..30f7f19a4f 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/UI/TextDialog.java +++ b/src/main/java/com/laytonsmith/PureUtilities/UI/TextDialog.java @@ -22,20 +22,18 @@ import javax.swing.text.html.HTMLEditorKit; /** - * A TextDialog is a JDialog that supports basic HTML text formatting. It comes - * with two main UI components, a JEditorPane that has been configured to support - * html text, and an "Ok" button which closes the dialog. The text supports some - * features that the JEditorPane doesn't normally support, namely link handling. - * URLs to an external site will work as expected, opening the user's web browser. - * Internal links are also supported, for scrolling to sections with a specific id. - * Those links should look like <a href="#section1">Link</a>, - * and some element elsewhere should have the id "section1". + * A TextDialog is a JDialog that supports basic HTML text formatting. It comes with two main UI components, a + * JEditorPane that has been configured to support html text, and an "Ok" button which closes the dialog. The text + * supports some features that the JEditorPane doesn't normally support, namely link handling. URLs to an external site + * will work as expected, opening the user's web browser. Internal links are also supported, for scrolling to sections + * with a specific id. Those links should look like <a href="#section1">Link</a>, and some + * element elsewhere should have the id "section1". */ public class TextDialog extends javax.swing.JDialog { /** - * Creates new TextDialog. The dialog box provides a window to show simple - * stylized text. + * Creates new TextDialog. The dialog box provides a window to show simple stylized text. + * * @param parent The parent window * @param modal Whether or not this is modal * @param text The text to show in the box. This should be html text. @@ -53,7 +51,7 @@ public void actionPerformed(ActionEvent e) { }); inputDialog.setContentType("text/html"); inputDialog.setEditable(false); - HTMLDocument doc = (HTMLDocument)inputDialog.getDocument(); + HTMLDocument doc = (HTMLDocument) inputDialog.getDocument(); HTMLEditorKit editorKit = (HTMLEditorKit) inputDialog.getEditorKit(); try { editorKit.insertHTML(doc, doc.getLength(), "" + text + "", 0, 0, null); @@ -65,26 +63,26 @@ public void actionPerformed(ActionEvent e) { @Override public void hyperlinkUpdate(HyperlinkEvent e) { - if(e.getEventType() == HyperlinkEvent.EventType.ACTIVATED){ + if(e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { URL url = e.getURL(); - if(url == null){ + if(url == null) { // This is an internal link. The url will be null if the url // is "invalid", which also includes anchor links. In order // to work around this, we have to get messy and use some // reflection stuff to grab what we actually need. - HTMLDocument doc = (HTMLDocument)inputDialog.getDocument(); + HTMLDocument doc = (HTMLDocument) inputDialog.getDocument(); Element clicked = e.getSourceElement(); Enumeration enu = clicked.getAttributes().getAttributeNames(); Object[] attr = (Object[]) ReflectionUtils.get(enu.getClass(), enu, "attr"); String link = null; - for(Object item : attr){ - if(item instanceof SimpleAttributeSet){ - SimpleAttributeSet tag = (SimpleAttributeSet)item; + for(Object item : attr) { + if(item instanceof SimpleAttributeSet) { + SimpleAttributeSet tag = (SimpleAttributeSet) item; @SuppressWarnings("UseOfObsoleteCollectionType") Hashtable table = (Hashtable) ReflectionUtils.get(tag.getClass(), tag, "table"); - for(Object key : table.keySet()){ - if(key instanceof HTML.Attribute){ - if("href".equals(((HTML.Attribute)key).toString())){ + for(Object key : table.keySet()) { + if(key instanceof HTML.Attribute) { + if("href".equals(((HTML.Attribute) key).toString())) { link = (String) table.get(key); break; } @@ -93,10 +91,10 @@ public void hyperlinkUpdate(HyperlinkEvent e) { break; } } - if(link != null){ + if(link != null) { String id = link.substring(1); Element elem = doc.getElement(id); - if(elem != null){ + if(elem != null) { inputDialog.setCaretPosition(elem.getStartOffset()); inputDialog.scrollToReference(id); } @@ -116,76 +114,76 @@ public void hyperlinkUpdate(HyperlinkEvent e) { /** * Sets the OK button text. + * * @param text */ - public void setOKButtonText(String text){ + public void setOKButtonText(String text) { okButton.setText(text); } /** - * This method is called from within the constructor to initialize the form. - * WARNING: Do NOT modify this code. The content of this method is always - * regenerated by the Form Editor. + * This method is called from within the constructor to initialize the form. WARNING: Do NOT modify this code. The + * content of this method is always regenerated by the Form Editor. */ @SuppressWarnings("unchecked") - // //GEN-BEGIN:initComponents - private void initComponents() { - - jPanel1 = new javax.swing.JPanel(); - okButton = new javax.swing.JButton(); - jScrollPane2 = new javax.swing.JScrollPane(); - inputDialog = new javax.swing.JEditorPane(); - - setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); - - okButton.setText("Ok"); - okButton.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); - okButton.setSelected(true); - - jScrollPane2.setAutoscrolls(true); - - inputDialog.setBorder(javax.swing.BorderFactory.createBevelBorder(javax.swing.border.BevelBorder.RAISED)); - inputDialog.setMinimumSize(new java.awt.Dimension(20, 10)); - jScrollPane2.setViewportView(inputDialog); - - javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); - jPanel1.setLayout(jPanel1Layout); - jPanel1Layout.setHorizontalGroup( - jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(jPanel1Layout.createSequentialGroup() - .addContainerGap() - .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 376, Short.MAX_VALUE) - .addContainerGap()) - .addGroup(jPanel1Layout.createSequentialGroup() - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(okButton) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - ); - jPanel1Layout.setVerticalGroup( - jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() - .addContainerGap() - .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 246, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(okButton) - .addContainerGap()) - ); - - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); - getContentPane().setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - ); - - pack(); - }// //GEN-END:initComponents - - public static void main(String[] args){ + // //GEN-BEGIN:initComponents + private void initComponents() { + + jPanel1 = new javax.swing.JPanel(); + okButton = new javax.swing.JButton(); + jScrollPane2 = new javax.swing.JScrollPane(); + inputDialog = new javax.swing.JEditorPane(); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + + okButton.setText("Ok"); + okButton.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); + okButton.setSelected(true); + + jScrollPane2.setAutoscrolls(true); + + inputDialog.setBorder(javax.swing.BorderFactory.createBevelBorder(javax.swing.border.BevelBorder.RAISED)); + inputDialog.setMinimumSize(new java.awt.Dimension(20, 10)); + jScrollPane2.setViewportView(inputDialog); + + javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); + jPanel1.setLayout(jPanel1Layout); + jPanel1Layout.setHorizontalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 376, Short.MAX_VALUE) + .addContainerGap()) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(okButton) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + jPanel1Layout.setVerticalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 246, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(okButton) + .addContainerGap()) + ); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + + pack(); + } // //GEN-END:initComponents + + public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override @@ -197,10 +195,10 @@ public void run() { }); } - // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JEditorPane inputDialog; - private javax.swing.JPanel jPanel1; - private javax.swing.JScrollPane jScrollPane2; - private javax.swing.JButton okButton; - // End of variables declaration//GEN-END:variables + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JEditorPane inputDialog; + private javax.swing.JPanel jPanel1; + private javax.swing.JScrollPane jScrollPane2; + private javax.swing.JButton okButton; + // End of variables declaration//GEN-END:variables } diff --git a/src/main/java/com/laytonsmith/PureUtilities/URIUtils.java b/src/main/java/com/laytonsmith/PureUtilities/URIUtils.java new file mode 100644 index 0000000000..72e3fa34e4 --- /dev/null +++ b/src/main/java/com/laytonsmith/PureUtilities/URIUtils.java @@ -0,0 +1,42 @@ +package com.laytonsmith.PureUtilities; + +import java.net.URI; +import java.net.URISyntaxException; + +/** + * Provides helper methods related to URIs. + */ +public final class URIUtils { + private URIUtils() {} + + /** + * Canonicalizes a URI. The URI {@code file:/file.txt} and {@code file:///file.txt} point to the same + * resource, but are neither toString.equal, or URI.equal. This method canonicalizes the second format + * into the first, as well as calling normalize on the URI. + * @param uri + * @return + */ + public static URI canonicalize(URI uri) { + if(uri.getScheme().equals("untitled")) { + return uri; + } + uri = uri.normalize(); + URI newURI; + try { + newURI = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(), + uri.getQuery(), uri.getFragment()); + } catch(URISyntaxException ex) { + throw new Error(ex); + } + return newURI; + } + + /** + * Creates and canonicalizes a URI from a String. See {@link #canonicalize}. + * @param uri + * @return + */ + public static URI canonicalize(String uri) { + return canonicalize(URI.create(uri)); + } +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/Vector3D.java b/src/main/java/com/laytonsmith/PureUtilities/Vector3D.java new file mode 100644 index 0000000000..3cd2e81eda --- /dev/null +++ b/src/main/java/com/laytonsmith/PureUtilities/Vector3D.java @@ -0,0 +1,156 @@ +package com.laytonsmith.PureUtilities; + +/** + * Represents both a point in 3D space and a vector representing a direction and magnitude. + */ +public class Vector3D extends Point3D { + + /** + * A Vector3D with x, y and z initialized at 0. + */ + public static final Vector3D ZERO = new Vector3D(0, 0, 0); + + /** + * Copy constructor. + * + * @param other the other point + */ + public Vector3D(Point3D other) { + super(other); + } + + /** + * Initializes the X and Y values. Z is initialized to 0. + */ + public Vector3D(double x, double y) { + super(x, y, 0); + } + + /** + * Initializes the X, Y, and Z values. + * + * @param x the x value + * @param y the y value + * @param z the z value + */ + public Vector3D(double x, double y, double z) { + super(x, y, z); + } + + /** + * Returns a new vector of this vector added to another. While functionally similar to translate(), + * this method will return a Vector3D whereas translate() returns a Point3D. + * + * @param other the other vector + * @return a new vector + */ + public Vector3D add(Vector3D other) { + return new Vector3D(x + other.x, y + other.y, z + other.z); + } + + /** + * Returns a new vector of this vector multiplied by another. + * + * @param other the other vector + * @return a new vector + */ + public Vector3D subtract(Vector3D other) { + return new Vector3D(x - other.x, y - other.y, z - other.z); + } + + /** + * Returns a new vector of this vector multiplied by another. + * + * @param other the other vector + * @return a new vector + */ + public Vector3D multiply(Vector3D other) { + return new Vector3D(x * other.x, y * other.y, z * other.z); + } + + /** + * Returns a new vector of this vector multiplied by a value. + * + * @param m the value to multiply by + * @return a new vector + */ + public Vector3D multiply(double m) { + return new Vector3D(x * m, y * m, z * m); + } + + /** + * Returns a new vector of this vector divided by another. + * + * @param other the other vector + * @return a new vector + */ + public Vector3D divide(Vector3D other) { + return new Vector3D(x / other.x, y / other.y, z / other.z); + } + + /** + * Returns a new vector of this vector divided by a value. + * + * @param m the value to divide by + * @return a new vector + */ + public Vector3D divide(double m) { + return new Vector3D(x / m, y / m, z / m); + } + + /** + * Returns a new vector that is a normalized version of this vector. The new vector will have the same direction, + * but a magnitude of one. + * + * @return a new vector + */ + public Vector3D normalize() { + double length = length(); + if(length == 0.0) { + return Vector3D.ZERO; + } + return new Vector3D(x / length, y / length, z / length); + } + + /** + * Gets the dot product of this vector. + * + * @param vec the other vector + * @return the dot product of this vector + */ + public double dot(Vector3D vec) { + return x * vec.x + y * vec.y + z * vec.z; + } + + /** + * Gets the cross product of this vector. + * + * @param vec the other vector + * @return the cross product of this vector + */ + public Vector3D cross(Vector3D vec) { + return new Vector3D( + y * vec.z - z * vec.y, + z * vec.x - x * vec.z, + x * vec.y - y * vec.x + ); + } + + /** + * Gets the magnitude (length) squared of this vector. + * + * @return the magnitude squared + */ + public double lengthSquared() { + return x * x + y * y + z * z; + } + + /** + * Gets the magnitude (length) of this vector. + * + * @return the magnitude + */ + public double length() { + return Math.sqrt(lengthSquared()); + } +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/Version.java b/src/main/java/com/laytonsmith/PureUtilities/Version.java index adb2670302..efaa41f74e 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Version.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Version.java @@ -1,73 +1,80 @@ package com.laytonsmith.PureUtilities; /** - * An interface that Version backings must implement. Versions may be implemented as an - * enum, instead of using strings in code, and so this must be a separate interface. See - * {@link SimpleVersion} for a string based implementation. + * An interface that Version backings must implement. Versions may be implemented as an enum, instead of using strings + * in code, and so this must be a separate interface. See {@link SimpleVersion} for a string based implementation. */ public interface Version { /** - * Returns true if the major, minor, and supplemental versions are all - * equal. + * Returns true if the major, minor, and supplemental versions are all equal. + * * @param obj - * @return + * @return */ @Override boolean equals(Object obj); /** * Returns the major version. + * * @return */ int getMajor(); /** * Returns the minor version. + * * @return */ int getMinor(); /** - * Returns the supplemental version. + * Returns the supplemental version, aka, the patch version. + * * @return */ int getSupplemental(); /** - * Returns a toString representation of this Version. Usually dots are used - * to separate the major, minor, and supplemental versions. - * @return + * Returns a toString representation of this Version. Usually dots are used to separate the major, minor, and + * supplemental versions. + * + * @return */ @Override String toString(); - + /** * Returns true if this version is less than the other version. + * * @param other - * @return + * @return */ boolean lt(Version other); - + /** * Returns true if this version is less than or equal to the other version. + * * @param other - * @return + * @return */ boolean lte(Version other); - + /** * Returns true if this version is greater than the other version. + * * @param other - * @return + * @return */ boolean gt(Version other); - + /** * Returns true if this version is greater than or equal to the other version. + * * @param other - * @return + * @return */ boolean gte(Version other); - + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/FileSystemLayer.java b/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/FileSystemLayer.java index 94718786c4..9f852a2ce8 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/FileSystemLayer.java +++ b/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/FileSystemLayer.java @@ -1,5 +1,6 @@ package com.laytonsmith.PureUtilities.VirtualFS; +import com.laytonsmith.PureUtilities.Common.Annotations.ForceImplementation; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.ElementType; @@ -9,65 +10,144 @@ /** * A file system layer is a layer between a VirtualFile and the real file - * system. This allows for non-traditional file systems to be transparently added - * to the VFS system. These layers are specified by symlinks in the VFS, which - * use a specific URI to denote the path. Once a FSL is implemented, - * it is trivial for a user to add a new symlink to make use of the new FSL. - * All functions may throw an IOException, which is not something real File objects - * normally do (for instance, delete() will simply return false) but to give the user - * more information, this class throws exceptions instead. - * + * system. This allows for non-traditional file systems to be transparently + * added to the VFS system. These layers are specified by symlinks in the VFS, + * which use a specific URI to denote the path. Once a FSL is implemented, it is + * trivial for a user to add a new symlink to make use of the new FSL. All + * functions may throw an IOException, which is not something real File objects + * normally do (for instance, delete() will simply return false) but to give the + * user more information, this class throws exceptions instead. + * */ public abstract class FileSystemLayer { - + protected final VirtualFile path; protected final VirtualFileSystem fileSystem; - protected FileSystemLayer(VirtualFile path, VirtualFileSystem fileSystem){ + + @ForceImplementation + protected FileSystemLayer(VirtualFile path, VirtualFileSystem fileSystem, String symlink) { this.path = path; this.fileSystem = fileSystem; } + /** + * Returns an input stream to the underlying resource. + * @return + * @throws IOException If the file cannot be read + */ public abstract InputStream getInputStream() throws IOException; + /** + * Given the byte array, writes it to the underlying resource. + * @param bytes + * @throws IOException If the file cannot be written, for instance, the underlying resource is not available, or + * the file is read only. + */ public abstract void writeByteArray(byte[] bytes) throws IOException; + /** + * Returns a list of files in this directory. If this is not a directory, this should throw an exception. + * @return + * @throws IOException If this file is not a directory, or the user does not have permission to list the + * files. + */ public abstract VirtualFile[] listFiles() throws IOException; + /** + * Deletes the file immediately. + * @throws IOException If the file could not in fact be deleted, either because of permissions issues, or because + * the file does not exist. + */ public abstract void delete() throws IOException; /** - * This may work the exact same as delete in some cases, but otherwise, - * the file will be deleted upon exit of the virtual machine. - * @throws IOException + * This may work the exact same as delete in some cases, but otherwise, the + * file will be deleted upon exit of the virtual machine. This method will not necessarily throw an exception if the + * operation should succeed, but doesn't when the deletion actually is attempted. However, in cases where the + * attempt will definitely never succeed, this should throw an exception. + * + * @throws IOException If the file cannot under any circumstance be deleted, for instance, if the file does not + * exist, or the user does not have permission, or the underlying resource is not available. */ - public abstract void deleteOnExit() throws IOException; + public abstract void deleteEventually() throws IOException; + /** + * Returns true if this file exists, false otherwise. + * @return + * @throws IOException If the underlying resource is not available, or the user does not have permission to check + * existence of a file. + */ public abstract boolean exists() throws IOException; + /** + * Returns true if the user can read this file. + * @return True if the file exists, and the user can read it. False if the file does exist, but the user cannot + * read it. + * @throws IOException If the file does not exist, the underlying resource is not available, or the user does + * not have permission to check if this file can be read. If the user has permission to see the existence of the + * file, but simply is not allowed to read it, then this will not throw an exception, but return false instead. + */ public abstract boolean canRead() throws IOException; + /** + * Returns true if the user can write to this file. + * @return True if the file exists, and the user can read it. False if the file does exist, but the user cannot + * write to it. + * @throws IOException If the file does not exist, the underlying resource is not available, or the user does + * not have permission to check if this can be written to. If the user has permission to see the existence of the + * file, but simply is not allowed to write to it, then this will not throw an exception, but return false instead. + */ public abstract boolean canWrite() throws IOException; + /** + * Returns true if this is a directory. In some cases, on some platforms, this is not guaranteed to return the + * opposite of {@link #isFile()}. + * @return True if this is a directory, false otherwise. + * @throws IOException If the path does not exist, the underlying resource is not available, or the user does + * not have permission to check if this is a directory. + */ public abstract boolean isDirectory() throws IOException; + /** + * Returns true if this is a file. In some cases, on some platforms, this is not guaranteed to return the + * opposite of {@link #isDirectory()}. + * @return True if this is a file, false otherwise. + * @throws IOException If the path does not exist, the underlying resource is not available, or the user does + * not have permission to check if this is a file. + */ public abstract boolean isFile() throws IOException; + /** + * Creates the specified directory, and any parent directories necessary. Directories that already exist will not be + * touched, and it is not an error to call this on an already existing directory. + * @throws IOException If the directory could not be created. Note that even in the case where the call fails, it + * may be that some of the parent directories were created. This will also throw an exception if the path specified + * already exists, but it is not a directory. It will also be thrown if the underlying resource is not available. + */ public abstract void mkdirs() throws IOException; + /** + * Creates a new file, if the path does not point to an existing file. + * @throws IOException If the path already exists, or the file could otherwise not be created, or if the underlying + * resource is not available. + */ public abstract void createNewFile() throws IOException; - + /** * Used to denote a FileSystemLayer protocol */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) + @SuppressWarnings("checkstyle:typename") // Fixing this violation might break dependents. public static @interface fslayer { + /** - * The protocol identifier, for instance, "file", which would - * map to a file://uri type uri. - * @return + * The protocol identifier, for instance, "file", which would map to a + * file://uri type uri. + * + * @return */ String value(); } - + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/InvalidVirtualFile.java b/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/InvalidVirtualFile.java index 156fab8d44..53cf7b923d 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/InvalidVirtualFile.java +++ b/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/InvalidVirtualFile.java @@ -1,9 +1,8 @@ package com.laytonsmith.PureUtilities.VirtualFS; /** - * Thrown to indicate that the path given to the VirtualFile - * contains restricted characters. - * + * Thrown to indicate that the path given to the VirtualFile contains restricted characters. + * */ public class InvalidVirtualFile extends RuntimeException { @@ -18,5 +17,5 @@ public InvalidVirtualFile(Throwable cause) { public InvalidVirtualFile(String message, Throwable cause) { super(message, cause); } - + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/PermissionException.java b/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/PermissionException.java index a3a4e92029..56aa1f66b5 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/PermissionException.java +++ b/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/PermissionException.java @@ -2,7 +2,7 @@ /** * Thrown if a function failed due to a permissions issue - * + * */ public class PermissionException extends SecurityException { @@ -17,5 +17,5 @@ public PermissionException(Throwable cause) { public PermissionException(String message, Throwable cause) { super(message, cause); } - + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/RealFileSystemLayer.java b/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/RealFileSystemLayer.java index e1e43eeea4..0efc762608 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/RealFileSystemLayer.java +++ b/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/RealFileSystemLayer.java @@ -12,7 +12,7 @@ /** * - * + * */ @FileSystemLayer.fslayer("file") public class RealFileSystemLayer extends FileSystemLayer { @@ -20,20 +20,20 @@ public class RealFileSystemLayer extends FileSystemLayer { protected final File real; public RealFileSystemLayer(VirtualFile path, VirtualFileSystem fileSystem, String symlink) throws IOException { - super(path, fileSystem); - if (symlink == null) { + super(path, fileSystem, symlink); + if(symlink == null) { real = new File(fileSystem.root, path.getPath()); - if (!real.getCanonicalPath().startsWith(fileSystem.root.getCanonicalPath())) { + if(!real.getCanonicalPath().startsWith(fileSystem.root.getCanonicalPath())) { throw new PermissionException(path.getPath() + " extends above the root directory of this file system, and does not point to a valid file."); } } else { File symlinkRoot = new File(fileSystem.symlinkFile, symlink); real = new File(symlinkRoot, path.getPath()); //If the path extends above the symlink, disallow it - if (!real.getCanonicalPath().startsWith(symlinkRoot.getCanonicalPath())) { + if(!real.getCanonicalPath().startsWith(symlinkRoot.getCanonicalPath())) { //Unless of course, the path is still within the full real path, then //eh, we'll allow it. - if (!real.getCanonicalPath().startsWith(fileSystem.root.getCanonicalPath())) { + if(!real.getCanonicalPath().startsWith(fileSystem.root.getCanonicalPath())) { throw new PermissionException(path.getPath() + " extends above the root directory of this file system, and does not point to a valid file."); } } @@ -53,7 +53,7 @@ public void writeByteArray(byte[] bytes) throws IOException { @Override public VirtualFile[] listFiles() throws IOException { List virtuals = new ArrayList(); - for (File sub : real.listFiles()) { + for(File sub : real.listFiles()) { virtuals.add(normalize(sub)); } return virtuals.toArray(new VirtualFile[virtuals.size()]); @@ -61,8 +61,8 @@ public VirtualFile[] listFiles() throws IOException { private VirtualFile normalize(File real) throws IOException { String path = real.getCanonicalPath().replaceFirst(Pattern.quote(fileSystem.root.getCanonicalPath()), ""); - path = path.replace("\\", "/"); - if (!path.startsWith("/")) { + path = path.replace('\\', '/'); + if(!path.startsWith("/")) { path = "/" + path; } return new VirtualFile(path); @@ -70,13 +70,19 @@ private VirtualFile normalize(File real) throws IOException { @Override public void delete() throws IOException { - if (!real.delete()) { + if(!exists()) { + throw new IOException("File does not exist"); + } + if(!real.delete()) { throw new IOException("Could not delete the file"); } } @Override - public void deleteOnExit() { + public void deleteEventually() throws IOException { + if(!exists()) { + throw new IOException("File does not exist"); + } real.deleteOnExit(); } @@ -86,36 +92,54 @@ public boolean exists() { } @Override - public boolean canRead() { + public boolean canRead() throws IOException { + if(!exists()) { + throw new IOException("File does not exist"); + } return real.canRead(); } @Override - public boolean canWrite() { + public boolean canWrite() throws IOException { + if(!exists()) { + throw new IOException("File does not exist"); + } return real.canWrite(); } @Override - public boolean isDirectory() { + public boolean isDirectory() throws IOException { + if(!exists()) { + throw new IOException("File does not exist"); + } return real.isDirectory(); } @Override - public boolean isFile() { + public boolean isFile() throws IOException { + if(!exists()) { + throw new IOException("File does not exist"); + } return real.isFile(); } @Override public void mkdirs() throws IOException { - if (!real.mkdirs()) { + if(exists() && !isDirectory()) { + throw new IOException("The specified path already exists, and is not a directory"); + } + if(!real.mkdirs()) { throw new IOException("Directory structure could not be created"); } } @Override public void createNewFile() throws IOException { - if (!real.createNewFile()) { - throw new IOException("File already exists!"); + if(exists()) { + throw new IOException("File already exists"); + } + if(!real.createNewFile()) { + throw new IOException("File could not be created"); } } } diff --git a/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/SystemVirtualFileSystemManifest.java b/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/SystemVirtualFileSystemManifest.java new file mode 100644 index 0000000000..3032fa6f9b --- /dev/null +++ b/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/SystemVirtualFileSystemManifest.java @@ -0,0 +1,128 @@ +package com.laytonsmith.PureUtilities.VirtualFS; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.nio.file.StandardWatchEventKinds; +import java.nio.file.WatchEvent; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This class represents the underlying manifest file. Generally, it should only be necessary to use this version, + * but it implements an interface which should be used throughout the code to allow for easier testing. + */ +public final class SystemVirtualFileSystemManifest implements VirtualFileSystemManifest { + + private static final Map INSTANCE = new HashMap<>(); + + /** + * There should only be one accessor for the manifest file in the system, but for test purposes, it may be + * useful to mock the interface. This method is used to get the instance for each manifest file (of which + * there will most likely ever only be one). A file listener will be added to the underlying file so that + * changes from other processes can be reflected here as well, but the file can be manually refreshed as well. + * @param manifestFile The underlying manifest file. + * @return The VirtualFileSystemManifest wrapping the given manifestFile + * @throws IOException If there is some error when loading the manifest + */ + public static VirtualFileSystemManifest getInstance(File manifestFile) throws IOException { + if(!INSTANCE.containsKey(manifestFile)) { + INSTANCE.put(manifestFile, new SystemVirtualFileSystemManifest(manifestFile)); + } + return INSTANCE.get(manifestFile); + } + + private final Set manifest; + private final File manifestFile; + + private final WatchService watcher = FileSystems.getDefault().newWatchService(); + + private SystemVirtualFileSystemManifest(File manifestFile) throws FileNotFoundException, IOException { + this.manifestFile = manifestFile; + manifest = Collections.synchronizedSet(new TreeSet<>()); + if(!manifestFile.exists()) { + save(); + } else { + read(manifestFile, manifest); + } + Path p = manifestFile.getParentFile().toPath(); + final WatchKey watchKey = p.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY); + new Thread(() -> { + while(true) { + WatchKey key; + try { + key = watcher.take(); + } catch (InterruptedException ex) { + return; + } + for(WatchEvent event : key.pollEvents()) { + if(event.kind().equals(StandardWatchEventKinds.ENTRY_MODIFY)) { + WatchEvent ev = (WatchEvent) event; + Path path = ev.context(); + if(path.equals(manifestFile.toPath())) { + try { + refresh(); + } catch (IOException ex) { + Logger.getLogger(SystemVirtualFileSystemManifest.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + } + } + }, SystemVirtualFileSystemManifest.class.getSimpleName() + " Manifest Watcher").start(); + } + + private void read(File manifestFile, Set manifest) throws IOException { + try { + // Read the file in outside of the synchronization block + Set fileSet = (Set) new ObjectInputStream(new FileInputStream(manifestFile)).readObject(); + synchronized(manifest) { + manifest.clear(); + manifest.addAll(fileSet); + } + } catch (ClassNotFoundException ex) { + throw new Error(ex); + } + } + + private void save() throws IOException { + new ObjectOutputStream(new FileOutputStream(manifestFile)).writeObject(manifest); + } + + @Override + public boolean fileInManifest(VirtualFile file) { + String p = file.getPath(); + return manifest.contains(p); + } + + @Override + public void removeFromManifest(VirtualFile file) throws IOException { + manifest.remove(file.getPath()); + save(); + } + + @Override + public void addToManifest(VirtualFile file) throws IOException { + manifest.add(file.getPath()); + save(); + } + + @Override + public void refresh() throws IOException { + System.out.println("Refreshing file"); + read(manifestFile, manifest); + } +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/VirtualFile.java b/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/VirtualFile.java index 2969188a5e..82616af6ed 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/VirtualFile.java +++ b/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/VirtualFile.java @@ -1,55 +1,54 @@ package com.laytonsmith.PureUtilities.VirtualFS; /** - * A virtual file represents a path to a file - * in the virtual file system. Not many operations - * can be done with this class, it is passed instead to - * the VirtualFileSystem. - * + * A virtual file represents a path to a file in the virtual file system. Not many operations can be done with this + * class, it is passed instead to the VirtualFileSystem. + * */ public class VirtualFile { + /** * These characters are not allowed in a file name */ - private static final String [] RESTRICTED_CHARS = new String[]{"?","%","*",":","|","\"","<",">"," "}; - - private String path; - private boolean isAbsolute; - - - public VirtualFile(String path){ - String working = path; - working = working.replace("\\", "/"); - for(String s : RESTRICTED_CHARS){ - if(working.contains(s)){ + private static final String[] RESTRICTED_CHARS = new String[]{"?", "%", "*", ":", "|", "\"", "<", ">", " "}; + + private final String path; + private final boolean isAbsolute; + + public VirtualFile(String path) { + String working = path.trim().toLowerCase(); + working = working.replace('\\', '/'); + for(String s : RESTRICTED_CHARS) { + if(working.contains(s)) { throw new InvalidVirtualFile("VirtualFiles cannot contain the '" + s + "' character."); } } //Now, remove duplicate slashes working = working.replaceAll("[/]{2,}", "/"); - //Remove unneccessary dots - if(working.startsWith("./")){ + //Remove unnecessary dots + if(working.startsWith("./")) { working = working.substring(2); } working = working.replaceAll("/\\./", "/"); isAbsolute = working.startsWith("/"); this.path = working; } - - boolean isAbsolute(){ + + boolean isAbsolute() { return isAbsolute; } - + /** * Returns the canonicalized path for this VirtualFile - * @return + * + * @return */ - public String getPath(){ + public String getPath() { return path; } - + @Override - public String toString(){ + public String toString() { return getPath(); } } diff --git a/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/VirtualFileSystem.java b/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/VirtualFileSystem.java index 386960e26e..26c019df43 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/VirtualFileSystem.java +++ b/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/VirtualFileSystem.java @@ -3,6 +3,7 @@ import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery; import com.laytonsmith.PureUtilities.ClassLoading.ClassMirror.ClassMirror; import com.laytonsmith.PureUtilities.Common.StreamUtils; +import com.laytonsmith.PureUtilities.Common.TimeConversionUtil; import com.laytonsmith.PureUtilities.VirtualFS.VirtualFileSystemSettings.VirtualFileSystemSetting; import java.io.File; import java.io.IOException; @@ -22,73 +23,90 @@ /** *

- * A virtual file system allows for strict control over a corresponding - * real file system. Reads and writes from the file system can be granularly controlled - * by a configuration, and things like file system quotas, file creation, and things can - * be restricted. All files in the virtual file system map to a real file, but where - * exactly on the real file system that is, is not exposed to the API user. Reads - * and writes will not be allowed outside of the root file system, so to delete a - * virtual file system simply requires deletion of that folder. All virtual files - * use a Unix style file path, and the root is whatever the root of the file system - * is. Primitive operations include reading and writing to the file system, iterating - * through the files, deleting files, and reading meta information about files. - * + * A virtual file system allows for strict control over a corresponding real file system. Reads and writes from the file + * system can be granularly controlled by a configuration, and things like file system quotas, file creation, and things + * can be restricted. All files in the virtual file system map to a real file, but where exactly on the real file system + * that is, is not exposed to the API user. Reads and writes will not be allowed outside of the root file system, so to + * delete a virtual file system simply requires deletion of that folder. All virtual files use a Unix style file path, + * and the root is whatever the root of the file system is. Primitive operations include reading and writing to the file + * system, iterating through the files, deleting files, and reading meta information about files. + * *

- * All accesses can be controlled on a per file or per directory basis, and limits - * can be placed on folder depth, or total file system size. - * + * All accesses can be controlled on a per file or per directory basis, and limits can be placed on folder depth, or + * total file system size. + * *

- * The file system as a whole can also be cordoned off, meaning that the - * files that are created by outside processes don't appear as part of the virtual - * file system. In this case, a virtual manifest will used to determine which files are actually - * in the virtual file system. Reads and writes to files not in this manifest will - * be denied, however creation of new files will be allowed, assuming a file doesn't - * already exist there, and non-included files will not be shown in file listings. - * External processes will not inherently be blocked from accessing these manifested + * The file system as a whole can also be cordoned off, meaning that the files that are created by outside + * processes don't appear as part of the virtual file system. In this case, a virtual manifest will used to determine + * which files are actually in the virtual file system. Reads and writes to files not in this manifest will be denied, + * however creation of new files will be allowed, assuming a file doesn't already exist there, and non-included files + * will not be shown in file listings. External processes will not inherently be blocked from accessing these manifested * files, however, so only accesses through the virtual file system will be restricted. - * + * *

- * The virtual file system will create a directory at the root of the file system, - * .vfsmeta, which will contain all the information stored by the virtual - * file system itself, and reads and writes to this special directory will always - * fail. - * + * The virtual file system will create a directory at the root of the file system, .vfsmeta, which will + * contain all the information stored by the virtual file system itself, and reads and writes to this special directory + * will always fail. + * *

- * Symlinks can be added, which map directories inside the virtual file system to - * other directories on the real file system, and these links appear completely - * transparent to the file system. This allows for non-continuous file systems - * to appear continuous internally. Additionally, remote file systems can be mounted - * via ssh, and they will appear continuous. - * + * Symlinks can be added, which map directories inside the virtual file system to other directories on the real file + * system, and these links appear completely transparent to the file system. This allows for non-continuous file systems + * to appear continuous internally. Additionally, remote file systems can be mounted via ssh, and they will appear + * continuous. + * + *

+ * In order to support the lower common denominator, files are case insensitive, and all files and globs are trimmed + * and lowercased before operations are performed. + * */ public class VirtualFileSystem { + private static final String META_DIRECTORY_PATH = ".vfsmeta"; public static final VirtualFile META_DIRECTORY = new VirtualFile("/" + META_DIRECTORY_PATH); private static final String TMP_DIRECTORY_PATH = META_DIRECTORY_PATH + "/tmp"; public static final VirtualFile TMP_DIRECTORY = new VirtualFile("/" + TMP_DIRECTORY_PATH); - public static final String SYMLINK_FILE_NAME = "symlinks.txt"; - public static final String MANIFEST_FILE_NAME = "manifest.txt"; + /** + * The name of the symlinks file. The symlinks file contains the list of "mount points" within the file system, + * which is particularly useful + */ + public static final String SYMLINK_FILE_NAME = "symlinks.ini"; + /** + * The name of the manifest file. The manifest file is a newline separated list of files that are in the file + * system. This file is only used if the file system is cordoned off, but if so, only files within this list + * can be read from, written to, listed, or deleted, though it is possible to create new files, so long as these + * files don't already exist in the underlying file system. The file contains a HashMap of listings in a java + * serialization format. + */ + public static final String MANIFEST_FILE_NAME = "manifest.ser"; + /** + * The name of the settings file. The settings file contains the information about the system's configuration. + */ public static final String SETTINGS_FILE_NAME = "settings.yml"; - + private final VirtualFileSystemSettings settings; protected final File root; public final File symlinkFile; - private BigInteger quota = new BigInteger("-1"); - private BigInteger FSSize = new BigInteger("0"); + private final BigInteger quota = new BigInteger("-1"); + private BigInteger fsSize = new BigInteger("0"); private Thread fsSizeThread; private final List currentTmpFiles = new ArrayList(); private final Map symlinks = new HashMap(); - - private static final Map FSLProviders = new HashMap(); + /** + * If this FS is cordoned off, this will be non-null. If it is cordoned off, then this + */ + private final VirtualFileSystemManifest vfsManifest; + + private static final Map FSL_PROVIDERS = new HashMap(); + static { ClassDiscovery.getDefaultInstance().addDiscoveryLocation(ClassDiscovery.GetClassContainer(VirtualFileSystem.class)); Set> fslayerClasses = ClassDiscovery.getDefaultInstance().getClassesWithAnnotation(FileSystemLayer.fslayer.class); - for(ClassMirror clazzMirror : fslayerClasses){ + for(ClassMirror clazzMirror : fslayerClasses) { try { Class clazz = clazzMirror.loadClass(); Constructor constructor = clazz.getConstructor(VirtualFile.class, VirtualFileSystem.class, String.class); FileSystemLayer.fslayer annotation = clazz.getAnnotation(FileSystemLayer.fslayer.class); - FSLProviders.put(annotation.value(), constructor); + FSL_PROVIDERS.put(annotation.value(), constructor); } catch (NoSuchMethodException ex) { throw new Error(clazzMirror.getClassName() + " must implement a constructor with the signature: public " + clazzMirror.getSimpleName() + "(" + VirtualFile.class.getSimpleName() + ", " + VirtualFileSystem.class.getSimpleName() + ", " + String.class.getSimpleName() + ")"); @@ -97,35 +115,40 @@ public class VirtualFileSystem { } } } - - - + /** - * Creates a new VirtualFileSystem, at the root specified. If the root - * doesn't exist, it will automatically be created. - * @param root - * @param settings The settings object, which represents this file system's settings. If null, - * it is assumed this is a fresh installation, and will be handled accordingly. + * Creates a new VirtualFileSystem, at the root specified. If the root doesn't exist, it will automatically be + * created. + * + * @param root The root of the file system + * @param settings The settings object, which represents this file system's settings. If null, it is assumed this is + * a fresh installation, and will be handled accordingly. * @throws IOException If the file system cannot be initialized at this location */ - public VirtualFileSystem(final File root, VirtualFileSystemSettings settings) throws IOException{ - this.settings = settings==null?new VirtualFileSystemSettings(""):settings; + public VirtualFileSystem(final File root, VirtualFileSystemSettings settings) throws IOException { + this.settings = settings == null ? new VirtualFileSystemSettings("") : settings; this.root = root; install(); symlinkFile = new File(root, META_DIRECTORY_PATH + "/" + SYMLINK_FILE_NAME); + if(this.settings.isCordonedOff()) { + File manifest = new File(new File(root, META_DIRECTORY_PATH), MANIFEST_FILE_NAME); + vfsManifest = SystemVirtualFileSystemManifest.getInstance(manifest); + } else { + vfsManifest = null; + } //TODO: If it is cordoned off, we don't need this thread either, we need a different //thread, but it only needs to run once - if(this.settings.hasQuota()){ + if(this.settings.hasQuota()) { //We need to kick off a thread to determine the current FS size. fsSizeThread = new Thread(new Runnable() { @Override public void run() { - while(true){ + while(true) { try { - FSSize = FileUtils.sizeOfDirectoryAsBigInteger(root); + fsSize = FileUtils.sizeOfDirectoryAsBigInteger(root); //Sleep for a minute before running again. - Thread.sleep(60 * 1000); + Thread.sleep(TimeConversionUtil.inMilliseconds(1, TimeConversionUtil.TimeUnit.MINUTE)); } catch (InterruptedException ex) { Logger.getLogger(VirtualFileSystem.class.getName()).log(Level.SEVERE, null, ex); } @@ -138,56 +161,72 @@ public void run() { } //TODO: Kick off the tmp file deleter thread } - - private void install() throws IOException{ - if(!root.exists()){ + + private void install() throws IOException { + if(!root.exists()) { root.mkdirs(); } File meta = new File(root, META_DIRECTORY_PATH); meta.mkdir(); - + File settingsFile = new File(meta, SETTINGS_FILE_NAME); - File manifest = new File(meta, MANIFEST_FILE_NAME); File symlinks = new File(meta, SYMLINK_FILE_NAME); File tmpDir = new File(meta, "tmp"); - - if(!settingsFile.exists()){ + + if(!settingsFile.exists()) { settingsFile.createNewFile(); } - - if(!manifest.exists()){ - manifest.createNewFile(); - } - - if(!symlinks.exists()){ + + if(!symlinks.exists()) { symlinks.createNewFile(); } - - if(!tmpDir.exists()){ + + if(!tmpDir.exists()) { tmpDir.mkdirs(); } - + } - - private void assertReadPermission(VirtualFile file){ - Boolean hidden = (Boolean)settings.getSetting(file, VirtualFileSystemSetting.HIDDEN); - if(hidden){ + + private void assertReadPermission(VirtualFile file) { + Boolean hidden = (Boolean) settings.getSetting(file, VirtualFileSystemSetting.HIDDEN); + if(hidden) { throw new PermissionException(file.getPath() + " cannot be read."); } + boolean cordonedOff = settings.isCordonedOff(); + if(cordonedOff) { + // Check in manifest, to see if this file is in it. If not, then this file doesn't exist, for this purpose, + // and so we throw a permission exception + if(!vfsManifest.fileInManifest(file)) { + throw new PermissionException(file.getPath() + " cannot be read."); + } + } } - - private void assertWritePermission(VirtualFile file){ - Boolean readOnly = (Boolean)settings.getSetting(file, VirtualFileSystemSetting.READONLY); - Boolean hidden = (Boolean)settings.getSetting(file, VirtualFileSystemSetting.HIDDEN); - if(readOnly || hidden){ + + private void assertWritePermission(VirtualFile file) throws IOException { + Boolean readOnly = (Boolean) settings.getSetting(file, VirtualFileSystemSetting.READONLY); + Boolean hidden = (Boolean) settings.getSetting(file, VirtualFileSystemSetting.HIDDEN); + if(readOnly || hidden) { throw new PermissionException(file.getPath() + " cannot be written to."); } + boolean cordonedOff = settings.isCordonedOff(); + if(cordonedOff) { + // If the file already exists in the manifest, then it's fine. + if(vfsManifest.fileInManifest(file)) { + return; + } + // Not in manifest + // Check if the underlying real file location exists already. If so, don't allow writing. If not, + // then writing is ok + if(normalize(file).exists()) { + throw new PermissionException(file.getPath() + " cannot be written to."); + } + } } - - private FileSystemLayer normalize(VirtualFile virtual) throws IOException{ + + private FileSystemLayer normalize(VirtualFile virtual) throws IOException { URI uri = null; - for(VirtualGlob vg : symlinks.keySet()){ - if(vg.matches(virtual)){ + for(VirtualGlob vg : symlinks.keySet()) { + if(vg.matches(virtual)) { uri = symlinks.get(vg); break; } @@ -198,14 +237,14 @@ private FileSystemLayer normalize(VirtualFile virtual) throws IOException{ //both a) who we need to instantiate to provide the fslayer for //us, and b) what the symlink actually is. Default to no //symlink, with a file: provider. - if(uri != null){ + if(uri != null) { provider = uri.getScheme(); symlink = uri.getSchemeSpecificPart(); } - if(FSLProviders.containsKey(provider)){ + if(FSL_PROVIDERS.containsKey(provider)) { FileSystemLayer fsl; try { - fsl = (FileSystemLayer) FSLProviders.get(provider).newInstance(virtual, this, symlink); + fsl = (FileSystemLayer) FSL_PROVIDERS.get(provider).newInstance(virtual, this, symlink); } catch (Exception ex) { //This shouldn't happen ever, minus a programming mistake? throw new Error(ex); @@ -217,248 +256,241 @@ private FileSystemLayer normalize(VirtualFile virtual) throws IOException{ throw new Error("Unknown provider for " + provider); } } - + /** - * Reads bytes from a file. - * Requires read permission. + * Reads bytes from a file. Requires read permission. + * * @param file - * @return + * @return */ - public byte[] read(VirtualFile file){ + public byte[] read(VirtualFile file) { try { return StreamUtils.GetBytes(readAsStream(file)); } catch (IOException ex) { throw new RuntimeException(ex); } } - - /** - * Writes an InputStream to a file. - * Requires write permission. - * @param file - * @param data - */ - public void write(VirtualFile file, InputStream data){ - try { - write(file, StreamUtils.GetBytes(data)); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - } - + /** - * Reads a file as a stream. - * Requires read permission. + * Reads a file as a stream. Requires read permission. + * * @param file - * @return + * @return */ - public InputStream readAsStream(VirtualFile file) throws IOException{ + public InputStream readAsStream(VirtualFile file) throws IOException { assertReadPermission(file); FileSystemLayer real = normalize(file); return real.getInputStream(); } - + /** - * Writes the bytes out to a file. - * Requires write permission. + * Writes the bytes out to a file. Requires write permission. + * * @param file - * @param bytes + * @param bytes */ - public void write(VirtualFile file, byte[] bytes) throws IOException{ + public void write(VirtualFile file, byte[] bytes) throws IOException { assertWritePermission(file); FileSystemLayer real = normalize(file); real.writeByteArray(bytes); } - + + /** + * Writes an InputStream to a file. Requires write permission. + * + * @param file + * @param data + */ + public void write(VirtualFile file, InputStream data) { + try { + write(file, StreamUtils.GetBytes(data)); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + /** * Convenience method to write out a plain string. + * * @param file * @return - * @throws IOException + * @throws IOException */ - public String readUTFString(VirtualFile file) throws IOException{ + public String readUTFString(VirtualFile file) throws IOException { return new String(read(file), "UTF-8"); } - + /** * Convenience method to read in a plain string. + * * @param file * @param string - * @throws IOException + * @throws IOException */ - public void writeUTFString(VirtualFile file, String string) throws IOException{ + public void writeUTFString(VirtualFile file, String string) throws IOException { write(file, string.getBytes("UTF-8")); } - + /** - * Lists the files and folders in this directory. Note that the - * . and .. directory will not be - * present. If this is cordoned off, it will be the virtual file - * listing. - * Requires read permission. + * Lists the files and folders in this directory. Note that the . and .. directory will not be present. If this is + * cordoned off, it will be the virtual file listing. Requires read permission. + * * @param directory - * @return + * @return */ - public VirtualFile [] list(VirtualFile directory) throws IOException{ + public VirtualFile[] list(VirtualFile directory) throws IOException { assertReadPermission(directory); - if(settings.isCordonedOff()){ + if(settings.isCordonedOff()) { throw new UnsupportedOperationException("Not yet implemented."); } else { FileSystemLayer real = normalize(directory); return real.listFiles(); } } - + /** - * Deletes a file or folder. Note that if this is cordoned off, and - * this is a directory, the directory may appear to be empty according - * to {@see #list}, but it won't be deleted if other files are actually - * living in it, but regardless, the entry will be removed from the manifest, - * and further calls to list will show it having been deleted. - * Requires write permission. + * Deletes a file or folder. Note that if this is cordoned off, and this is a directory, the directory may appear to + * be empty according to {@link #list(VirtualFile)} method, but it won't be deleted if other files are actually + * living in it, but regardless, the entry will be removed from the manifest, and further calls to list will show + * it having been deleted. Requires write permission. * @param file - * @return */ - public void delete(VirtualFile file) throws IOException{ + public void delete(VirtualFile file) throws IOException { assertWritePermission(file); - if(settings.isCordonedOff()){ + if(settings.isCordonedOff()) { throw new UnsupportedOperationException("Not implemented yet."); } else { normalize(file).delete(); } } - + /** - * Works the same as {@see #delete}, but the file will be - * deleted upon exit of the JVM. + * Works the same as {@link #delete(VirtualFile)}, but the file will be deleted upon exit of the JVM. * Requires write permission. * @param file */ - public void deleteOnExit(VirtualFile file) throws IOException{ + public void deleteOnExit(VirtualFile file) throws IOException { assertWritePermission(file); - if(settings.isCordonedOff()){ + if(settings.isCordonedOff()) { throw new UnsupportedOperationException("Not implemented yet."); } else { - normalize(file).deleteOnExit(); + normalize(file).deleteEventually(); } } - + /** - * Returns true if the file represented by this VirtualFile - * actually exists on the file system. - * Requires read permission. + * Returns true if the file represented by this VirtualFile actually exists on the file system. Requires read + * permission. + * * @param file - * @return + * @return */ - public boolean exists(VirtualFile file) throws IOException{ + public boolean exists(VirtualFile file) throws IOException { assertReadPermission(file); return normalize(file).exists(); } - + /** - * Returns true if this file can be read. If the file doesn't - * exist, returns false. - * Requires read permission. + * Returns true if this file can be read. If the file doesn't exist, returns false. Requires read permission. + * * @param file - * @return + * @return */ - public boolean canRead(VirtualFile file) throws IOException{ + public boolean canRead(VirtualFile file) throws IOException { assertReadPermission(file); - if(!exists(file)){ + if(!exists(file)) { return false; } return normalize(file).canRead(); } - + /** - * Returns true if this file can be written to. - * Requires read permission. + * Returns true if this file can be written to. Requires read permission. + * * @param file - * @return + * @return */ - public boolean canWrite(VirtualFile file) throws IOException{ + public boolean canWrite(VirtualFile file) throws IOException { assertReadPermission(file); return normalize(file).canWrite(); } - + /** - * Returns true if the abstract path represented by this file - * is absolute, that is, if it starts with a forward slash. - * Does not require any permissions, because it simply deals with - * the virtual file path. + * Returns true if the abstract path represented by this file is absolute, that is, if it starts with a forward + * slash. Does not require any permissions, because it simply deals with the virtual file path. + * * @param file - * @return + * @return */ - public boolean isAbsolute(VirtualFile file){ + public boolean isAbsolute(VirtualFile file) { return file.isAbsolute(); } - + /** - * Returns true if this path represented by the VirtualFile path is a directory. - * If no file or folder exists, false is returned. - * Requires read permissions. + * Returns true if this path represented by the VirtualFile path is a directory. If no file or folder exists, false + * is returned. Requires read permissions. + * * @param fileOrFolder - * @return + * @return */ - public boolean isDirectory(VirtualFile fileOrFolder) throws IOException{ + public boolean isDirectory(VirtualFile fileOrFolder) throws IOException { assertReadPermission(fileOrFolder); - if(!exists(fileOrFolder)){ + if(!exists(fileOrFolder)) { return false; } return normalize(fileOrFolder).isDirectory(); } - + /** - * Returns true if this path represented by the VirtualFile path is - * a file. If no file or folder exists, false is returned. - * Requires read permissions. + * Returns true if this path represented by the VirtualFile path is a file. If no file or folder exists, false is + * returned. Requires read permissions. + * * @param fileOrFolder - * @return + * @return */ - public boolean isFile(VirtualFile fileOrFolder) throws IOException{ + public boolean isFile(VirtualFile fileOrFolder) throws IOException { assertReadPermission(fileOrFolder); - if(!exists(fileOrFolder)){ + if(!exists(fileOrFolder)) { return false; } return normalize(fileOrFolder).isFile(); } - + /** - * Creates the directory specified by the VirtualFile path, and any - * parent directories as needed. - * Requires write permissions. - * @param directory + * Creates the directory specified by the VirtualFile path, and any parent directories as needed. Requires write + * permissions. + * + * @param directory */ - public void mkdirs(VirtualFile directory) throws IOException{ + public void mkdirs(VirtualFile directory) throws IOException { assertWritePermission(directory); normalize(directory).mkdirs(); } - + /** - * Creates a new, empty file at this location, if no file already - * exists at this location. - * Requires write permissions. - * @param file + * Creates a new, empty file at this location, if no file already exists at this location. Requires write + * permissions. + * + * @param file */ - public void createEmptyFile(VirtualFile file) throws IOException{ + public void createEmptyFile(VirtualFile file) throws IOException { assertWritePermission(file); - if(exists(file)){ + if(exists(file)) { return; } normalize(file).createNewFile(); } - + /** - * Creates a new temporary file, which is guaranteed to be unique, and - * will definitely exist for this session. The file is likely to be deleted - * at the start of the next session however, and so must not be relied on to - * continue to exist. Temporary files do count towards the quota if enabled, - * but will be deleted by the system automatically. You must have read and - * write permissions to / to create a temp file. + * Creates a new temporary file, which is guaranteed to be unique, and will definitely exist for this session. The + * file is likely to be deleted at the start of the next session however, and so must not be relied on to continue + * to exist. Temporary files do count towards the quota if enabled, but will be deleted by the system automatically. + * You must have read and write permissions to / to create a temp file. + * * @return - * @throws IOException + * @throws IOException */ - public VirtualFile createTempFile() throws IOException{ + public VirtualFile createTempFile() throws IOException { assertWritePermission(new VirtualFile("/")); assertReadPermission(new VirtualFile("/")); String filename = "/" + TMP_DIRECTORY_PATH + "/" + UUID.randomUUID().toString() + ".tmp"; @@ -469,5 +501,5 @@ public VirtualFile createTempFile() throws IOException{ real.createNewFile(); return path; } - + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/VirtualFileSystemManifest.java b/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/VirtualFileSystemManifest.java new file mode 100644 index 0000000000..f93a2052d3 --- /dev/null +++ b/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/VirtualFileSystemManifest.java @@ -0,0 +1,35 @@ +package com.laytonsmith.PureUtilities.VirtualFS; + +import java.io.IOException; + +/** + * + */ +public interface VirtualFileSystemManifest { + /** + * Returns whether or not this file is listed in the manifest + * @param file The file to check + * @return True if the files is in the manifest, false otherwise. + */ + public boolean fileInManifest(VirtualFile file); + + /** + * Removes the given file from the manifest + * @param file + * @throws IOException + */ + public void removeFromManifest(VirtualFile file) throws IOException; + + /** + * Adds the given file to the manifest + * @param file + * @throws IOException + */ + public void addToManifest(VirtualFile file) throws IOException; + + /** + * Refreshes the manifest from the source (if applicable) + * @throws IOException + */ + public void refresh() throws IOException; +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/VirtualFileSystemSettings.java b/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/VirtualFileSystemSettings.java index 84d492c562..65f75df949 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/VirtualFileSystemSettings.java +++ b/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/VirtualFileSystemSettings.java @@ -12,33 +12,30 @@ import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; -import java.util.logging.Level; -import java.util.logging.Logger; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.Yaml; /** - * A collection of file system settings are tied to a glob, and - * are passed in upon creation of the VirtualFileSystem. Any files - * or folders that exist (or are attempted to be created) and match the glob - * are first checked against these settings, upon which the request will either - * be allowed, or be denied. - * + * A collection of file system settings are tied to a glob, and are passed in upon creation of the VirtualFileSystem. + * Any files or folders that exist (or are attempted to be created) and match the glob are first checked against these + * settings, upon which the request will either be allowed, or be denied. + * */ public class VirtualFileSystemSettings { + /** * DO NOT CHANGE THIS. It will break future integrations if you do. - * - * This is the line that denotes the divider between the top and bottom parts - * of a settings file. + * + * This is the line that denotes the divider between the top and bottom parts of a settings file. */ - public static final String GENERATED_LINE = - "This file is automatically generated, to keep up-to-date with new features.\n" + public static final String GENERATED_LINE + = "This file is automatically generated, to keep up-to-date with new features.\n" + "# Comments you add to the file will not be retained.\n"; private static final String SETTING_TYPES; - static{ + + static { List list = new ArrayList(); - for(VirtualFileSystemSetting setting : VirtualFileSystemSetting.values()){ + for(VirtualFileSystemSetting setting : VirtualFileSystemSetting.values()) { String s = "Setting name: " + setting.getName() + "\n" + "# Default value: " + setting.getDef().toString() + "\n" + "# Description: " + setting.getDescription() + "\n"; @@ -46,14 +43,14 @@ public class VirtualFileSystemSettings { } SETTING_TYPES = StringUtils.Join(list, "\n# "); } - - public static String getDefaultSettingsString(){ + + public static String getDefaultSettingsString() { return StreamUtils.GetString(VirtualFileSystemSettings.class.getResourceAsStream("example_settings.yml")) .replace("%%SETTING_TYPES%%", SETTING_TYPES) .replace("%%GENERATED_LINE%%", GENERATED_LINE); } - - public static enum VirtualFileSystemSetting{ + + public static enum VirtualFileSystemSetting { HIDDEN("hidden", false, "If true, the file system will not allow the file or directory to be created, and if a file or directory already exists, it will not" + " be exposed. This is essentially a way to revoke both read and write privileges."), QUOTA("quota", -1, "Sets the quota for the total list of files or folders that match this glob. Quotas for a cordoned off file system will only affect files" @@ -67,19 +64,18 @@ public static enum VirtualFileSystemSetting{ + " off."), FOLDER_DEPTH("folder-depth", -1, "The number of folders deep that will be allowed to be created in this directory. The glob must be a directory if this is anything" + " other than -1. -1 means that the number of sub folders is unrestricted, 0 means that no folders can be created inside this one. This does not" - + " affect existing folder structure."), - - ; + + " affect existing folder structure."),; private String name; private Object def; private String description; - private VirtualFileSystemSetting(String name, Object def, String description){ + + private VirtualFileSystemSetting(String name, Object def, String description) { this.name = name; this.def = def; this.description = description; } - - public String getName(){ + + public String getName() { return name; } @@ -90,43 +86,43 @@ public Object getDef() { public String getDescription() { return description; } - - static VirtualFileSystemSetting getSettingByName(String name){ - for(VirtualFileSystemSetting s : VirtualFileSystemSetting.values()){ - if(s.getName().equals(name)){ + + static VirtualFileSystemSetting getSettingByName(String name) { + for(VirtualFileSystemSetting s : VirtualFileSystemSetting.values()) { + if(s.getName().equals(name)) { return s; } } return null; } - + } - - public static String serialize(Map settings){ + + public static String serialize(Map settings) { DumperOptions options = new DumperOptions(); options.setPrettyFlow(true); - Yaml yaml = new Yaml(options); + Yaml yaml = new Yaml(options); Map> serializable = new HashMap>(); - for(VirtualGlob glob : settings.keySet()){ + for(VirtualGlob glob : settings.keySet()) { Map inner = new HashMap(); - for(VirtualFileSystemSetting setting : settings.get(glob).settingGroup.keySet()){ + for(VirtualFileSystemSetting setting : settings.get(glob).settingGroup.keySet()) { inner.put(setting.getName(), settings.get(glob).get(setting)); } serializable.put(glob.toString(), inner); } - return yaml.dump(serializable); + return yaml.dump(serializable); } - - public static Map deserialize(String settings){ + + public static Map deserialize(String settings) { Yaml yaml = new Yaml(); - Map> unserialized = (Map)yaml.load(settings); - Map parsedSettings = new HashMap(); - if(unserialized != null){ - for(String glob : unserialized.keySet()){ + Map> unserialized = (Map) yaml.load(settings); + Map parsedSettings = new HashMap(); + if(unserialized != null) { + for(String glob : unserialized.keySet()) { VirtualGlob vglob = new VirtualGlob(glob); - Map settingGroup = (Map)unserialized.get(glob); + Map settingGroup = (Map) unserialized.get(glob); SettingGroup group = new SettingGroup(); - for(String settingName : settingGroup.keySet()){ + for(String settingName : settingGroup.keySet()) { VirtualFileSystemSetting s = VirtualFileSystemSetting.getSettingByName(settingName); Object value = settingGroup.get(settingName); group.set(s, value); @@ -136,22 +132,25 @@ public static Map deserialize(String settings){ } return parsedSettings; } - - public static class SettingGroup{ + + public static class SettingGroup { + private Map settingGroup; - public SettingGroup(){ + + public SettingGroup() { this.settingGroup = new EnumMap(VirtualFileSystemSetting.class); } - public SettingGroup(Map settingGroup){ + + public SettingGroup(Map settingGroup) { this.settingGroup = settingGroup; } - - public void set(VirtualFileSystemSetting setting, Object value){ + + public void set(VirtualFileSystemSetting setting, Object value) { settingGroup.put(setting, value); } - - public Object get(VirtualFileSystemSetting setting){ - if(settingGroup.containsKey(setting)){ + + public Object get(VirtualFileSystemSetting setting) { + if(settingGroup.containsKey(setting)) { return settingGroup.get(setting); } else { return setting.getDef(); @@ -161,81 +160,83 @@ public Object get(VirtualFileSystemSetting setting){ @Override public String toString() { StringBuilder b = new StringBuilder(); - for(VirtualFileSystemSetting s : settingGroup.keySet()){ + for(VirtualFileSystemSetting s : settingGroup.keySet()) { b.append(s.getName()).append(": ").append(settingGroup.get(s)).append("; "); } return b.toString().trim(); } - + } - + private static final Map META_DIRECTORY_SETTINGS = new EnumMap(VirtualFileSystemSetting.class); - static{ + + static { META_DIRECTORY_SETTINGS.put(VirtualFileSystemSetting.HIDDEN, true); } - + private Map settings; private boolean hasQuota = false; private boolean cordonedOff = false; - - public VirtualFileSystemSettings(File settingsFile) throws IOException{ - this(settingsFile.exists()?FileUtil.read(settingsFile):""); + + public VirtualFileSystemSettings(File settingsFile) throws IOException { + this(settingsFile.exists() ? FileUtil.read(settingsFile) : ""); //Here, we will also output the serialized file, with the comment //block at top FileUtil.write(getDefaultSettingsString() + serialize(settings), settingsFile); } - public VirtualFileSystemSettings(String unparsedSettings){ - settings = (HashMap) deserialize(unparsedSettings); + + public VirtualFileSystemSettings(String unparsedSettings) { + this((HashMap) deserialize(unparsedSettings)); } - - public VirtualFileSystemSettings(Map settings){ - this.settings = new HashMap(settings); + + public VirtualFileSystemSettings(Map settings) { + this.settings = new HashMap<>(settings); this.settings.put(new VirtualGlob(VirtualFileSystem.META_DIRECTORY), new SettingGroup(META_DIRECTORY_SETTINGS)); - for(VirtualGlob g : settings.keySet()){ + for(VirtualGlob g : settings.keySet()) { SettingGroup s = settings.get(g); - if(s.settingGroup.keySet().contains(VirtualFileSystemSetting.QUOTA)){ - if((Integer)s.settingGroup.get(VirtualFileSystemSetting.QUOTA) >= 0){ - if(g.matches(new VirtualFile("/"))){ + if(s.settingGroup.keySet().contains(VirtualFileSystemSetting.QUOTA)) { + if((Integer) s.settingGroup.get(VirtualFileSystemSetting.QUOTA) >= 0) { + if(g.matches(new VirtualFile("/"))) { hasQuota = true; } else { - Logger.getLogger(VirtualFileSystemSettings.class.getName()).log(Level.WARNING, "The \"quota\" setting can only be applied to the root of the " - + "file system at this time. The quota setting for " + g.toString() + " is being ignored."); + throw new IllegalArgumentException("The \"quota\" setting can only be applied to the root of the " + + "file system at this time but it was set on " + g.toString() + "."); } } } - - if(s.settingGroup.keySet().contains(VirtualFileSystemSetting.CORDONED_OFF)){ - if((Boolean)s.settingGroup.get(VirtualFileSystemSetting.CORDONED_OFF) == true){ - if(g.matches(new VirtualFile("/"))){ + + if(s.settingGroup.keySet().contains(VirtualFileSystemSetting.CORDONED_OFF)) { + if((Boolean) s.settingGroup.get(VirtualFileSystemSetting.CORDONED_OFF) == true) { + if(g.matches(new VirtualFile("/"))) { cordonedOff = true; } else { - Logger.getLogger(VirtualFileSystemSettings.class.getName()).log(Level.WARNING, "The \"cordoned-off\" setting can only be applied to the root" - + " of the file system at this time. The setting for " + g.toString() + " is being ignored."); + throw new IllegalArgumentException("The \"cordoned-off\" setting can only be applied to the root" + + " of the file system but it was set on " + g.toString() + "."); } } } } } - + /** - * Gets the most specific value for the specified setting, for the specified file. - * File specificity will match whatever is closest, so if this matches both the globs: ** and file/**, - * then the file/** glob settings will win. + * Gets the most specific value for the specified setting, for the specified file. File specificity will match + * whatever is closest, so if this matches both the globs: ** and file/**, then the file/** glob settings will win. + * * @param file * @param setting - * @return + * @return */ - public Object getSetting(VirtualFile file, VirtualFileSystemSetting setting){ + public Object getSetting(VirtualFile file, VirtualFileSystemSetting setting) { SortedSet matchedGlobs = new TreeSet(); - for(VirtualGlob glob : settings.keySet()){ - if(glob.matches(file)){ + for(VirtualGlob glob : settings.keySet()) { + if(glob.matches(file)) { matchedGlobs.add(glob); } } - if(matchedGlobs.isEmpty()){ + if(matchedGlobs.isEmpty()) { //trivial state return setting.getDef(); - } else if(matchedGlobs.size() == 1){ + } else if(matchedGlobs.size() == 1) { //trivial state return settings.get(matchedGlobs.first()).get(setting); } else { @@ -245,10 +246,10 @@ public Object getSetting(VirtualFile file, VirtualFileSystemSetting setting){ public boolean hasQuota() { return hasQuota; - } - - public boolean isCordonedOff(){ + } + + public boolean isCordonedOff() { return cordonedOff; } - + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/VirtualGlob.java b/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/VirtualGlob.java index 3c4eea5809..7a47ba2192 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/VirtualGlob.java +++ b/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/VirtualGlob.java @@ -1,54 +1,114 @@ package com.laytonsmith.PureUtilities.VirtualFS; +import java.util.regex.Pattern; + /** - * A VirtualGlob is a simple wrapper around a file matching glob. The following - * rules are applied: All characters are taken literally, except for the asterisk, - * double asterisk, or question mark, which mean the following: + * A VirtualGlob is a simple wrapper around a file matching glob. The following rules are applied: All characters are + * taken literally, except for the asterisk, double asterisk, or question mark, which mean the following: *

- * - * The essential operations of a glob simply ask if a particular VirtualFile actually match - * this glob or not. - * - * + * + * The essential operations of a glob simply ask if a particular VirtualFile actually match this glob or not. + * + * */ public class VirtualGlob implements Comparable { - - private String glob; - + + private final String glob; + private final boolean matchAll; + private final Pattern globPattern; + /** - * Creates a new virtual glob object, that will match this glob - * pattern. - * @param glob + * Creates a new virtual glob object, that will match this glob pattern. + * + * @param glob */ - public VirtualGlob(String glob){ - this.glob = glob; + public VirtualGlob(String glob) { + this.glob = glob.trim().toLowerCase(); + matchAll = "**".equals(glob); + globPattern = getPattern(glob); } - + /** * Creates a glob that will match only this file. - * @param file + * + * @param file */ - public VirtualGlob(VirtualFile file){ + public VirtualGlob(VirtualFile file) { glob = file.getPath(); + matchAll = false; + globPattern = getPattern(glob); + } + + private Pattern getPattern(String glob) { + if(glob.isEmpty()) { + throw new IllegalArgumentException("Glob cannot be empty"); + } + StringBuilder buffer = new StringBuilder(); + StringBuilder pattern = new StringBuilder(); + for(int i = 0; i < glob.length(); i++) { + char c1 = glob.charAt(i); + char c2 = '\0'; + if(i < glob.length() - 1) { + c2 = glob.charAt(i + 1); + } + if(c1 == '?' || c1 == '*') { + if(buffer.length() > 0) { + pattern.append(Pattern.quote(buffer.toString())); + buffer = new StringBuilder(); + } + if(c1 == '?') { + pattern.append('.'); + } else { + if(c2 == '*') { + i++; + pattern.append(".*"); + } else { + pattern.append("[^/\\\\]*"); + } + } + continue; + } + buffer.append(c1); + } + if(buffer.length() > 0) { + pattern.append(Pattern.quote(buffer.toString())); + } + return Pattern.compile(pattern.toString(), Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE); } - - public boolean matches(VirtualFile file){ - throw new UnsupportedOperationException("Not implemented yet."); + + /** + * Returns true if the specified file matches this glob. + * @param file The actual file path to test + * @return + */ + public boolean matches(VirtualFile file) { + if(matchAll) { + // Trivial case + return true; + } + return globPattern.matcher(file.getPath()).matches(); } + /** + * Compares two globs, to see which one is more specific than the other. + * Returns 0 if they are the same weight, -1 if the passed in glob is more specific, and 1 if the passed + * in glob is less specific (TODO or maybe this should be reversed) + * @param o + * @return + */ @Override public int compareTo(VirtualGlob o) { - throw new UnsupportedOperationException("Not supported yet."); + // TODO: This is wrong, we need to do a more specific comparison where we take into account the wildcards + return glob.length() - o.glob.length(); } @Override public String toString() { return glob; } - - + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/VirtualSymlink.java b/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/VirtualSymlink.java index 0c5d072b2f..b365ca2909 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/VirtualSymlink.java +++ b/src/main/java/com/laytonsmith/PureUtilities/VirtualFS/VirtualSymlink.java @@ -3,25 +3,25 @@ import java.io.File; /** - * A virtual symlink is a transparent link from a VirtualFile - * to a real File, anywhere on the actual file system. According - * to the virtual file system, this link will be completely transparent. - * + * A virtual symlink is a transparent link from a VirtualFile to a real File, anywhere on the actual file system. + * According to the virtual file system, this link will be completely transparent. + * */ public class VirtualSymlink { - - private VirtualFile virtual; - private File real; - public VirtualSymlink(VirtualFile virtual, File real){ + + private final VirtualFile virtual; + private final File real; + + public VirtualSymlink(VirtualFile virtual, File real) { this.virtual = virtual; this.real = real; } - - public VirtualFile getVirtual(){ + + public VirtualFile getVirtual() { return virtual; } - - public File getReal(){ + + public File getReal() { return real; } } diff --git a/src/main/java/com/laytonsmith/PureUtilities/Web/Cookie.java b/src/main/java/com/laytonsmith/PureUtilities/Web/Cookie.java index bd93229c01..a3225f7308 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Web/Cookie.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Web/Cookie.java @@ -12,6 +12,7 @@ * This class wraps a single HTTP cookie. */ public final class Cookie implements Comparable { + private String name; private String value; private String domain; @@ -21,18 +22,18 @@ public final class Cookie implements Comparable { private boolean secureOnly = false; /** - * Given an unparsed value, this adds a new cookie to this cookie list - * after parsing the string into a proper cookie, and setting the fields - * appropriately. + * Given an unparsed value, this adds a new cookie to this cookie list after parsing the string into a proper + * cookie, and setting the fields appropriately. + * * @param unparsedValue - * @param currentURL + * @param currentURL */ public Cookie(String unparsedValue, URL currentURL) { //Split on ; String[] parts = unparsedValue.split(";"); - for (int i = 0; i < parts.length; i++) { + for(int i = 0; i < parts.length; i++) { String part = parts[i]; - if (i == 0) { + if(i == 0) { //This is the actual cookie value String[] nameVal = part.split("=", 2); name = nameVal[0].trim(); @@ -43,53 +44,55 @@ public Cookie(String unparsedValue, URL currentURL) { String[] keyval = part.split("=", 2); String key = keyval[0].trim().toLowerCase(); String val = null; - if (keyval.length >= 2) { + if(keyval.length >= 2) { val = keyval[1].trim(); } - if ("expires".equals(key)) { + if("expires".equals(key)) { DateFormat formatter = new SimpleDateFormat("EEE, dd-MMM-yyyy kk:mm:ss zzz"); try { expiration = formatter.parse(val).getTime(); } catch (ParseException ex) { Logger.getLogger(WebUtility.class.getName()).log(Level.SEVERE, null, ex); } - } else if ("path".equals(key)) { + } else if("path".equals(key)) { path = val; - } else if ("domain".equals(key)) { + } else if("domain".equals(key)) { //Finish the PublicSuffix stuff to validate this domain. - if (PublicSuffix.get().getEffectiveTLDLength(val) != -1) { + if(PublicSuffix.get().getEffectiveTLDLength(val) != -1) { domain = val; } else { Logger.getLogger(WebUtility.class.getName()).log(Level.SEVERE, "Possible attack cookie being set from " + currentURL + ". Attempted" + " to set " + val + " as the domain."); } - } else if ("httponly".equals(key)) { + } else if("httponly".equals(key)) { httpOnly = true; - } else if ("secureonly".equals(key)) { + } else if("secureonly".equals(key)) { secureOnly = true; } } - if (domain == null) { + if(domain == null) { domain = currentURL.getHost(); } - if (path == null) { + if(path == null) { path = currentURL.getPath(); } } - + /** - * Creates a cookie with only the required parameters set. That is, it creates - * a session cookie with httpOnly and secure set to false. + * Creates a cookie with only the required parameters set. That is, it creates a session cookie with httpOnly and + * secure set to false. + * * @param domain The domain under which this cookie applies * @param name The name of this cookie * @param value The value of this cookie * @param path The path under which this cookie applies in the domain */ - public Cookie(String name, String value, String domain, String path){ + public Cookie(String name, String value, String domain, String path) { this(name, value, domain, path, 0, false, false); } /** * Creates a cookie with all of the various parameters set. + * * @param domain The domain under which this cookie applies * @param name The name of this cookie * @param value The value of this cookie @@ -115,6 +118,7 @@ public int compareTo(Cookie o) { /** * Returns the domain of this cookie. + * * @return */ public String getDomain() { @@ -123,6 +127,7 @@ public String getDomain() { /** * Returns the name of this cookie. + * * @return */ public String getName() { @@ -131,6 +136,7 @@ public String getName() { /** * Returns the value of this cookie. + * * @return */ public String getValue() { @@ -139,6 +145,7 @@ public String getValue() { /** * Returns the path under which this cookie applies. + * * @return */ public String getPath() { @@ -146,8 +153,8 @@ public String getPath() { } /** - * Returns the expiration time of this cookie, in unix time. If the - * expiration is 0, the cookie never expires. + * Returns the expiration time of this cookie, in unix time. If the expiration is 0, the cookie never expires. + * * @return */ public long getExpiration() { @@ -156,6 +163,7 @@ public long getExpiration() { /** * Returns true if this cookie only applies in http, not https requests. + * * @return */ public boolean isHttpOnly() { @@ -164,28 +172,29 @@ public boolean isHttpOnly() { /** * Returns true if this cookie is only applicable in https, not http requests. + * * @return */ public boolean isSecureOnly() { return secureOnly; } - - + /** * Returns true if the cookie is currently expired. - * @return + * + * @return */ - public boolean isExpired(){ + public boolean isExpired() { return isExpired(System.currentTimeMillis()); } - + /** * Returns true if the cookie will be or was expired at the given time. - * @param time The time to check against to determine if the cookie - * will be expired at that time. - * @return + * + * @param time The time to check against to determine if the cookie will be expired at that time. + * @return */ - public boolean isExpired(long time){ + public boolean isExpired(long time) { return expiration != 0 && expiration < time; } diff --git a/src/main/java/com/laytonsmith/PureUtilities/Web/CookieJar.java b/src/main/java/com/laytonsmith/PureUtilities/Web/CookieJar.java index 513b16214e..73d911c10a 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Web/CookieJar.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Web/CookieJar.java @@ -13,17 +13,16 @@ import java.util.logging.Logger; /** - * This class manages a list of several cookies. There are several ways to add - * a new cookie to this manager, and the getCookies method returns the pre-parsed - * string that is suitable for direct use in an HTTP request header. + * This class manages a list of several cookies. There are several ways to add a new cookie to this manager, and the + * getCookies method returns the pre-parsed string that is suitable for direct use in an HTTP request header. */ public final class CookieJar { @Override public String toString() { StringBuilder b = new StringBuilder(); - for (Cookie cookie : cookies) { - if (!cookie.isExpired()) { + for(Cookie cookie : cookies) { + if(!cookie.isExpired()) { b.append(cookie.getName()).append("=") .append(cookie.getValue()).append("; used in ") .append(cookie.getDomain()).append(cookie.getPath()) @@ -39,14 +38,15 @@ public boolean checkIfEquals(Cookie val1, Cookie val2) { return val1.compareTo(val2) == 0; } }); - + /** - * Adds a new, pre-made cookie to this list. The constructors for Cookie - * are capable of parsing the cookies found in a HTTP request. - * @param cookie + * Adds a new, pre-made cookie to this list. The constructors for Cookie are capable of parsing the cookies found in + * a HTTP request. + * + * @param cookie */ - public void addCookie(Cookie cookie){ - if(this.cookies.contains(cookie)){ + public void addCookie(Cookie cookie) { + if(this.cookies.contains(cookie)) { //This is an update, so remove it first this.cookies.remove(cookie); } @@ -54,20 +54,19 @@ public void addCookie(Cookie cookie){ } /** - * Returns a string that is suitable to send as is with the Cookie - * HTTP header. The URL is the search url, which will look through this - * cookie jar, and only use the cookies that are applicable to this domain, - * not expired, etc. + * Returns a string that is suitable to send as is with the Cookie HTTP header. The URL is the search url, which + * will look through this cookie jar, and only use the cookies that are applicable to this domain, not expired, etc. + * * @param url - * @return + * @return */ public String getCookies(URL url) { List usable = new ArrayList(); //So we can iterate linearly List foundCookies = new ArrayList(this.cookies); - for (int i = 0; i < foundCookies.size(); i++) { + for(int i = 0; i < foundCookies.size(); i++) { Cookie cookie = foundCookies.get(i); - if (cookie.isExpired()) { + if(cookie.isExpired()) { //This cookie is expired. Remove it from our list, and continue. this.cookies.remove(cookie); foundCookies.remove(i); @@ -75,31 +74,31 @@ public String getCookies(URL url) { continue; } //Or it's secure only, and we aren't in https, continue. - if (cookie.isSecureOnly() && !url.getProtocol().equals("https")) { + if(cookie.isSecureOnly() && !url.getProtocol().equals("https")) { continue; } //If we aren't in the correct domain String domain = cookie.getDomain(); - if (domain.startsWith(".")) { + if(domain.startsWith(".")) { domain = domain.substring(1); } - if (!url.getHost().endsWith(domain)) { + if(!url.getHost().endsWith(domain)) { continue; } //Or if we aren't in the right path - String path = (url.getPath().startsWith("/")?"":"/") + url.getPath(); - if (!path.startsWith(cookie.getPath())) { + String path = (url.getPath().startsWith("/") ? "" : "/") + url.getPath(); + if(!path.startsWith(cookie.getPath())) { continue; } //If we're still here, it's good. usable.add(cookie); } - if (usable.isEmpty()) { + if(usable.isEmpty()) { return null; } StringBuilder b = new StringBuilder(); - for (Cookie cookie : usable) { - if (b.length() != 0) { + for(Cookie cookie : usable) { + if(b.length() != 0) { b.append("; "); } try { @@ -110,33 +109,33 @@ public String getCookies(URL url) { } return b.toString(); } - + /** - * Clears out all session cookies from this cookie jar. That is, all - * cookies with an expiration of 0 set. + * Clears out all session cookies from this cookie jar. That is, all cookies with an expiration of 0 set. */ - public void clearSessionCookies(){ + public void clearSessionCookies() { Iterator it = cookies.iterator(); - while(it.hasNext()){ + while(it.hasNext()) { Cookie c = it.next(); - if(c.getExpiration() == 0){ + if(c.getExpiration() == 0) { it.remove(); } } } - + /** * Clears all cookies from this cookie jar. */ - public void clearAllCookies(){ + public void clearAllCookies() { cookies.clear(); } - + /** * Returns a copy of all the cookies in this cookie jar. - * @return + * + * @return */ - public Set getAllCookies(){ + public Set getAllCookies() { return new TreeSet(cookies); } diff --git a/src/main/java/com/laytonsmith/PureUtilities/Web/HTTPHeader.java b/src/main/java/com/laytonsmith/PureUtilities/Web/HTTPHeader.java index 32377a2711..d4d8b960c0 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Web/HTTPHeader.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Web/HTTPHeader.java @@ -1,10 +1,10 @@ package com.laytonsmith.PureUtilities.Web; /** - * This object wraps an HTTP header, which contains the header name - * and value. + * This object wraps an HTTP header, which contains the header name and value. */ public final class HTTPHeader { + private final String header; private final String value; diff --git a/src/main/java/com/laytonsmith/PureUtilities/Web/HTTPHeaders.java b/src/main/java/com/laytonsmith/PureUtilities/Web/HTTPHeaders.java new file mode 100644 index 0000000000..f078ee699b --- /dev/null +++ b/src/main/java/com/laytonsmith/PureUtilities/Web/HTTPHeaders.java @@ -0,0 +1,253 @@ +package com.laytonsmith.PureUtilities.Web; + +import com.laytonsmith.PureUtilities.Common.StringUtils; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * This class wraps and represents a list of HTTP headers, and includes methods for easily parsing common headers. + */ +public class HTTPHeaders implements Iterable { + + private final List model; + + /** + * Creates a new HTTPHeaders object. + * @param model + */ + public HTTPHeaders(List model) { + this.model = model; + } + + public int size() { + return model.size(); + } + + @Override + public Iterator iterator() { + return model.iterator(); + } + + @Override + public String toString() { + StringBuilder b = new StringBuilder(); + for(HTTPHeader h : model) { + b.append(h.getHeader()).append(": ").append(h.getValue()).append("\n"); + } + return b.toString(); + } + + + + /** + * (Read the note below if you're using this for "Set-Cookie" headers!) + *

+ * Combines the values of all the given header with commas. + *


+	 * A sender MUST NOT generate multiple header fields with the same field
+	 * name in a message unless either the entire field value for that
+	 * header field is defined as a comma-separated list [i.e., #(values)]
+	 * or the header field is a well-known exception (as noted below).
+	 *
+	 * A recipient MAY combine multiple header fields with the same field
+	 * name into one "field-name: field-value" pair, without changing the
+	 * semantics of the message, by appending each subsequent field value to
+	 * the combined field value in order, separated by a comma.  The order
+	 * in which header fields with the same field name are received is
+	 * therefore significant to the interpretation of the combined field
+	 * value; a proxy MUST NOT change the order of these field values when
+	 * forwarding a message.
+	 * 
+ * + * Therefore, this method is a convenience method to implementing the correct semantics described here. If + * there are multiple headers with the same name, then they are concatenated together with a comma, and returned + * as if they were a single header. + * + * In practice, however, there is a caveat with Set-Cookie headers: + * + *

+	 * Note: In practice, the "Set-Cookie" header field ([RFC6265]) often
+	 * appears multiple times in a response message and does not use the
+	 * list syntax, violating the above requirements on multiple header
+	 * fields with the same name.  Since it cannot be combined into a
+	 * single field-value, recipients ought to handle "Set-Cookie" as a
+	 * special case while processing header fields.  (See Appendix A.2.3
+	 * of [Kri2001] for details.)
+	 * 
+ * + * In this case, generally one should just use {@link #getHeaders(java.lang.String)} and loop through the results. + * @param key The header to look up. + * @return + */ + public String getCombinedHeader(String key) { + return StringUtils.Join(getHeaders(key), ",").trim(); + } + + /** + * Gets the value of the first header returned. If the header isn't set, null is returned. + * + * Generally, you should use {@link #getCombinedHeader} instead, but this may not be desirable in all cases. + * + * @param key + * @return + */ + public String getFirstHeader(String key) { + for(HTTPHeader header : model) { + // header.getHeader() can return null. This means that it's the first header, the status header. + if(key == null && header.getHeader() == null) { + return header.getValue(); + } + if(key == null) { + continue; + } + if(key.equalsIgnoreCase(header.getHeader())) { + return header.getValue(); + } + } + return null; + } + + /** + * Returns a list of all the header names that are set in this request. + * + * @return + */ + public Set getHeaderNames() { + Set set = new HashSet<>(); + for(HTTPHeader h : model) { + if(h.getHeader() != null) { + set.add(h.getHeader()); + } + } + return set; + } + + /** + * Returns all the headers for a given key. If there are no headers set for this key, an empty list is returned. + * + * @param key + * @return + */ + public List getHeaders(String key) { + List list = new ArrayList<>(); + for(HTTPHeader header : model) { + if((header.getHeader() == null && key == null) || (header.getHeader() != null + && header.getHeader().equalsIgnoreCase(key))) { + list.add(header.getValue()); + } + } + return list; + } + + /** + * Returns the content type. If there was no content type present, null is returned. + * @return + */ + public ContentType getContentType() { + String header = getFirstHeader("Content-Type"); + if(header == null) { + return null; + } + return new ContentType(header); + } + + public static class MIMEType { + /** + * The full MIME type, for instance "text/html". Should never be null. + */ + public final String mediaType; + + /** + * The primary type of the media type. For instance in "text/html" it would be "text". Should never be null. + */ + public final String type; + + /** + * The subtype of the media type. For instance, in "text/html" it would be "html". Should never be null. + */ + public final String subtype; + + public MIMEType(String mediaType) { + this.mediaType = mediaType; + String[] parts = mediaType.split("/"); + this.type = parts[0]; + this.subtype = parts[1]; + } + } + + public static class ContentType { + /** + * The MIME type of the content. Should never be null. + */ + public final MIMEType mimeType; + + /** + * The charset of the content type. This will be null if not present, and should be null if the type was + * "multipart". If null, ISO-8859-1 is the default charset of HTTP 1.1, and is a reasonable default. + */ + public final String charset; + + /** + * In a multipart form, this is the boundary separator. Should always be null if the content type was not + * "multipart". + */ + public final String boundary; + + private static final Pattern CONTENT_TYPE_PATTERN = Pattern.compile("^(.*?)(?:;\\s*(.*?))?$"); + + public ContentType(String contentType) { + Matcher m = CONTENT_TYPE_PATTERN.matcher(contentType); + m.find(); + mimeType = new MIMEType(m.group(1)); + if(m.group(2) != null) { + String parameter = m.group(2); + String[] parts = parameter.split("="); + if("charset".equalsIgnoreCase(parts[0])) { + charset = parts[1]; + boundary = null; + } else if("boundary".equalsIgnoreCase(parts[0])) { + boundary = parts[1]; + charset = null; + } else { + charset = null; + boundary = null; + } + } else { + charset = null; + boundary = null; + } + } + } + + /** + * Reads the link headers (if present) and finds the link with the given relation. If there are no link headers, + * or there are but none with the specified relation, null is returned. Note that the returned link may be + * either relative or absolute, so if the value starts with /, one should use the existing domain as the url base. + * @param rel + * @return + */ + public String getLink(String rel) { + String links = getCombinedHeader("link"); + if(links.isEmpty()) { + return null; + } + List headers = Arrays.asList(links.split(",")); + for(String link : headers) { + link = link.trim(); + if(link.contains("rel=\"" + rel + "\"")) { + return link.replaceAll("<(.*)>.*", "$1"); + } + } + return null; + } + +} diff --git a/src/main/java/com/laytonsmith/PureUtilities/Web/HTTPMethod.java b/src/main/java/com/laytonsmith/PureUtilities/Web/HTTPMethod.java index ede13dd267..3de363dd72 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Web/HTTPMethod.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Web/HTTPMethod.java @@ -4,5 +4,5 @@ * This enum contains all the valid HTTP methods. */ public enum HTTPMethod { - POST, GET, HEAD, OPTIONS, PUT, DELETE, TRACE; + POST, GET, HEAD, OPTIONS, PUT, DELETE, TRACE, PATCH; } diff --git a/src/main/java/com/laytonsmith/PureUtilities/Web/HTTPResponse.java b/src/main/java/com/laytonsmith/PureUtilities/Web/HTTPResponse.java index 2fed85ec87..a822463b52 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Web/HTTPResponse.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Web/HTTPResponse.java @@ -1,8 +1,7 @@ package com.laytonsmith.PureUtilities.Web; +import java.io.UnsupportedEncodingException; import java.util.ArrayList; -import java.util.HashSet; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -11,111 +10,131 @@ * This class wraps all the data that an HTTP response contains. */ public final class HTTPResponse { + private String rawResponse = null; - private List headers = new LinkedList(); - private String responseText; - private int responseCode; - private String content; - private String httpVersion; + private final HTTPHeaders headers; + private final String responseText; + private final int responseCode; + private final byte[] content; + private final String httpVersion; /** - * Creates a new HTTP Response object, which wraps all the data that a HTTP response - * would contain. - * @param responseText The raw response text associated with the response code, for instance - * "OK" for a 200 response. + * Creates a new HTTP Response object, which wraps all the data that a HTTP response would contain. + * + * @param responseText The raw response text associated with the response code, for instance "OK" for a 200 + * response. * @param responseCode The response code, for instance 404. * @param headers The headers returned by the server * @param response The response body * @param httpVersion The HTTP version that the server is using, for instance "1.0" */ - public HTTPResponse(String responseText, int responseCode, Map> headers, - String response, String httpVersion) { + public HTTPResponse(String responseText, int responseCode, Map> headers, + byte[] response, String httpVersion) { this.responseText = responseText; this.responseCode = responseCode; - for (String key : headers.keySet()) { - for (String value : headers.get(key)) { - this.headers.add(new HTTPHeader(key, value)); + List h = new ArrayList<>(); + for(String key : headers.keySet()) { + for(String value : headers.get(key)) { + h.add(new HTTPHeader(key, value)); } } + this.headers = new HTTPHeaders(h); this.content = response; this.httpVersion = httpVersion; } /** - * Gets the contents of this HTTP request. If this request was a - * download request, this will be null. + * Gets the contents of this HTTP request. If this request was a download request, this will be null. * * @return */ - public String getContent() { + public byte[] getContent() { return this.content; } - + /** - * Returns the HTTP version that the server is using. This string will - * be in the format HTTP/1.0 for instance. - * @return + * Returns the content as UTF-8 text. + * @return */ - public String getHttpVersion(){ + public String getContentAsString() { + try { + return getContentAsString("UTF-8"); + } catch (UnsupportedEncodingException ex) { + throw new Error(ex); + } + } + + /** + * Returns the content as text with the specified encoding. + * @param encoding + * @return + * @throws UnsupportedEncodingException + */ + public String getContentAsString(String encoding) throws UnsupportedEncodingException { + return new String(getContent(), encoding); + } + + /** + * Returns the HTTP version that the server is using. This string will be in the format HTTP/1.0 for instance. + * + * @return + */ + public String getHttpVersion() { return httpVersion; } /** - * Gets the value of the first header returned. If the header - * isn't set, null is returned. + * Gets the value of the first header returned. If the header isn't set, null is returned. + * * @param key * @return + * @deprecated Use {@link #getHeaderObject()} instead. */ + @Deprecated public String getFirstHeader(String key) { - for (HTTPHeader header : headers) { - if (header.getHeader().equalsIgnoreCase(key)) { - return header.getValue(); - } - } - return null; + return headers.getFirstHeader(key); } /** * Returns a list of all the header names that are set in this request. - * @return + * + * @return + * @deprecated Use {@link #getHeaderObject()} instead. */ + @Deprecated public Set getHeaderNames() { - Set set = new HashSet(); - for (HTTPHeader h : headers) { - if (h.getHeader() != null) { - set.add(h.getHeader()); - } - } - return set; + return headers.getHeaderNames(); } /** - * Returns all the headers for a given key. If there - * are no headers set for this key, an empty list is returned. + * Returns all the headers for a given key. If there are no headers set for this key, an empty list is returned. + * * @param key - * @return + * @return + * @deprecated Use {@link #getHeaderObject()} instead. */ + @Deprecated public List getHeaders(String key) { - List list = new ArrayList(); - for (HTTPHeader header : headers) { - if ((header.getHeader() == null && key == null) || (header.getHeader() != null && header.getHeader().equals(key))) { - list.add(header.getValue()); - } - } - return list; + return headers.getHeaders(key); + } + + public HTTPHeaders getHeaderObject() { + return headers; } /** * Returns the response code, for instance 404. - * @return + * + * @return */ public int getResponseCode() { return responseCode; } - + /** * Returns the response text, for instance for a 404 page, "Not Found" - * @return + * + * @return */ public String getResponseText() { return responseText; @@ -123,15 +142,15 @@ public String getResponseText() { @Override public String toString() { - if (rawResponse == null) { + if(rawResponse == null) { rawResponse = "HTTP/" + httpVersion + " " + responseCode + " " + responseText + "\n"; - for (HTTPHeader h : headers) { - if (h.getHeader() == null) { + for(HTTPHeader h : headers) { + if(h.getHeader() == null) { continue; } rawResponse += h.getHeader() + ": " + h.getValue() + "\n"; } - rawResponse += "\n" + content; + rawResponse += "\n" + ""; } return rawResponse; } diff --git a/src/main/java/com/laytonsmith/PureUtilities/Web/HTTPResponseCallback.java b/src/main/java/com/laytonsmith/PureUtilities/Web/HTTPResponseCallback.java index 86775325e2..3b6e3a9c2f 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Web/HTTPResponseCallback.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Web/HTTPResponseCallback.java @@ -1,20 +1,22 @@ package com.laytonsmith.PureUtilities.Web; /** - * This interface should be implemented to respond to async web requests - * that are managed from within the WebUtility class. + * This interface should be implemented to respond to async web requests that are managed from within the WebUtility + * class. */ public interface HTTPResponseCallback { /** * If the call is successful, the HTTPResponse is returned here. - * @param response + * + * @param response */ public void response(HTTPResponse response); /** * If the call is unsuccessful, the Throwable should be handled here. - * @param error + * + * @param error */ public void error(Throwable error); diff --git a/src/main/java/com/laytonsmith/PureUtilities/Web/RawHTTPResponse.java b/src/main/java/com/laytonsmith/PureUtilities/Web/RawHTTPResponse.java index 930bc8c54f..87e63fd045 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Web/RawHTTPResponse.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Web/RawHTTPResponse.java @@ -1,4 +1,3 @@ - package com.laytonsmith.PureUtilities.Web; import java.io.InputStream; @@ -8,28 +7,32 @@ * */ public class RawHTTPResponse { - private HttpURLConnection connection; - private InputStream stream; - public RawHTTPResponse(HttpURLConnection connection, InputStream stream){ + + private final HttpURLConnection connection; + private final InputStream stream; + + public RawHTTPResponse(HttpURLConnection connection, InputStream stream) { this.connection = connection; this.stream = stream; } /** * Returns the underlying HttpURLConnection. - * @return + * + * @return */ public HttpURLConnection getConnection() { return connection; } /** - * Returns the raw HTTP stream. This is already wrapped in an appropriate - * decoding stream if the content is compressed. - * @return + * Returns the raw HTTP stream. This is already wrapped in an appropriate decoding stream if the content is + * compressed. + * + * @return */ public InputStream getStream() { return stream; } - + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/Web/RequestSettings.java b/src/main/java/com/laytonsmith/PureUtilities/Web/RequestSettings.java index c45038cc72..4a5d68e662 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Web/RequestSettings.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Web/RequestSettings.java @@ -1,211 +1,261 @@ package com.laytonsmith.PureUtilities.Web; +import com.laytonsmith.PureUtilities.Common.FileWriteMode; import java.io.File; import java.net.Proxy; import java.util.Arrays; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.logging.Logger; /** * An object that wraps the HTTP request settings used in the WebUtility class. */ public class RequestSettings { + private HTTPMethod method = HTTPMethod.GET; private Map> headers = null; private Map> parameters = null; + private Map> queryParameters = null; private CookieJar cookieJar = null; private boolean followRedirects = true; private int timeout = 60000; private String username = null; private String password = null; private Proxy proxy = null; - private String rawParameter; + private byte[] rawParameter; private File downloadTo; - + private FileWriteMode downloadStrategy = FileWriteMode.SAFE_WRITE; + private boolean blocking = false; + private boolean disableCertChecking = false; + private boolean useDefaultTrustStore = true; + private LinkedHashMap trustStore = new LinkedHashMap<>(); + @SuppressWarnings("NonConstantLogger") + private Logger logger; + private boolean disableDecompressionHandling = false; + /** - * + * * @param method The HTTP method to use - * @return + * @return */ - public RequestSettings setMethod(HTTPMethod method){ + public RequestSettings setMethod(HTTPMethod method) { this.method = method; return this; } - + /** - * + * * @return The HTTP method to use */ - public HTTPMethod getMethod(){ + public HTTPMethod getMethod() { return method; } - + /** - * + * * @param username The username to use in response to HTTP Basic authentication. Null ignores this parameter. * @param password The password to use in response to HTTP Basic authentication. Null ignores this parameter. - * @return + * @return */ - public RequestSettings setAuthenticationDetails(String username, String password){ + public RequestSettings setAuthenticationDetails(String username, String password) { this.username = username; this.password = password; return this; } - + /** - * + * * @param headers The HTTP headers to set in the request. - * @return + * @return */ - public RequestSettings setHeaders(Map> headers){ + public RequestSettings setHeaders(Map> headers) { this.headers = headers; return this; } - + /** - * + * * @return The HTTP headers to set in the request. */ - public Map> getHeaders(){ + public Map> getHeaders() { return headers; } - + /** - * - * @param parameters The parameters to be sent. Parameters can be also - * specified directly in the URL, and they will be merged. May be null. - * This is a convenience method for setComplexParameters, because that is - * technically the only way to set the parameters, because array parameters - * are supported, but often times this isn't needed, so this is a simpler - * setter. - * @return + * + * @param parameters The parameters to be sent. Parameters can be also specified directly in the URL, and they will + * be merged. May be null. This is a convenience method for setComplexParameters, because that is technically the + * only way to set the parameters, because array parameters are supported, but often times this isn't needed, so + * this is a simpler setter. + * @return */ - public RequestSettings setParameters(Map parameters){ - if(parameters == null){ + public RequestSettings setParameters(Map parameters) { + if(parameters == null) { this.parameters = null; return this; } else { - Map> p = new HashMap>(); - for(String key : parameters.keySet()){ + Map> p = new HashMap<>(); + for(String key : parameters.keySet()) { p.put(key, Arrays.asList(new String[]{parameters.get(key)})); } return setComplexParameters(p); } } + /** - * - * @param parameters The parameters to be sent. Parameters can be also - * specified directly in the URL, and they will be merged. May be null. - * @return + * + * @param parameters The parameters to be sent. Parameters can be also specified directly in the URL, and they will + * be merged. May be null. + * @return */ - public RequestSettings setComplexParameters(Map> parameters){ + public RequestSettings setComplexParameters(Map> parameters) { this.parameters = parameters; return this; } - + /** - * - * @return The parameters to be sent. Parameters can be also - * specified directly in the URL, and they will be merged. May be null. + * + * @return The parameters to be sent. Parameters can be also specified directly in the URL, and they will be merged. + * May be null. */ - public Map> getParameters(){ + public Map> getParameters() { return parameters; } - + + /** + * + * @param parameters The parameters to be sent. Parameters can be also specified directly in the URL, and they will + * be merged. May be null. This is a convenience method for setComplexParameters, because that is technically the + * only way to set the parameters, because array parameters are supported, but often times this isn't needed, so + * this is a simpler setter. + * @return + */ + public RequestSettings setQueryParameters(Map parameters) { + if(parameters == null) { + this.queryParameters = null; + return this; + } else { + Map> p = new HashMap<>(); + for(String key : parameters.keySet()) { + p.put(key, Arrays.asList(new String[]{parameters.get(key)})); + } + return setComplexQueryParameters(p); + } + } + + /** + * Sets the query parameters. Unlike the normal parameters, these are ALWAYS put in the query, regardless + * of whether or not the method is GET or POST, which can be useful when a protocol requires an empty post body + * as well as query parameters. If you are explicitly setting a post body, you may use either this or + * setParameters, as the effect will be the same. + * @param parameters + * @return + */ + public RequestSettings setComplexQueryParameters(Map> parameters) { + this.queryParameters = parameters; + return this; + } + + /** + * Returns the query parameters. + * @return + */ + public Map> getQueryParameters() { + return this.queryParameters; + } + /** - * - * @param cookieJar An instance of a cookie jar to use, or null if none - * is needed. Cookies will automatically be added and used from this - * instance. - * @return + * + * @param cookieJar An instance of a cookie jar to use, or null if none is needed. Cookies will automatically be + * added and used from this instance. + * @return */ - public RequestSettings setCookieJar(CookieJar cookieJar){ + public RequestSettings setCookieJar(CookieJar cookieJar) { this.cookieJar = cookieJar; return this; } - + /** - * + * * @param proxyAddress The proxy for this connection to use - * @return + * @return */ - public RequestSettings setProxy(Proxy proxy){ + public RequestSettings setProxy(Proxy proxy) { this.proxy = proxy; return this; } - + /** - * - * @return An instance of a cookie jar to use, or null if none - * is needed. Cookies will automatically be added and used from this - * instance. + * + * @return An instance of a cookie jar to use, or null if none is needed. Cookies will automatically be added and + * used from this instance. */ - public CookieJar getCookieJar(){ + public CookieJar getCookieJar() { return cookieJar; } - + /** - * - * @param followRedirects If 300 code responses should automatically be - * followed. - * @return + * + * @param followRedirects If 300 code responses should automatically be followed. + * @return */ - public RequestSettings setFollowRedirects(boolean followRedirects){ + public RequestSettings setFollowRedirects(boolean followRedirects) { this.followRedirects = followRedirects; return this; } - + /** - * - * @return If 300 code responses should automatically be - * followed. + * + * @return If 300 code responses should automatically be followed. */ - public boolean getFollowRedirects(){ + public boolean getFollowRedirects() { return followRedirects; } - + /** - * - * @param timeout Sets the timeout in ms for this connection. 0 means no timeout. If the timeout - * is reached, a SocketTimeoutException will be thrown. - * @return + * + * @param timeout Sets the timeout in ms for this connection. 0 means no timeout. If the timeout is reached, a + * SocketTimeoutException will be thrown. + * @return */ - public RequestSettings setTimeout(int timeout){ + public RequestSettings setTimeout(int timeout) { this.timeout = timeout; return this; } - + /** - * - * @return Sets the timeout in ms for this connection. 0 means no timeout. If the timeout - * is reached, a SocketTimeoutException will be thrown. + * + * @return Sets the timeout in ms for this connection. 0 means no timeout. If the timeout is reached, a + * SocketTimeoutException will be thrown. */ - public int getTimeout(){ + public int getTimeout() { return timeout; } - + /** - * + * * @return The username to use in response to HTTP Basic authentication. */ - public String getUsername(){ + public String getUsername() { return username; } - + /** - * + * * @return The password to use in response to HTTP Basic authentication. Null ignores this parameter. */ - public String getPassword(){ + public String getPassword() { return password; } - + /** - * + * * @return The proxy address for this connection to use */ - public Proxy getProxy(){ + public Proxy getProxy() { return proxy; } @@ -213,38 +263,178 @@ public Proxy getProxy(){ * * @return The raw parameter to send in a post request */ - public String getRawParameter() { + public byte[] getRawParameter() { return rawParameter; } - + /** - * + * * @param rawParamter The raw parameter to send in a post request - * @return + * @return */ - public RequestSettings setRawParameter(String rawParamter) { + public RequestSettings setRawParameter(byte[] rawParamter) { this.rawParameter = rawParamter; return this; } - + /** - * If this is not null, the resulting page will be downloaded to the - * specified file location. + * If this is not null, the resulting page will be downloaded to the specified file location. + * * @param downloadTo The file location to download to, or null. - * @return + * @return */ - public RequestSettings setDownloadTo(File downloadTo){ + public RequestSettings setDownloadTo(File downloadTo) { this.downloadTo = downloadTo; return this; } - + + /** + * Sets the download strategy. + * @param downloadStrategy + * @return + */ + public RequestSettings setDownloadStrategy(FileWriteMode downloadStrategy) { + this.downloadStrategy = downloadStrategy; + return this; + } + /** - * - * @return The file location to download to, or null if this shouldn't save the - * request as a file. + * + * @return The file location to download to, or null if this shouldn't save the request as a file. */ - public File getDownloadTo(){ + public File getDownloadTo() { return this.downloadTo; } - + + /** + * Returns the configured download strategy. The default is {@link FileWriteMode#SAFE_WRITE} + * @return + */ + public FileWriteMode getDownloadStrategy() { + return this.downloadStrategy; + } + + /** + * Sets the logger to use. If null, disables logging. + * + * @param logger + * @return + */ + public RequestSettings setLogger(Logger logger) { + this.logger = logger; + return this; + } + + /** + * @return The logger to use when making requests. May be null, in which case, it means to not do logging. + */ + public Logger getLogger() { + return this.logger; + } + + /** + * Sets whether or not this should be a blocking request + * + * @param blocking + * @return + */ + public RequestSettings setBlocking(boolean blocking) { + this.blocking = blocking; + return this; + } + + /** + * Returns whether or not this should be a blocking request + * + * @return + */ + public boolean getBlocking() { + return this.blocking; + } + + /** + * Sets whether or not cert checking is disabled. If this is true, NO certificate checking is done, and all + * certificates will be considered valid. If this is true, {@link #setUseDefaultTrustStore(boolean)} and + * {@link #setTrustStore(java.util.Map)} are ignored. + * + * @param check + * @return + */ + public RequestSettings setDisableCertChecking(boolean check) { + this.disableCertChecking = check; + return this; + } + + /** + * Returns whether or not the trust store should be disabled. Default is false. + * + * @return + */ + public boolean getDisableCertChecking() { + return this.disableCertChecking; + } + + /** + * Sets whether or not to use the default trust store. If false, then only certificates registered using + * {@link #setTrustStore(java.util.Map)} will be accepted. If this is false, and + * {@link #setTrustStore(java.util.Map)} is false, this effectively prevents any ssl connections. + * + * @param useDefaultTrustStore + * @return + */ + public RequestSettings setUseDefaultTrustStore(boolean useDefaultTrustStore) { + this.useDefaultTrustStore = useDefaultTrustStore; + return this; + } + + /** + * Returns whether or not the default trust store should be used. + * + * @return + */ + public boolean getUseDefaultTrustStore() { + return this.useDefaultTrustStore; + } + + /** + * Sets the trust store. Values should be in the form: "02 79 AB D6 97 19 A2 CB E8 79 11 B2 7F AF 8D": "SHA-256" + * where the key is the fingerprint, and the value is the encryption scheme. Note that the map is cloned, and the + * original map is not used. + * + * @param trustStore The trust store to use + * @return + */ + public RequestSettings setTrustStore(LinkedHashMap trustStore) { + this.trustStore = new LinkedHashMap<>(trustStore); + return this; + } + + /** + * Returns the trust store in use. Note that the map is cloned, and the original map is not used. + * + * @return + */ + public LinkedHashMap getTrustStore() { + return new LinkedHashMap<>(trustStore); + } + + /** + * Sets the disableCompressionHandling flag. If true, the content will be returned as is, no matter what the + * value of the Content-Encoding header is, and must be processed manually. + * @param disableCompressionHandling + * @return + */ + public RequestSettings setDisableCompressionHandling(boolean disableCompressionHandling) { + this.disableDecompressionHandling = disableCompressionHandling; + return this; + } + + /** + * Returns the disableCompressionHandling flag. Defaults to false. + * @return + */ + public boolean getDisableCompressionHandling() { + return this.disableDecompressionHandling; + } + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/Web/WebUtility.java b/src/main/java/com/laytonsmith/PureUtilities/Web/WebUtility.java index ca8f75f009..3001fe2952 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/Web/WebUtility.java +++ b/src/main/java/com/laytonsmith/PureUtilities/Web/WebUtility.java @@ -1,50 +1,90 @@ package com.laytonsmith.PureUtilities.Web; +import com.laytonsmith.PureUtilities.Common.ArrayUtils; +import com.laytonsmith.PureUtilities.Common.FileUtil; +import com.laytonsmith.PureUtilities.Common.FileWriteMode; +import com.laytonsmith.PureUtilities.Common.StreamUtils; import com.laytonsmith.PureUtilities.Common.StringUtils; +import java.io.BufferedInputStream; + import java.io.BufferedOutputStream; -import java.io.BufferedReader; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Proxy; +import java.net.Socket; import java.net.SocketTimeoutException; import java.net.URL; import java.net.URLEncoder; import java.net.UnknownHostException; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; +import java.util.logging.Level; +import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; import java.util.zip.GZIPInputStream; import java.util.zip.InflaterInputStream; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; import org.apache.commons.codec.binary.Base64; +import org.brotli.dec.BrotliInputStream; /** * Contains methods to simplify web connections. * - * + * */ public final class WebUtility { - + public static void main(String[] args) throws Exception { CookieJar stash = new CookieJar(); HTTPResponse resp = GetPage(new URL("http://www.google.com/"), HTTPMethod.GET, null, null, stash, true, 60000); - System.out.println(stash.getCookies(new URL("http://www.google.com"))); + StreamUtils.GetSystemOut().println(stash.getCookies(new URL("http://www.google.com"))); } + /** + * This is the list of encodings that this class supports. Generally speaking, this is the list that you should + * provide in the Accept-Encoding list. If you wish to support something other than this list, you should + * disable encoding support, and manage the decompression entirely yourself. + */ + public static final Set SUPPORTED_ENCODINGS + = Collections.unmodifiableSet(new HashSet<>( + Arrays.asList(new String[]{"gzip", "deflate", "br", "identity"}))); + private WebUtility() { } private static int urlRetrieverPoolId = 0; - private static ExecutorService urlRetrieverPool = Executors.newCachedThreadPool(new ThreadFactory() { + private static final ExecutorService URL_RETRIEVER_POOL = Executors.newCachedThreadPool(new ThreadFactory() { @Override public Thread newThread(Runnable r) { return new Thread(r, "URLRetrieverThread-" + (++urlRetrieverPoolId)); @@ -52,189 +92,489 @@ public Thread newThread(Runnable r) { }); /** - * Gets a web page based on the parameters specified. This is a blocking - * call, if you wish for it to be event driven, consider using the GetPage - * that requires a HTTPResponseCallback. + * Gets a web page based on the parameters specified. This is a blocking call, if you wish for it to be event + * driven, consider using the GetPage that requires a HTTPResponseCallback. * * @param url The url to navigate to * @param method The HTTP method to use - * @param parameters The parameters to be sent. Parameters can be also - * specified directly in the URL, and they will be merged. May be null. - * @param cookieStash An instance of a cookie stash to use, or null if none - * is needed. Cookies will automatically be added and used from this - * instance. - * @param followRedirects If 300 code responses should automatically be - * followed. - * @param timeout Sets the timeout in ms for this connection. 0 means no timeout. If the timeout - * is reached, a SocketTimeoutException will be thrown. + * @param parameters The parameters to be sent. Parameters can be also specified directly in the URL, and they will + * be merged. May be null. + * @param cookieStash An instance of a cookie stash to use, or null if none is needed. Cookies will automatically be + * added and used from this instance. + * @param followRedirects If 300 code responses should automatically be followed. + * @param timeout Sets the timeout in ms for this connection. 0 means no timeout. If the timeout is reached, a + * SocketTimeoutException will be thrown. * @return * @throws IOException */ - public static HTTPResponse GetPage(URL url, HTTPMethod method, Map> headers, + public static HTTPResponse GetPage(URL url, HTTPMethod method, Map> headers, Map parameters, CookieJar cookieStash, boolean followRedirects, int timeout) throws SocketTimeoutException, IOException { RequestSettings settings = new RequestSettings() .setMethod(method).setHeaders(headers).setParameters(parameters) .setCookieJar(cookieStash).setFollowRedirects(followRedirects).setTimeout(timeout); return GetPage(url, settings); } + /** - * Gets a web page based on the parameters specified. This is a blocking - * call, if you wish for it to be event driven, consider using the GetPage - * that requires a HTTPResponseCallback. - * + * Gets a web page based on the parameters specified. This is a blocking call, if you wish for it to be event + * driven, consider using the GetPage that requires a HTTPResponseCallback. + *

+ * The settings may have the following parameters: + *

    + *
  • method - The HTTP method to use
  • + *
  • parameters - The parameters to be sent. Parameters can be also specified directly in the URL, and they will + * be merged. May be null.
  • + *
  • cookieStash - An instance of a cookie stash to use, or null if none is needed. Cookies will automatically be + * added and used from this instance.
  • + *
  • followRedirects - If 300 code responses should automatically be followed.
  • + *
  • timeout - Sets the timeout in ms for this connection. 0 means no timeout. If the timeout is reached, a + * SocketTimeoutException will be thrown.
  • + *
  • username - The username to use in response to HTTP Basic authentication. Null ignores this parameter.
  • + *
  • password - The password to use in response to HTTP Basic authentication. Null ignores this parameter.
  • + *
* @param url The url to navigate to - * @param method The HTTP method to use - * @param parameters The parameters to be sent. Parameters can be also - * specified directly in the URL, and they will be merged. May be null. - * @param cookieStash An instance of a cookie stash to use, or null if none - * is needed. Cookies will automatically be added and used from this - * instance. - * @param followRedirects If 300 code responses should automatically be - * followed. - * @param timeout Sets the timeout in ms for this connection. 0 means no timeout. If the timeout - * is reached, a SocketTimeoutException will be thrown. - * @param username The username to use in response to HTTP Basic authentication. Null ignores this parameter. - * @param password The password to use in response to HTTP Basic authentication. Null ignores this parameter. + * @param settings The settings to use for this request * @return - * @throws IOException + * @throws SocketTimeoutException If the request took longer than the configured timeout + * @throws IOException If the connection could not be made properly */ public static HTTPResponse GetPage(URL url, RequestSettings settings) throws SocketTimeoutException, IOException { + // If SAFE_WRITE is set, there's no reason to do the download given we know it will fail later, so let's fail + // fast, and do the check here + if(settings == null) { + settings = new RequestSettings(); + } + if(settings.getDownloadTo() != null && settings.getDownloadStrategy() == FileWriteMode.SAFE_WRITE) { + if(settings.getDownloadTo().exists()) { + throw new IOException("Refusing to download file, destination path already exists [" + + settings.getDownloadTo() + "]"); + } + } CookieJar cookieStash = settings.getCookieJar(); RawHTTPResponse response = getWebStream(url, settings); - StringBuilder b = null; - BufferedReader in = new BufferedReader(new InputStreamReader(response.getStream())); - if(settings.getDownloadTo() == null){ - b = new StringBuilder(); - String line; - while ((line = in.readLine()) != null) { - b.append(line).append("\n"); + byte[] b = null; + InputStream in = new BufferedInputStream(response.getStream()); + if(settings.getDownloadTo() == null) { + if(settings.getLogger() != null) { + settings.getLogger().log(Level.INFO, "Reading in response body"); + } + try { + b = StreamUtils.GetBytes(in); + } finally { + in.close(); } - in.close(); } else { - int r; - OutputStream out = new BufferedOutputStream(new FileOutputStream(settings.getDownloadTo())); - while((r = in.read()) != -1){ - out.write(r); + if(settings.getLogger() != null) { + settings.getLogger().log(Level.INFO, "Saving file to [{0}] using {1} strategy", + new Object[]{settings.getDownloadTo(), settings.getDownloadStrategy()}); } - try{ - out.close(); + try { + FileUtil.write(StreamUtils.GetBytes(in), settings.getDownloadTo(), settings.getDownloadStrategy(), true); } finally { in.close(); } } //Assume 1.0 if something breaks String httpVersion = "1.0"; - Matcher m = Pattern.compile("HTTP/(\\d\\+.\\d+).*").matcher(response.getConnection().getHeaderField(0)); - if(m.find()){ + Matcher m = Pattern.compile("HTTP/(\\d+.\\d+).*").matcher(response.getConnection().getHeaderField(0)); + if(m.find()) { httpVersion = m.group(1); } - HTTPResponse resp = new HTTPResponse(response.getConnection().getResponseMessage(), - response.getConnection().getResponseCode(), response.getConnection().getHeaderFields(), b==null?null:b.toString(), httpVersion); - if (cookieStash != null && resp.getHeaderNames().contains("Set-Cookie")) { + HTTPResponse resp = new HTTPResponse(response.getConnection().getResponseMessage(), + response.getConnection().getResponseCode(), response.getConnection().getHeaderFields(), b, httpVersion); + if(cookieStash != null && resp.getHeaderNames().contains("Set-Cookie")) { //We need to add the cookie to the stash - for (String h : resp.getHeaders("Set-Cookie")) { + for(String h : resp.getHeaders("Set-Cookie")) { cookieStash.addCookie(new Cookie(h, url)); } } return resp; } - + + /** + * Makes an asynchronous call to a URL, and runs the callback when finished. + */ + public static void GetPage(final URL url, final RequestSettings settings, final HTTPResponseCallback callback) { + URL_RETRIEVER_POOL.submit(new Runnable() { + @Override + public void run() { + try { + HTTPResponse response = GetPage(url, settings); + if(callback == null) { + return; + } + callback.response(response); + } catch (IOException ex) { + if(callback == null) { + return; + } + callback.error(ex); + } + } + }); + } + /** - * Returns the raw web stream. Cookies are used to initiate the request, but - * the cookie jar isn't updated with the received cookies. + * A very simple convenience method to get a page, using all the default settings found in {@link RequestSettings}. + * + * @param url + * @return + */ + public static HTTPResponse GetPage(URL url) throws IOException { + return GetPage(url, null); + } + + /** + * A very simple convenience method to get a page using a string url. + * + * @param url + * @return + * @throws IOException + */ + public static HTTPResponse GetPage(String url) throws IOException { + return GetPage(new URL(url)); + } + + /** + * Returns the raw web stream. Cookies are used to initiate the request, but the cookie jar isn't updated with the + * received cookies. + * * @param url * @param settings * @return * @throws SocketTimeoutException - * @throws IOException + * @throws IOException */ - public static RawHTTPResponse getWebStream(URL url, RequestSettings settings) throws SocketTimeoutException, IOException{ - if(settings == null){ - settings = new RequestSettings(); + public static RawHTTPResponse getWebStream(URL url, RequestSettings requestSettings) + throws SocketTimeoutException, IOException { + if(requestSettings == null) { + requestSettings = new RequestSettings(); } + final RequestSettings settings = requestSettings; + Logger logger = settings.getLogger(); HTTPMethod method = settings.getMethod(); Map> headers = settings.getHeaders(); Map> parameters = settings.getParameters(); + Map> queryParameters = settings.getQueryParameters(); CookieJar cookieStash = settings.getCookieJar(); boolean followRedirects = settings.getFollowRedirects(); final int timeout = settings.getTimeout(); String username = settings.getUsername(); String password = settings.getPassword(); + if(logger != null) { + logger.log(Level.INFO, "Using the following settings:\n" + + "HTTP method: {0}\n" + + "Headers: {1}\n" + + "Parameters: {2}\n" + + "Raw parameter Length: {3}\n" + + "Cookie stash: {4}\n" + + "Follow redirects? {5}\n" + + "Timeout: {6}\n" + + "Username: {7}\n" + + "Password length: {8}\n", + new Object[]{method, headers, parameters, + settings.getRawParameter() == null ? "null" : settings.getRawParameter().length, cookieStash, + followRedirects, timeout, username, password == null ? "null" : password.length()}); + } //First, let's check to see that the url is properly formatted. If there are parameters, - //and this is a GET request, we want to tack them on to the end. - if (parameters != null && !parameters.isEmpty() && method == HTTPMethod.GET) { + //and this is a GET request, we want to tack them on to the end. OR, if there is a raw parameter and parameters, + //and this is a post request, put the parameters on anyways. + if(parameters != null && !parameters.isEmpty() + && (method == HTTPMethod.GET + || (method != HTTPMethod.GET + && settings.getRawParameter() != null && settings.getRawParameter().length != 0))) { StringBuilder b = new StringBuilder(url.getQuery() == null ? "" : url.getQuery()); - if (b.length() != 0) { + if(b.length() != 0) { b.append("&"); } - b.append(encodeParameters(parameters)); + b.append(encodeListParameters(parameters)); + // Setting this to null avoids further processing below + parameters = null; String query = b.toString(); url = new URL(url.getProtocol(), url.getHost(), url.getPort(), url.getPath() + "?" + query); } - + + if(queryParameters != null && !queryParameters.isEmpty()) { + String query = url.getQuery(); + if(query == null) { + query = "?"; + } + query += encodeListParameters(queryParameters); + url = new URL(url.getProtocol(), url.getHost(), url.getPort(), url.getPath() + query); + } + if(logger != null) { + logger.log(Level.INFO, "Using url: {0}", url); + } + Proxy proxy; - if(settings.getProxy() == null){ + if(settings.getProxy() == null) { proxy = Proxy.NO_PROXY; } else { proxy = settings.getProxy(); } - InetSocketAddress addr = (InetSocketAddress)proxy.address(); - if(addr != null){ - if(addr.isUnresolved()){ + if(logger != null) { + logger.log(Level.INFO, "Using proxy: {0}", proxy); + } + InetSocketAddress addr = (InetSocketAddress) proxy.address(); + if(addr != null) { + if(addr.isUnresolved()) { throw new IOException("Could not resolve the proxy address: " + addr.toString()); } } + if(logger != null) { + logger.log(Level.INFO, "Opening connection..."); + } //FIXME: When given a bad proxy, this causes it to stall forever HttpURLConnection conn = (HttpURLConnection) url.openConnection(/*proxy*/); + if(conn instanceof HttpsURLConnection + && (settings.getDisableCertChecking() || settings.getUseDefaultTrustStore() == false + || !settings.getTrustStore().isEmpty())) { + HttpsURLConnection conns = (HttpsURLConnection) conn; + // User has requested special handling in the certificates. + + final SSLContext sslc; + try { + sslc = SSLContext.getInstance("SSL"); + } catch (NoSuchAlgorithmException ex) { + throw new IOException(ex); + } + TrustManager defaultTrustManager = null; + { + if(settings.getUseDefaultTrustStore()) { + TrustManagerFactory tmf; + try { + tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + } catch (NoSuchAlgorithmException ex) { + throw new RuntimeException(ex); + } + try { + tmf.init((KeyStore) null); + } catch (KeyStoreException ex) { + throw new IOException(ex); + } + for(TrustManager tm : tmf.getTrustManagers()) { + if(tm instanceof X509TrustManager) { + defaultTrustManager = tm; + break; + } + } + } else { + defaultTrustManager = null; + } + } + final X509TrustManager finalDefaultTrustManager = (X509TrustManager) defaultTrustManager; + final TrustManager[] overrideTrustManager = new TrustManager[]{ + new X509TrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException { + // Hmm. Not sure when this would be used. Always throw for now. + throw new CertificateException("Not supported yet"); + } + + @Override + public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException { + if(settings.getDisableCertChecking()) { + // No cert checking, all pass + return; + } + boolean trusted = true; + if(finalDefaultTrustManager != null) { + try { + finalDefaultTrustManager.checkClientTrusted(xcs, string); + } catch (CertificateException ex) { + trusted = false; + } + } + if(trusted) { + return; + } + // If any of the certificates are trusted, then the whole chain is trusted + for(X509Certificate c : xcs) { + // Unfortunately, we do not know what schemes to use, so we must walk through each + // trust store item one at a time. We have a documented guarantee that we will walk + // this list from top to bottom, so we have a linked hash map. + LinkedHashMap ts = settings.getTrustStore(); + for(String fingerprint : ts.keySet()) { + fingerprint = fingerprint.toLowerCase().replace(" ", ""); + try { + String scheme = ts.get(fingerprint); + String fp = getThumbPrint(c, scheme).toLowerCase().replace(" ", ""); + if(fp.equals(fingerprint)) { + return; + } + } catch (NoSuchAlgorithmException | CertificateEncodingException ex) { + throw new RuntimeException(ex); + } + } + } + // None of the certificates matched, so throw an exception + throw new CertificateException(); + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + if(settings.getDisableCertChecking()) { + return new X509Certificate[0]; + } + if(finalDefaultTrustManager != null) { + return finalDefaultTrustManager.getAcceptedIssuers(); + } + return new X509Certificate[0]; + } + } + }; + try { + sslc.init(null, overrideTrustManager, new java.security.SecureRandom()); + } catch (KeyManagementException ex) { + throw new IOException(ex); + } + final SSLSocketFactory ssf; + ssf = sslc.getSocketFactory(); + conns.setSSLSocketFactory(new SSLSocketFactory() { + + @Override + public String[] getDefaultCipherSuites() { + return ssf.getDefaultCipherSuites(); + } + + @Override + public String[] getSupportedCipherSuites() { + return ssf.getSupportedCipherSuites(); + } + + @Override + public Socket createSocket(Socket socket, String string, int i, boolean bln) throws IOException { + return ssf.createSocket(socket, string, i, bln); + } + + @Override + public Socket createSocket(String string, int i) throws IOException, UnknownHostException { + return ssf.createSocket(string, i); + } + + @Override + public Socket createSocket(String string, int i, InetAddress ia, int i1) throws IOException, UnknownHostException { + return ssf.createSocket(string, i, ia, i1); + } + + @Override + public Socket createSocket(InetAddress ia, int i) throws IOException { + return ssf.createSocket(ia, i); + } + + @Override + public Socket createSocket(InetAddress ia, int i, InetAddress ia1, int i1) throws IOException { + return ssf.createSocket(ia, i, ia1, i1); + } + }); + } conn.setConnectTimeout(timeout); + conn.setReadTimeout(timeout); conn.setInstanceFollowRedirects(followRedirects); - if (cookieStash != null) { + if(cookieStash != null) { String cookies = cookieStash.getCookies(url); - if (cookies != null) { + if(cookies != null) { conn.setRequestProperty("Cookie", cookies); } } - if(username != null && password != null){ + if(username != null && password != null) { + if(logger != null) { + logger.log(Level.INFO, "Using Username/Password authentication, adding Authorization header"); + } conn.setRequestProperty("Authorization", "Basic " + new String(Base64.encodeBase64((username + ":" + password).getBytes("UTF-8")), "UTF-8")); } - if (headers != null) { - for (String key : headers.keySet()) { + if(headers != null) { + for(String key : headers.keySet()) { conn.setRequestProperty(key, StringUtils.Join(headers.get(key), ",")); } } conn.setRequestMethod(method.name()); - if (method == HTTPMethod.POST) { + if((parameters != null && !parameters.isEmpty() && !method.equals(HTTPMethod.GET)) + || settings.getRawParameter() != null) { + if(logger != null) { + if(method.equals(HTTPMethod.GET)) { + logger.log(Level.WARNING, "Method was set to GET, but raw parameter data was provided, so method" + + " is changing to POST."); + } + } conn.setDoOutput(true); - String params = ""; - if(parameters != null && !parameters.isEmpty()){ - params = encodeParameters(parameters); + byte[] params = ArrayUtils.EMPTY_BYTE_ARRAY; + if(parameters != null && !parameters.isEmpty()) { + if(logger != null) { + logger.log(Level.INFO, "Parameters are added, and content type set to application/x-www-form-urlencoded"); + } + params = encodeListParameters(parameters).getBytes("UTF-8"); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); - } else if(settings.getRawParameter() != null){ + } else if(settings.getRawParameter() != null) { + if(logger != null) { + logger.log(Level.INFO, "Raw parameter is added"); + } params = settings.getRawParameter(); } - conn.setRequestProperty("Content-Length", Integer.toString(params.length())); + conn.setRequestProperty("Content-Length", Integer.toString(params.length)); + if(logger != null) { + logger.log(Level.INFO, "Content length is {0}", params.length); + logger.log(Level.INFO, "Writing out request now"); + } OutputStream os = new BufferedOutputStream(conn.getOutputStream()); - WriteStringToOutputStream(params, os); + os.write(params); os.close(); } - + if(logger != null) { + logger.log(Level.INFO, "Output sent"); + } InputStream is; - try{ + try { is = conn.getInputStream(); - } catch(UnknownHostException e){ + } catch (UnknownHostException e) { throw e; - } catch(Exception e){ + } catch (Exception e) { + if(logger != null) { + logger.log(Level.SEVERE, "Exception occurred, {0} response from server", conn.getResponseCode()); + } + if(e instanceof SSLHandshakeException) { + // The certificate was not valid, and the input stream will be null anyways, so just throw at this + // point. + throw new IOException("Invalid SSL certificate for " + url.getHost() + ". Refusing to connect."); + } is = conn.getErrorStream(); } - if("x-gzip".equals(conn.getContentEncoding()) || "gzip".equals(conn.getContentEncoding())){ - is = new GZIPInputStream(is); - } else if("deflate".equals(conn.getContentEncoding())){ - is = new InflaterInputStream(is); - } else if("identity".equals(conn.getContentEncoding())){ - //This is the default, meaning no transformation is needed. + + if(!settings.getDisableCompressionHandling() && conn.getContentEncoding() != null) { + /* + The HTTP spec for Content-Encoding specifies that multiple comma separated values can be provided. Where + more than one is provided, this means that the content was compressed multiple times, in the specified order. + Given that, we must loop through the list, wrapping the input stream in the given decompression handlers. + In practice, this will only loop once though. + */ + List compression + = Stream.of(conn.getContentEncoding().split(",")).map((e) -> e.trim()).collect(Collectors.toList()); + for(String scheme : compression) { + if("x-gzip".equals(scheme) || "gzip".equals(scheme)) { + if(logger != null) { + logger.log(Level.INFO, "Response is gzipped, using a GZIPInputStream"); + } + is = new GZIPInputStream(is); + } else if("deflate".equals(scheme)) { + if(logger != null) { + logger.log(Level.INFO, "Response is zipped, using an InflaterInputStream"); + } + is = new InflaterInputStream(is); + } else if("br".equals(scheme)) { + if(logger != null) { + logger.log(Level.INFO, "Response is Brotli compressed, using a BrotliInputStream"); + } + is = new BrotliInputStream(is); + } else if("identity".equals(scheme)) { + //This is the default, meaning no transformation is needed. + if(logger != null) { + logger.log(Level.INFO, "Response is not compressed"); + } + } + } } - if(is == null){ - throw new IOException("Could not connnect to " + url); + if(is == null) { + throw new IOException("Could not connect to " + url); } return new RawHTTPResponse(conn, is); } @@ -245,27 +585,48 @@ public static RawHTTPResponse getWebStream(URL url, RequestSettings settings) th * @param parameters * @return */ - public static String encodeParameters(Map> parameters) { - if (parameters == null) { + public static String encodeParameters(Map parameters) { + Map> p = new HashMap<>(); + for(Map.Entry e : parameters.entrySet()) { + List list = new ArrayList<>(); + list.add(e.getValue()); + p.put(e.getKey(), list); + } + return encodeListParameters(p); + } + + /** + * Returns a properly encoded string of parameters. Array types are returned using bracket notation, + * for instance, if the input is {@code {a: [1, 2]}}, then the output will be {@code a[]=1&a[]=2}. + * + * @param parameters + * @return The properly encoded string of parameters. + */ + public static String encodeListParameters(Map> parameters) { + if(parameters == null) { return ""; } StringBuilder b = new StringBuilder(); boolean first = true; - for (String key : parameters.keySet()) { - if (!first) { + for(String key : parameters.keySet()) { + if(!first) { b.append("&"); } first = false; - List values = parameters.get(key); try { - if(values.size() == 1){ - String value = values.get(0); - b.append(URLEncoder.encode(key, "UTF-8")).append("=").append(URLEncoder.encode(value, "UTF-8")); + if(values.size() == 1) { + String value = values.get(0); + b.append(URLEncoder.encode(key, "UTF-8")).append("=").append(URLEncoder.encode(value, "UTF-8")); } else { - for(String value : values){ - b.append(URLEncoder.encode(key + "[]", "UTF-8")).append("=").append(URLEncoder.encode(value, "UTF-8")); + boolean innerFirst = true; + for(String value : values) { + if(!innerFirst) { + b.append("&"); } + innerFirst = false; + b.append(URLEncoder.encode(key + "[]", "UTF-8")).append("=").append(URLEncoder.encode(value, "UTF-8")); + } } } catch (UnsupportedEncodingException ex) { throw new Error(ex); @@ -274,92 +635,129 @@ public static String encodeParameters(Map> parameters) { return b.toString(); } - private static void WriteStringToOutputStream(String data, OutputStream os) throws IOException { - for (Character c : data.toCharArray()) { - os.write((int) c.charValue()); - } - } - /** - * A very simple convenience method to get a page, using all the default settings - * found in {@link RequestSettings}. + * A very simple convenience method to get a page. Only the contents are returned by this method. + * It is assumed that the content is a UTF-8 formatted string, and is not binary content. * * @param url * @return + * @throws IOException */ - public static HTTPResponse GetPage(URL url) throws IOException { - return GetPage(url, null); + public static String GetPageContents(URL url) throws IOException { + return new String(GetPage(url).getContent(), "UTF-8"); } /** - * A very simple convenience method to get a page using a string url. + * A very simple convenience method to get a page. Only the contents are returned by this method. + * It is assumed that the content is a UTF-8 formatted string, and is not binary content. * * @param url * @return * @throws IOException */ - public static HTTPResponse GetPage(String url) throws IOException { - return GetPage(new URL(url)); + public static String GetPageContents(String url) throws IOException { + return new String(GetPage(url).getContent(), "UTF-8"); } /** - * A very simple convenience method to get a page. Only the contents are - * returned by this method. + * A very simple convenience method to get a page. Only the contents are returned by this method. + * This supports returning binary content. * * @param url * @return * @throws IOException */ - public static String GetPageContents(URL url) throws IOException { + public static byte[] GetPageContentsBinary(URL url) throws IOException { return GetPage(url).getContent(); } /** - * A very simple convenience method to get a page. Only the contents are - * returned by this method. + * A very simple convenience method to get a page. Only the contents are returned by this method. + * This supports returning binary content. * * @param url * @return * @throws IOException */ - public static String GetPageContents(String url) throws IOException { + public static byte[] GetPageContentsBinary(String url) throws IOException { return GetPage(url).getContent(); } /** - * Makes an asynchronous call to a URL, and runs the callback when finished. + * Given a query string "a=1&b=2", returns a map of that data. Note that this method does not properly + * support array values, which are generally supported in the url format, so "a[]=1&a[]=2" and "a=1&a=2", + * while technically + * allowed in the specification, will not be returned correctly here, and the key will be a[]/a, and the value + * will be either 1 or 2, which one is selected is undefined. If you are 100% certain the query string + * will not contain array values, it is safe to use this method anyways, but if there is the possibility + * of array values being present, use {@link #getQueryMapList} instead. + * + * @param query + * @return */ - public static void GetPage(final URL url, final RequestSettings settings, final HTTPResponseCallback callback) { - urlRetrieverPool.submit(new Runnable() { - @Override - public void run() { - try { - HTTPResponse response = GetPage(url, settings); - if (callback == null) { - return; - } - callback.response(response); - } catch (IOException ex) { - if (callback == null) { - return; - } - callback.error(ex); - } - } - }); - } - public static Map getQueryMap(String query) { - Map map = new HashMap(); - if(query == null){ + Map map = new HashMap<>(); + if(query == null) { return map; } String[] params = query.split("&"); - for (String param : params) { + for(String param : params) { String name = param.split("=")[0]; String value = param.split("=")[1]; map.put(name, value); } return map; } + + /** + * Given a query string "a=1&b=2", returns a map of that data. Note that this method properly + * supports array values, so "a[]=1&a[]=2" and "a=1&a=2", will both return a map like + * {@code {a: [1, 2]}}. In any case, multidimensional arrays are not supported. + * + * @param query + * @return + */ + public static Map> getQueryMapList(String query) { + Map> map = new HashMap<>(); + if(query == null) { + return map; + } + String[] params = query.split("&"); + for(String param : params) { + String name = param.split("=")[0]; + String value = param.split("=")[1]; + if(name.endsWith("[]")) { + name = name.substring(0, name.length() - 2); + } + List values; + if(map.containsKey(name)) { + values = map.get(name); + } else { + values = new ArrayList<>(); + map.put(name, values); + } + values.add(value); + } + return map; + } + + /** + * Given an X509Certificate, calculates and returns the fingerprint in the given encryption scheme + * + * @param cert The certificate to get the fingerprint from + * @param encryptionScheme The encryption scheme, for instance "SHA-1". + * @return The hex fingerprint + * @throws NoSuchAlgorithmException + * @throws CertificateEncodingException + */ + public static String getThumbPrint(X509Certificate cert, String encryptionScheme) + throws NoSuchAlgorithmException, CertificateEncodingException { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + byte[] der = cert.getEncoded(); + md.update(der); + byte[] digest = md.digest(); + return StringUtils.toHex(digest); + + } + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/XMLDocument.java b/src/main/java/com/laytonsmith/PureUtilities/XMLDocument.java index 8ba6720ccb..cbb3cb9e46 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/XMLDocument.java +++ b/src/main/java/com/laytonsmith/PureUtilities/XMLDocument.java @@ -30,13 +30,12 @@ import org.xml.sax.SAXException; /** - * This class abstracts up and simplifies XML document parsing. You give it an XML - * string, and it gives you the ability to manipulate and query the document. This works - * via a DOM implementation. - * + * This class abstracts up and simplifies XML document parsing. You give it an XML string, and it gives you the ability + * to manipulate and query the document. This works via a DOM implementation. + * */ public class XMLDocument { - + private DocumentBuilder docBuilder; private Document doc; private XPath xpath; @@ -44,30 +43,31 @@ public class XMLDocument { private boolean prettyDirty = true; private String uglyRender; private String prettyRender; - + /** * Creates a new, blank XMLDocument. */ - public XMLDocument(){ + public XMLDocument() { try { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(false); docBuilder = dbf.newDocumentBuilder(); doc = docBuilder.newDocument(); - XPathFactory xpf = XPathFactory.newInstance(XPathFactory.DEFAULT_OBJECT_MODEL_URI, + XPathFactory xpf = XPathFactory.newInstance(XPathFactory.DEFAULT_OBJECT_MODEL_URI, "com.sun.org.apache.xpath.internal.jaxp.XPathFactoryImpl", XMLDocument.class.getClassLoader()); xpath = xpf.newXPath(); } catch (ParserConfigurationException | XPathFactoryConfigurationException ex) { throw new RuntimeException(ex); } } - + /** * Given an XML document in a string, creates a new XMLDocument. - * @param document + * + * @param document * @throws IOException If any IO error occurs */ - public XMLDocument(String document, String encoding) throws UnsupportedEncodingException, SAXException{ + public XMLDocument(String document, String encoding) throws UnsupportedEncodingException, SAXException { this(); try { doc = docBuilder.parse(new ByteArrayInputStream(document.getBytes(encoding))); @@ -75,13 +75,14 @@ public XMLDocument(String document, String encoding) throws UnsupportedEncodingE throw new RuntimeException(ex); } } - + /** * Creates a new XMLDocument from an XML string, assuming UTF-8 encoding. + * * @param document - * @throws SAXException + * @throws SAXException */ - public XMLDocument(String document) throws SAXException{ + public XMLDocument(String document) throws SAXException { this(); try { doc = docBuilder.parse(new ByteArrayInputStream(document.getBytes("UTF-8"))); @@ -89,56 +90,58 @@ public XMLDocument(String document) throws SAXException{ throw new RuntimeException(ex); } } - + /** * Creates a new XMLDocument from an InputStream that represents an XML document. + * * @param in * @throws SAXException - * @throws IOException + * @throws IOException */ - public XMLDocument(InputStream in) throws SAXException, IOException{ + public XMLDocument(InputStream in) throws SAXException, IOException { this(); doc = docBuilder.parse(in); } - + /** * Returns an xpath expression from a given xpath string + * * @param xpath * @return - * @throws XPathExpressionException + * @throws XPathExpressionException */ - private XPathExpression getXPath(String xpath) throws XPathExpressionException{ + private XPathExpression getXPath(String xpath) throws XPathExpressionException { return this.xpath.compile(xpath); } - + /** - * Sets the text value of a node, creating nodes as needed. If a node already exists and has - * content, the content is replaced. All XPath expressions - * are considered absolute, even if they don't start with a '/'. + * Sets the text value of a node, creating nodes as needed. If a node already exists and has content, the content is + * replaced. All XPath expressions are considered absolute, even if they don't start with a '/'. + * * @param xpath * @param value - * @throws XPathExpressionException + * @throws XPathExpressionException */ - public void setNode(String xpath, Object value) throws XPathExpressionException{ + public void setNode(String xpath, Object value) throws XPathExpressionException { String sval = ""; - if(value != null){ + if(value != null) { sval = value.toString(); } getXPath(xpath); //Verifies this is a generally valid xpath, so we can roll with that assumption - while(xpath.startsWith("/")){ + while(xpath.startsWith("/")) { xpath = xpath.substring(1); } - String [] xpathParts = xpath.split("/"); + String[] xpathParts = xpath.split("/"); int count = xpathParts.length; - while(count > 0){ + while(count > 0) { String newXPath = "/" + StringUtils.Join(ArrayUtils.slice(xpathParts, 0, count - 1), "/"); - if(!nodeExists(newXPath)){ + if(!nodeExists(newXPath)) { count--; } else { break; } } - if(count == xpathParts.length){ + if(count == xpathParts.length) { //We're at the node already, so just set it and bail getElement(xpath).setTextContent(sval); setDirty(); @@ -148,25 +151,25 @@ public void setNode(String xpath, Object value) throws XPathExpressionException{ //create nodes as we go Element parent = null; Element newNode = null; - do{ + do { String part = xpathParts[count]; String nodeName = getNodeName(part); - if(count > 0){ + if(count > 0) { parent = getElement("/" + StringUtils.Join(ArrayUtils.slice(xpathParts, 0, count - 1), "/")); } - if(nodeName == null){ + if(nodeName == null) { //This is an attribute, edit the node above us parent.setAttribute(getAttributeName(part), sval); setDirty(); return; //Go ahead and bail } else { int position = getNodeIndex(part); - if(count == 0 && position != -1){ + if(count == 0 && position != -1) { throw new XPathExpressionException("The root node cannot have multiple instances."); } newNode = doc.createElement(nodeName); - if(position == -1){ - if(count == 0){ + if(position == -1) { + if(count == 0) { //Special case, we need to create a new element and put it in the root doc.appendChild(newNode); } else { @@ -174,26 +177,26 @@ public void setNode(String xpath, Object value) throws XPathExpressionException{ } } else { //It's an array - if(!(countChildren(parent) + 1 >= position)){ + if(!(countNodeChildren(parent) + 1 >= position)) { //If /root/node[1] exists, but they try to create /root/node[3], this exception is thrown throw new XPathExpressionException("Will not tolerate a jump in node numbers, will only create the next node in sequence."); } parent.appendChild(newNode); } } - count++; + count++; } while(count < xpathParts.length); newNode.setTextContent(sval); setDirty(); } - - private int countChildren(Element e){ + + private int countNodeChildren(Element e) { Node child = e.getFirstChild(); - if(child == null){ + if(child == null) { return 0; } int counter = 1; - while((child = child.getNextSibling()) != null){ + while((child = child.getNextSibling()) != null) { counter++; } return counter; @@ -201,144 +204,153 @@ private int countChildren(Element e){ /** * Returns the node name, or null if this is an attribute. + * * @param node - * @return + * @return */ - private static String getNodeName(String node){ - if(node.startsWith("@")){ + private static String getNodeName(String node) { + if(node.startsWith("@")) { return null; } int firstBracket = node.indexOf("["); - if(firstBracket != -1){ + if(firstBracket != -1) { return node.substring(0, firstBracket).trim(); } else { return node.trim(); } } - + /** - * Gets the position of the node, for instance, node[1] would return 1. - * If no node position is specified, -1 is returned. + * Gets the position of the node, for instance, node[1] would return 1. If no node position is specified, -1 is + * returned. + * * @param node - * @return + * @return */ - private static int getNodeIndex(String node){ + private static int getNodeIndex(String node) { int indexFirst = node.indexOf("["); int indexLast = node.indexOf("]"); - if(indexFirst == -1){ + if(indexFirst == -1) { return -1; } else { return Integer.parseInt(node.substring(indexFirst + 1, indexLast).trim()); } } - + /** * Returns the attribute name, or null if this is not an attribute. + * * @param node - * @return + * @return */ - private static String getAttributeName(String node){ - if(node.trim().startsWith("@")){ + private static String getAttributeName(String node) { + if(node.trim().startsWith("@")) { return node.trim().substring(1); } else { return null; } } - + /** - * Returns the text value at a particular node. All XPath expressions - * are considered absolute, even if they don't start with a '/' + * Returns the text value at a particular node. All XPath expressions are considered absolute, even if they don't + * start with a '/' + * * @param xpath * @return - * @throws XPathExpressionException + * @throws XPathExpressionException */ - public String getNode(String xpath) throws XPathExpressionException{ - return getXPath(xpath).evaluate(doc); + public String getNode(String xpath) throws XPathExpressionException { + return getXPath(xpath).evaluate(doc); } - + /** * Shorthand for Boolean.parseBoolean(getNode(xpath)) + * * @param xpath * @return - * @throws XPathExpressionException + * @throws XPathExpressionException */ - public boolean getBoolean(String xpath) throws XPathExpressionException{ + public boolean getBoolean(String xpath) throws XPathExpressionException { return Boolean.parseBoolean(getNode(xpath)); } - + /** * Shorthand for Integer.parseInt(getNode(xpath)) + * * @param xpath * @return - * @throws XPathExpressionException + * @throws XPathExpressionException */ - public int getInt(String xpath) throws XPathExpressionException{ + public int getInt(String xpath) throws XPathExpressionException { return Integer.parseInt(getNode(xpath)); } - + /** * Shorthand for Long.parseLong(getNode(xpath)) + * * @param xpath * @return - * @throws XPathExpressionException + * @throws XPathExpressionException */ - public long getLong(String xpath) throws XPathExpressionException{ + public long getLong(String xpath) throws XPathExpressionException { return Long.parseLong(getNode(xpath)); } - + /** * Shorthand for Double.parseDouble(getNode(xpath)) + * * @param xpath * @return - * @throws XPathExpressionException + * @throws XPathExpressionException */ - public double getDouble(String xpath) throws XPathExpressionException{ + public double getDouble(String xpath) throws XPathExpressionException { return Double.parseDouble(getNode(xpath)); } - + /** * Checks to see if a node exists or not. + * * @param xpath * @return - * @throws XPathExpressionException + * @throws XPathExpressionException */ - public boolean nodeExists(String xpath) throws XPathExpressionException{ - Object o = getXPath(xpath).evaluate(doc, XPathConstants.NODE); + public boolean nodeExists(String xpath) throws XPathExpressionException { + Object o = getXPath(xpath).evaluate(doc, XPathConstants.NODE); return o != null; } - - private Element getElement(String xpath) throws XPathExpressionException{ - return (Element)getXPath(xpath).evaluate(doc, XPathConstants.NODE); + + private Element getElement(String xpath) throws XPathExpressionException { + return (Element) getXPath(xpath).evaluate(doc, XPathConstants.NODE); } - + /** * Counts the number of direct descendants of this node. + * * @param xpath - * @return + * @return */ - public int countChildren(String xpath) throws XPathExpressionException{ + public int countChildren(String xpath) throws XPathExpressionException { Element e = getElement(xpath); return e.getChildNodes().getLength(); } - + /** - * Counts the number of elements that exist at this level. For instance, if - * the xml were: + * Counts the number of elements that exist at this level. For instance, if the xml were: *
 	 * <xmlroot>
 	 *	<elem />
 	 *	<elem />
 	 * </xmlroot>
-	 * 
- * And the xpath were /xmlroot/elem, then this would return 2. + * And the xpath were /xmlroot/elem, then this would return 2. + * * @param xpath * @return - * @throws XPathExpressionException + * @throws XPathExpressionException */ public int countNodes(String xpath) throws XPathExpressionException { - return ((Number)(getXPath("count(" + xpath + ")").evaluate(doc, XPathConstants.NUMBER))).intValue(); + return ((Number) (getXPath("count(" + xpath + ")").evaluate(doc, XPathConstants.NUMBER))).intValue(); } - + /** * Returns a list of all the child element names at the specified location. For instance, in *
@@ -347,59 +359,61 @@ public int countNodes(String xpath) throws XPathExpressionException {
 	 *  <elem />
 	 *  <elem2 />
 	 * </root>
-	 * 
- * The list for "/root" would contain [elem, elem, elem2]. This is useful for examining undefined or variable xml - * elements. If this is a text node or has no children, an empty list is returned. The elements - * will be listed in the order they are defined in the xml. + * The list for "/root" would contain [elem, elem, elem2]. This is useful for examining undefined or variable + * xml elements. If this is a text node or has no children, an empty list is returned. The elements will be listed + * in the order they are defined in the xml. + * * @param xpath * @return - * @throws XPathExpressionException + * @throws XPathExpressionException */ public List getChildren(String xpath) throws XPathExpressionException { List list = new ArrayList(); - NodeList o = (NodeList)getXPath(xpath + "/child::*").evaluate(doc, XPathConstants.NODESET); - for(int i = 0; i < o.getLength(); i++){ + NodeList o = (NodeList) getXPath(xpath + "/child::*").evaluate(doc, XPathConstants.NODESET); + for(int i = 0; i < o.getLength(); i++) { Node n = o.item(i); list.add(n.getNodeName()); } return list; } - + /** * Signals to the getXML function that the cache is no longer valid. */ - private void setDirty(){ + private void setDirty() { uglyDirty = true; prettyDirty = true; } - + /** * Equivalent to getXML(false); - * @return + * + * @return */ - public String getXML(){ + public String getXML() { return getXML(false); } - + /** - * Renders the XML as it currently stands. If pretty is true, it is formatted with - * indentation, otherwise, no indentation is used. + * Renders the XML as it currently stands. If pretty is true, it is formatted with indentation, otherwise, no + * indentation is used. + * * @param pretty - * @return + * @return */ - public String getXML(boolean pretty){ - if(uglyDirty || prettyDirty){ + public String getXML(boolean pretty) { + if(uglyDirty || prettyDirty) { try { Transformer transformer = TransformerFactory.newInstance().newTransformer(); DOMSource source = new DOMSource(doc); StringWriter writer = new StringWriter(); StreamResult result = new StreamResult(writer); - if(pretty){ + if(pretty) { transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); } transformer.transform(source, result); - if(pretty){ + if(pretty) { prettyRender = writer.toString(); prettyDirty = false; } else { @@ -410,7 +424,7 @@ public String getXML(boolean pretty){ throw new RuntimeException(ex); } } - if(pretty){ + if(pretty) { return prettyRender; } else { return uglyRender; @@ -421,5 +435,5 @@ public String getXML(boolean pretty){ public String toString() { return getXML(true); } - + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/ZipIterator.java b/src/main/java/com/laytonsmith/PureUtilities/ZipIterator.java index 44cc439d1f..0f4a8cc90f 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/ZipIterator.java +++ b/src/main/java/com/laytonsmith/PureUtilities/ZipIterator.java @@ -8,45 +8,47 @@ import java.util.zip.ZipInputStream; /** - * Goes through all the files in a zip (not the directories), and provides a callback with the input stream - * at each file in a callback. + * Goes through all the files in a zip (not the directories), and provides a callback with the input stream at each file + * in a callback. */ public class ZipIterator { - private File zip; - + private final File zip; + public ZipIterator(File zip) { this.zip = zip; } - + /** * Iterates a zip file. + * * @param callback - * @throws FileNotFoundException + * @throws FileNotFoundException */ - public void iterate(ZipIteratorCallback callback) throws IOException{ + public void iterate(ZipIteratorCallback callback) throws IOException { iterate(callback, null); } - + /** * Iterates a zip file. + * * @param callback - * @throws FileNotFoundException + * @throws FileNotFoundException */ - public void iterate(ZipIteratorCallback callback, final ProgressIterator progressIterator) throws IOException{ - final ZipInputStream zis = new ZipInputStream(new FileInputStream(zip)); + public void iterate(ZipIteratorCallback callback, final ProgressIterator progressIterator) throws IOException { + final ZipInputStream zis = new ZipInputStream(new FileInputStream(zip)); final double size = zip.length(); ZipEntry entry; - while((entry = zis.getNextEntry()) != null){ - if(!entry.isDirectory()){ + while((entry = zis.getNextEntry()) != null) { + if(!entry.isDirectory()) { callback.handle(entry.getName(), new InputStream() { private double soFar = 0; - + @Override public int read() throws IOException { - if(progressIterator != null){ + if(progressIterator != null) { ++soFar; - if(soFar % 128 == 0){ + if(soFar % 128 == 0) { progressIterator.progressChanged(soFar, size); } } @@ -57,14 +59,15 @@ public int read() throws IOException { public void close() throws IOException { //Do nothing, we will close this later, ourselves. } - + }); } } zis.close(); } - + public static interface ZipIteratorCallback { + void handle(String filename, InputStream in); } } diff --git a/src/main/java/com/laytonsmith/PureUtilities/ZipMaker.java b/src/main/java/com/laytonsmith/PureUtilities/ZipMaker.java index 5c9a418329..d85487be66 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/ZipMaker.java +++ b/src/main/java/com/laytonsmith/PureUtilities/ZipMaker.java @@ -13,7 +13,7 @@ /** * - * + * */ public final class ZipMaker { @@ -21,9 +21,8 @@ private ZipMaker() { } /** - * Makes a zip file using all the files in the directory specified. The - * filename is used to place the zip file at the same level as the - * starting directory. + * Makes a zip file using all the files in the directory specified. The filename is used to place the zip file at + * the same level as the starting directory. * * @param startingDir The directory to zip up * @param filename The name of the zip file to create @@ -31,27 +30,26 @@ private ZipMaker() { public static void MakeZip(File startingDir, String filename) throws IOException { MakeZip(startingDir, filename, false); } - + /** - * Makes a zip file using all the files in the directory specified. The - * filename is used to place the zip file at the same level as the - * starting directory. + * Makes a zip file using all the files in the directory specified. The filename is used to place the zip file at + * the same level as the starting directory. * * @param startingDir The directory to zip up * @param filename The name of the zip file to create - * @param createTopLevelFolder If true, then files in the zip will be created inside a folder - * named the same as the filename (minus extension) + * @param createTopLevelFolder If true, then files in the zip will be created inside a folder named the same as the + * filename (minus extension) */ public static void MakeZip(File startingDir, String filename, boolean createTopLevelFolder) throws IOException { String topLevel = ""; - if(createTopLevelFolder){ - if(filename.lastIndexOf(".") == -1){ + if(createTopLevelFolder) { + if(filename.lastIndexOf(".") == -1) { topLevel = filename + "/"; } else { topLevel = filename.substring(0, filename.lastIndexOf(".")) + "/"; } } - if (startingDir.isDirectory()) { + if(startingDir.isDirectory()) { Set files = new LinkedHashSet(); GetFiles(files, startingDir.getCanonicalFile(), startingDir.getCanonicalFile()); MakeZip(files, new File(startingDir.getParentFile(), filename), startingDir, topLevel); @@ -60,17 +58,6 @@ public static void MakeZip(File startingDir, String filename, boolean createTopL } } - private static void GetFiles(Set ongoing, File directory, File base) throws IOException { - if (directory.isDirectory()) { - for (File f : directory.listFiles()) { - GetFiles(ongoing, f, base); - } - } else { - File file = new File(directory.getAbsolutePath().replaceFirst(Pattern.quote(base.getAbsolutePath() + "/"), "")); - ongoing.add(file); - } - } - private static void MakeZip(Set files, File output, File base, String topLevel) throws IOException { // These are the files to include in the ZIP file @@ -80,15 +67,15 @@ private static void MakeZip(Set files, File output, File base, String topL ZipOutputStream out = new ZipOutputStream(new FileOutputStream(output)); // Compress the files - for (File f : files) { + for(File f : files) { FileInputStream in = new FileInputStream(new File(base, f.getPath())); - // Add ZIP entry to output stream. + // Add ZIP entry to output stream. out.putNextEntry(new ZipEntry(topLevel + GetUnabsoluteFile(base, f).getPath())); // Transfer bytes from the file to the ZIP file int len; - while ((len = in.read(buf)) > 0) { + while((len = in.read(buf)) > 0) { out.write(buf, 0, len); } @@ -97,13 +84,24 @@ private static void MakeZip(Set files, File output, File base, String topL in.close(); //JVM Bug in = null; - System.gc(); + GCUtil.BlockUntilGC(); } // Complete the ZIP file out.close(); } + private static void GetFiles(Set ongoing, File directory, File base) throws IOException { + if(directory.isDirectory()) { + for(File f : directory.listFiles()) { + GetFiles(ongoing, f, base); + } + } else { + File file = new File(directory.getAbsolutePath().replaceFirst(Pattern.quote(base.getAbsolutePath() + "/"), "")); + ongoing.add(file); + } + } + private static File GetUnabsoluteFile(File base, File child) throws IOException { String path = new File(base, child.getPath()).getCanonicalPath().replaceFirst(Pattern.quote(base.getCanonicalPath() + File.separatorChar), ""); return new File(path); diff --git a/src/main/java/com/laytonsmith/PureUtilities/ZipReader.java b/src/main/java/com/laytonsmith/PureUtilities/ZipReader.java index 632a541c50..04b51151d2 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/ZipReader.java +++ b/src/main/java/com/laytonsmith/PureUtilities/ZipReader.java @@ -3,7 +3,7 @@ import com.laytonsmith.PureUtilities.Common.ArrayUtils; import com.laytonsmith.PureUtilities.Common.FileUtil; import com.laytonsmith.PureUtilities.Common.StreamUtils; -import com.laytonsmith.PureUtilities.Common.StringUtils; +import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -23,384 +23,384 @@ import java.util.zip.ZipInputStream; /** - * Allows read operations to happen transparently on a zip file, as if it were a - * folder. Nested zips are also supported. All operations are read only. - * Operations on a ZipReader with a path in an actual zip are expensive, so it's - * good to keep in mind this when using the reader, you'll have to balance - * between memory usage (caching) or CPU use (re-reading as needed). - * - * Smith + * Allows read operations to happen transparently on a zip file, as if it were a folder. Nested zips are also supported. + * All operations are read only. Operations on a ZipReader with a path in an actual zip are expensive, so it's good to + * keep in mind this when using the reader, you'll have to balance between memory usage (caching) or CPU use (re-reading + * as needed). */ public class ZipReader { - /** - * The top level zip file, which represents the actual file on the file system. - */ - private final File topZip; - - /** - * The chain of Files that this file represents. - */ - private final Deque chainedPath; - - /** - * The actual file object. - */ - private final File file; - - /** - * Whether or not we have to dig down into the zip, or if - * we can use trivial file operations. - */ - private final boolean isZipped; - /** - * A list of zip entries, which is cached, so we don't need to re-read - * the zip file each time we want to do enumerative stuff. + * The top level zip file, which represents the actual file on the file system. + */ + private final File topZip; + + /** + * The chain of Files that this file represents. + */ + private final Deque chainedPath; + + /** + * The actual file object. + */ + private final File file; + + /** + * Whether or not we have to dig down into the zip, or if we can use trivial file operations. + */ + private final boolean isZipped; + + /** + * A list of zip entries, which is cached, so we don't need to re-read the zip file each time we want to do + * enumerative stuff. */ private List zipEntries = null; - + /** - * The ZipEntry contains the information of whether or not the listed file is - * a directory, but since we discard that information, we cache the list of directories - * here. + * The ZipEntry contains the information of whether or not the listed file is a directory, but since we discard that + * information, we cache the list of directories here. */ - private List zipDirectories = new ArrayList(); + private List zipDirectories = new ArrayList<>(); /** - * Convenience constructor, which allows for a URL to be passed in instead of a file, - * which may be useful when working with resources. - * @param url + * Convenience constructor, which allows for a URL to be passed in instead of a file, which may be useful when + * working with resources. + * + * @param url */ - public ZipReader(URL url){ + public ZipReader(URL url) { this(new File(url.getFile())); } - /** - * Creates a new ZipReader object, which can be used to read from a zip - * file, as if the zip files were simple directories. All files are checked - * to see if they are a zip. - * - *

{@code new ZipReader(new File("path/to/container.zip/with/nested.zip/file.txt"));}

- * - * - * @param file The path to the internal file. This needn't exist, according - * to File, as the zip file won't appear as a directory to other classes. - * This constructor will however throw a FileNotFoundException if it - * determines that the file doesn't exist. - */ - public ZipReader(File file){ - chainedPath = new LinkedList(); + /** + * Creates a new ZipReader object, which can be used to read from a zip file, as if the zip files were simple + * directories. All files are checked to see if they are a zip. + * + *

+ * {@code new ZipReader(new File("path/to/container.zip/with/nested.zip/file.txt"));}

+ * + * + * @param file The path to the internal file. This needn't exist, according to File, as the zip file won't appear as + * a directory to other classes. This constructor will however throw a FileNotFoundException if it determines that + * the file doesn't exist. + */ + public ZipReader(File file) { + chainedPath = new LinkedList<>(); //We need to remove jar style or uri style things from the file, so do that here - if(file.getPath().startsWith("file:")){ + if(file.getPath().startsWith("jar:")) { + String newFile = file.getPath().substring(4); + file = new File(newFile); + } + if(file.getPath().startsWith("file:")) { String newFile = file.getPath().substring(5); //Replace all \ with /, to simply processing, but also replace ! with /, since jar addresses //use that to denote the jar. We don't care, it's just a folder, so replace that with a slash. - newFile = newFile.replace("\\", "/").replace("!", "/"); - while(newFile.startsWith("//")){ + newFile = newFile.replace('\\', '/').replace('!', '/'); + while(newFile.startsWith("//")) { //We only want up to one slash here newFile = newFile.substring(1); } file = new File(newFile); } - - - //make sure file is absolute - file = file.getAbsoluteFile(); - this.file = file; - - //We need to walk up the parents, putting those files onto the stack which are valid Zips - File f = file; - chainedPath.addFirst(f); //Gotta add the file itself to the path for everything to work - File tempTopZip = null; - while ((f = f.getParentFile()) != null) { - chainedPath.addFirst(f); - try { - //If this works, we'll know we have our top zip file. Everything else will have - //to be in memory, so we'll start with this if we have to dig deeper. - if (tempTopZip == null) { - ZipFile zf = new ZipFile(f); - tempTopZip = f; - } - } catch (ZipException ex) { - //This is fine, it's just not a zip file - } catch (IOException | AccessControlException ex) { - //This is fine too, it may mean we don't have permission to access this directory, - //but that's ok, we don't need access yet. - } - } - - //If it's not a zipped file, this will make operations easier to deal with, - //so let's save that information - isZipped = tempTopZip != null; - if(isZipped){ - topZip = tempTopZip; - } else { - topZip = file; - } - - } - - /** - * Returns the top level file for the underlying file. If this is not zipped, the file - * returned will be the file this object was constructed with. Otherwise, the File - * representing the actual file on the filesystem will be returned. This is mostly - * useful for the case where locks need to be implemented, or to find the "root" of - * the directory. - * @return - */ - public File getTopLevelFile(){ - return topZip; - } - - /** - * Returns if this file exists or not. Note this is a non-trivial operation. - * - * @return - */ - public boolean exists(){ - if(!topZip.exists()){ - return false; //Don't bother trying - } - try{ - getInputStream().close(); - return true; - } catch(IOException e){ - return false; - } - } - - /** - * Returns true if this file is read accessible. Note that if the file is a zip, - * the permissions are checked on the topmost zip file. - * @return - */ - public boolean canRead(){ - return topZip.canRead(); - } - - /** - * Returns true if this file has write permissions. Note that if the file is nested - * in a zip, then this will always return false. If the file doesn't exist, this will - * also return false, but that doesn't imply that you won't be able to create file here, - * so you may also need to check isZipped(). - * @return - */ - public boolean canWrite(){ - if(isZipped){ - return false; - } else { - return topZip.canWrite(); - } - } - - /** - * Returns whether or not the file is inside of a zip file or not. - * @return - */ - public boolean isZipped(){ - return isZipped; - } - - /* - * This function recurses down into a zip file, ultimately returning the InputStream for the file, - * or throwing exceptions if it can't be found. - */ - private InputStream getFile(Deque fullChain, String zipName, final ZipInputStream zis) throws FileNotFoundException, IOException { - ZipEntry entry; - InputStream zipReader = new InputStream() { - - @Override - public int read() throws IOException { - if (zis.available() > 0) { - return zis.read(); - } else { - return -1; - } - } - - @Override - public void close() throws IOException { - zis.close(); - } - }; - boolean isZip = false; - List recurseAttempts = new ArrayList(); - while ((entry = zis.getNextEntry()) != null) { - //This is at least a zip file - isZip = true; - Deque chain = new LinkedList(fullChain); - File chainFile = null; - while ((chainFile = chain.pollFirst()) != null) { - if (chainFile.equals(new File(zipName + File.separator + entry.getName()))) { - //We found it. Now, chainFile is one that is in our tree - //We have to do some further analyzation on it - break; - } - } - if (chainFile == null) { - //It's not in the chain at all, which means we don't care about it at all. - continue; - } - if (chain.isEmpty()) { - //It was the last file in the chain, so no point in looking at it at all. - //If it was a zip or not, it doesn't matter, because this is the file they - //specified, precisely. Read it out, and return it. - return zipReader; - } - - //It's a single file, it's in the chain, and the chain isn't finished, so that - //must mean it's a container (or it's being used as one, anyways). + + //make sure file is absolute + file = file.getAbsoluteFile(); + this.file = file; + + //We need to walk up the parents, putting those files onto the stack which are valid Zips + File f = file; + chainedPath.addFirst(f); //Gotta add the file itself to the path for everything to work + File tempTopZip = null; + while((f = f.getParentFile()) != null) { + chainedPath.addFirst(f); + try { + //If this works, we'll know we have our top zip file. Everything else will have + //to be in memory, so we'll start with this if we have to dig deeper. + if(tempTopZip == null) { + ZipFile zf = new ZipFile(f); + tempTopZip = f; + } + } catch (ZipException ex) { + //This is fine, it's just not a zip file + } catch (IOException | AccessControlException ex) { + //This is fine too, it may mean we don't have permission to access this directory, + //but that's ok, we don't need access yet. + } + } + + //If it's not a zipped file, this will make operations easier to deal with, + //so let's save that information + isZipped = tempTopZip != null; + if(isZipped) { + topZip = tempTopZip; + } else { + topZip = file; + } + + } + + /** + * Returns the top level file for the underlying file. If this is not zipped, the file returned will be the file + * this object was constructed with. Otherwise, the File representing the actual file on the filesystem will be + * returned. This is mostly useful for the case where locks need to be implemented, or to find the "root" of the + * directory. + * + * @return + */ + public File getTopLevelFile() { + return topZip; + } + + /** + * Returns if this file exists or not. Note this is a non-trivial operation. + * + * @return + */ + public boolean exists() { + if(!topZip.exists()) { + return false; //Don't bother trying + } + try { + getInputStream().close(); + return true; + } catch (IOException e) { + return false; + } + } + + /** + * Returns true if this file is read accessible. Note that if the file is a zip, the permissions are checked on the + * topmost zip file. + * + * @return + */ + public boolean canRead() { + return topZip.canRead(); + } + + /** + * Returns true if this file has write permissions. Note that if the file is nested in a zip, then this will always + * return false. If the file doesn't exist, this will also return false, but that doesn't imply that you won't be + * able to create file here, so you may also need to check isZipped(). + * + * @return + */ + public boolean canWrite() { + if(isZipped) { + return false; + } else { + return topZip.canWrite(); + } + } + + /** + * Returns whether or not the file is inside of a zip file or not. + * + * @return + */ + public boolean isZipped() { + return isZipped; + } + + /** + * Returns a raw input stream for this file. If you just need the string contents, it would probably be easier to + * use getFileContents instead, however, this method is necessary for accessing binary files. + * + * @return An InputStream that will read the specified file + * @throws FileNotFoundException If the file is not found + * @throws IOException If you specify a file that isn't a zip file as if it were a folder + */ + public InputStream getInputStream() throws FileNotFoundException, IOException { + if(!isZipped) { + return new FileInputStream(file); + } else { + return getFile(chainedPath, topZip.getAbsolutePath(), new ZipInputStream(new FileInputStream(topZip))); + } + } + + + /** + * If the file is a simple text file, this function is your best option.It returns the contents of the file as a + * UTF-8 string. + * + * @return + * @throws FileNotFoundException If the file is not found + * @throws IOException If you specify a file that isn't a zip file as if it were a folder + */ + public String getFileContents() throws FileNotFoundException, IOException { + return getFileContents("UTF-8"); + } + + /** + * If the file is a simple text file, this function is your best option.It returns the contents of the file as a + * string. + * + * @param charset + * @return + * @throws FileNotFoundException If the file is not found + * @throws IOException If you specify a file that isn't a zip file as if it were a folder + */ + public String getFileContents(String charset) throws FileNotFoundException, IOException { + if(!isZipped) { + return FileUtil.read(file, charset); + } else { + return StreamUtils.GetString(getInputStream()); + } + } + + /** + * Delegates the equals check to the underlying File object. + * + * @param obj + * @return + */ + @Override + public boolean equals(Object obj) { + if(obj == null) { + return false; + } + if(getClass() != obj.getClass()) { + return false; + } + final ZipReader other = (ZipReader) obj; + return other.file.equals(this.file); + } + + /** + * Delegates the hashCode to the underlying File object. + * + * @return + */ + @Override + public int hashCode() { + return file.hashCode(); + } + + @Override + public String toString() { + return file.toString(); + } + + public File getFile() { + return file; + } + + /* + * This function recurses down into a zip file, ultimately returning the InputStream for the file, + * or throwing exceptions if it can't be found. + */ + private InputStream getFile(Deque fullChain, String zipName, final ZipInputStream zis) throws FileNotFoundException, IOException { + ZipEntry entry; + InputStream zipReader = new BufferedInputStream(zis); + boolean isZip = false; + List recurseAttempts = new ArrayList<>(); + while((entry = zis.getNextEntry()) != null) { + //This is at least a zip file + isZip = true; + Deque chain = new LinkedList<>(fullChain); + File chainFile = null; + while((chainFile = chain.pollFirst()) != null) { + if(chainFile.equals(new File(zipName + File.separator + entry.getName()))) { + //We found it. Now, chainFile is one that is in our tree + //We have to do some further analyzation on it + break; + } + } + if(chainFile == null) { + //It's not in the chain at all, which means we don't care about it at all. + continue; + } + if(chain.isEmpty()) { + //It was the last file in the chain, so no point in looking at it at all. + //If it was a zip or not, it doesn't matter, because this is the file they + //specified, precisely. Read it out, and return it. + return zipReader; + } + + //It's a single file, it's in the chain, and the chain isn't finished, so that + //must mean it's a container (or it's being used as one, anyways). //It could be that either this is just a folder in the entry list, or it could //mean that this is a zip. We will make note of this as one we need to attempt to //recurse, but only if it doesn't pan out that this is a file. recurseAttempts.add(zipName + File.separator + entry.getName()); - } - for(String recurseAttempt : recurseAttempts){ + } + for(String recurseAttempt : recurseAttempts) { ZipInputStream inner = new ZipInputStream(zipReader); - try{ - return getFile(fullChain, recurseAttempt, inner); - } catch(IOException e){ + try { + return getFile(fullChain, recurseAttempt, inner); + } catch (IOException e) { //We don't care if this breaks, we'll throw out own top level exception //in a moment if we got here. We still need to finish going through //out recurse attempts. } } - //If we get down here, it means either we recursed into not-a-zip file, or - //the file was otherwise not found - if (isZip) { - //if this is the terminal node in the chain, it's due to a file not found. - throw new FileNotFoundException(zipName + " could not be found!"); - } else { - //if not, it's due to this not being a zip file - throw new IOException(zipName + " is not a zip file!"); - } - } - - /** - * Returns a raw input stream for this file. If you just need the string contents, - * it would probably be easer to use getFileContents instead, however, this method - * is necessary for accessing binary files. - * @return An InputStream that will read the specified file - * @throws FileNotFoundException If the file is not found - * @throws IOException If you specify a file that isn't a zip file as if it were a folder - */ - public InputStream getInputStream() throws FileNotFoundException, IOException { - if (!isZipped) { - return new FileInputStream(file); - } else { - return getFile(chainedPath, topZip.getAbsolutePath(), new ZipInputStream(new FileInputStream(topZip))); - } - } - - /** - * If the file is a simple text file, this function is your best option. It returns - * the contents of the file as a string. - * @return - * @throws FileNotFoundException If the file is not found - * @throws IOException If you specify a file that isn't a zip file as if it were a folder - */ - public String getFileContents() throws FileNotFoundException, IOException { - if (!isZipped) { - return FileUtil.read(file); - } else { - return StreamUtils.GetString(getInputStream()); - } - } - - /** - * Delegates the equals check to the underlying File object. - * @param obj - * @return - */ - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final ZipReader other = (ZipReader) obj; - return other.file.equals(this.file); - } - - /** - * Delegates the hashCode to the underlying File object. - * @return - */ - @Override - public int hashCode() { - return file.hashCode(); - } - - @Override - public String toString() { - return file.toString(); + //If we get down here, it means either we recursed into not-a-zip file, or + //the file was otherwise not found + if(isZip) { + //if this is the terminal node in the chain, it's due to a file not found. + throw new FileNotFoundException(zipName + " could not be found!"); + } else { + //if not, it's due to this not being a zip file + throw new IOException(zipName + " is not a zip file!"); + } } - - public File getFile(){ - return file; - } - - private void initList() throws IOException{ - if(!isZipped){ + + private void initList() throws IOException { + if(!isZipped) { return; } - if(this.zipEntries == null){ - zipEntries = new ArrayList(); - ZipInputStream zis = new ZipInputStream(new FileInputStream(topZip)); - ZipEntry entry; - while((entry = zis.getNextEntry()) != null){ - File f = new File(topZip, entry.getName()); - zipEntries.add(f); - if(entry.isDirectory()){ - zipDirectories.add(f); + if(this.zipEntries == null) { + zipEntries = new ArrayList<>(); + try(ZipInputStream zis = new ZipInputStream(new FileInputStream(topZip))) { + ZipEntry entry; + while((entry = zis.getNextEntry()) != null) { + File f = new File(topZip, entry.getName()); + zipEntries.add(f); + if(entry.isDirectory()) { + zipDirectories.add(f); + } } } - zis.close(); } } - - public boolean isDirectory() throws IOException{ - if(!isZipped){ + + public boolean isDirectory() throws IOException { + if(!isZipped) { return file.isDirectory(); } else { initList(); return zipDirectories.contains(file); } } - - public String getName(){ + + public String getName() { return file.getName(); } - + /** - * Returns a list of File objects that are subfiles or directories in - * this directory. + * Returns a list of File objects that are subfiles or directories in this directory. This method does not + * recurse, to match the behavior of + * * @return - * @throws IOException + * @throws IOException */ - public File [] listFiles() throws IOException{ - if(!isZipped){ + public File[] listFiles() throws IOException { + if(!isZipped) { return file.listFiles(); } else { - StringUtils.Join(new String[]{}, ""); initList(); - List files = new ArrayList(); - for(File f : zipEntries){ + List files = new ArrayList<>(); + for(File f : zipEntries) { //If the paths start with the same thing... - if(f.getPath().startsWith(file.getPath())){ + if(f.getPath().startsWith(file.getPath())) { //...and it's not the file we're looking from to begin with... - if(!file.equals(f)){ + if(!file.equals(f)) { //...and it's not in a sub-sub folder of this file... - if(!f.getPath().matches(Pattern.quote(file.getPath() + File.separatorChar) + "[^" + Pattern.quote(File.separator) + "]*" + Pattern.quote(File.separator) + ".*")){ + if(!f.getPath().matches(Pattern.quote(file.getPath() + File.separatorChar) + "[^" + Pattern.quote(File.separator) + "]*" + Pattern.quote(File.separator) + ".*")) { //...add it to the list. - String root = f.getPath().replaceFirst(Pattern.quote(file.getPath() + File.separator), ""); - f = new File(root); - files.add(f); + files.add(f); } } } @@ -408,40 +408,58 @@ public String getName(){ return ArrayUtils.asArray(File.class, files); } } - - public ZipReader[] zipListFiles() throws IOException{ + + /** + * Shortcut to getting a ZipReader object for the files returned by {@link #listFiles()} + * + * @return + * @throws IOException + */ + public ZipReader[] zipListFiles() throws IOException { File[] ret = listFiles(); ZipReader[] zips = new ZipReader[ret.length]; - for(int i = 0; i < ret.length; i++){ - zips[i] = new ZipReader(new File(file, ret[i].getPath())); + for(int i = 0; i < ret.length; i++) { + if(ret[i].isAbsolute()) { + zips[i] = new ZipReader(ret[i]); + } else { + zips[i] = new ZipReader(new File(file, ret[i].getPath())); + } } return zips; } - + /** - * Copies all the files from this directory to the source directory. - * If create is false, and the folder doesn't already exist, and IOException - * will be thrown. This is similar to an "unzip" operation. - * @param dstFolder + * Copies all the files from this directory to the source directory. If create is false, and the folder doesn't + * already exist, and IOException will be thrown. Sub directories will always be created, however. + * This is similar to an "unzip" operation. + * + * @param dstFolder + * @param create + * @throws java.io.IOException */ - public void recursiveCopy(File dstFolder, boolean create) throws IOException{ - if(create){ + public void recursiveCopy(File dstFolder, boolean create) throws IOException { + if(create) { dstFolder.mkdirs(); } - if(!dstFolder.isDirectory()){ + if(!dstFolder.isDirectory()) { throw new IOException("Destination folder is not a directory!"); } - for(ZipReader r : zipListFiles()){ - if(r.isDirectory()){ - r.recursiveCopy(dstFolder, create); + for(ZipReader r : zipListFiles()) { + if(r.isDirectory()) { + File newFile = new File(dstFolder, r.getName()); + // Unlike the first mkdirs, we only want to mkdir here. If create was false, and the parent directory + // did not already exist, we do not want this call to succeed. + newFile.mkdir(); + r.recursiveCopy(newFile, create); } else { File newFile = new File(dstFolder, r.file.getName()); - newFile.getParentFile().mkdirs(); - FileOutputStream fos = new FileOutputStream(newFile, false); - StreamUtils.Copy(r.getInputStream(), fos); + newFile.getParentFile().mkdir(); + try(FileOutputStream fos = new FileOutputStream(newFile, false); + InputStream fis = r.getInputStream()) { + StreamUtils.Copy(fis, fos); + } } } } - - + } diff --git a/src/main/java/com/laytonsmith/PureUtilities/package-info.java b/src/main/java/com/laytonsmith/PureUtilities/package-info.java index 4dda9272c3..43d2ec35b6 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/package-info.java +++ b/src/main/java/com/laytonsmith/PureUtilities/package-info.java @@ -2,11 +2,11 @@ * This packages contains classes that are very generic utilities. There * are no project specific dependencies in any of these classes, however they may * depend on other classes within this package. This makes them highly portable - * to other projects. Dependencies amongst other classes within the package is - * also kept to a minimum, so in many cases, single files may be copied over into + * to other projects. Dependencies amongst other classes within the package is + * also kept to a minimum, so in many cases, single files may be copied over into * another project. - * + * * In a few cases, the files have dependencies on external libraries, and serve more - * as a wrapper around other utilities, rather than actual standalone utilities. + * as a wrapper around other utilities, rather than actual standalone utilities. */ package com.laytonsmith.PureUtilities; diff --git a/src/main/java/com/laytonsmith/PureUtilities/rParser.java b/src/main/java/com/laytonsmith/PureUtilities/rParser.java index cd8274ae9b..256415c4d6 100644 --- a/src/main/java/com/laytonsmith/PureUtilities/rParser.java +++ b/src/main/java/com/laytonsmith/PureUtilities/rParser.java @@ -5,263 +5,264 @@ import java.util.Arrays; /** - * This class provides a method for working around the not so pretty line breaks - * that SMP does. The original class was written by Nossr50, with portions contributed - * by Reil. - * + * This class provides a method for working around the not so pretty line breaks that SMP does. The original class was + * written by Nossr50, with portions contributed by Reil. + * */ +@SuppressWarnings("checkstyle:typename") // Fixing this violation might break dependents. public final class rParser { - - private rParser(){} - private static final int lineLength = 312; + private rParser() { + } - /* - * Finds the last color sequence used in the string - */ - public static String lastColor(String findColor) { - int i = findColor.lastIndexOf('§'); - if (i != -1 && i != findColor.length() - 1) { - return "§" + findColor.charAt(i + 1); - } else { - return ""; - } - } + private static final int LINE_LENGTH = 312; - /* - * - */ - public static String combineSplit(int beginHere, String[] split, String seperator) { - StringBuilder combined = new StringBuilder(split[beginHere]); - if (beginHere + 1 < split.length) { - for (int i = beginHere + 1; i < split.length; i++) { - combined.append(seperator).append(split[i]); - } - } - return combined.toString(); - } + /* + * Finds the last color sequence used in the string + */ + public static String lastColor(String findColor) { + int i = findColor.lastIndexOf('§'); + if(i != -1 && i != findColor.length() - 1) { + return "§" + findColor.charAt(i + 1); + } else { + return ""; + } + } - public static String[] wordWrap(String msg) { - return wordWrap(msg, "", lineLength); - } + /* + * + */ + public static String combineSplit(int beginHere, String[] split, String seperator) { + StringBuilder combined = new StringBuilder(split[beginHere]); + if(beginHere + 1 < split.length) { + for(int i = beginHere + 1; i < split.length; i++) { + combined.append(seperator).append(split[i]); + } + } + return combined.toString(); + } - public static String[] wordWrap(String msg, String prefix) { - return wordWrap(msg, prefix, lineLength); - } + public static String[] wordWarp(String msg, int lineLength) { + return wordWrap(msg, "", lineLength); + } - public static String[] wordWarp(String msg, int lineLength) { - return wordWrap(msg, "", lineLength); - } + public static String[] wordWrap(String msg) { + return wordWrap(msg, "", LINE_LENGTH); + } - public static String[] wordWrap(String msg, String prefix, int lineLength) { - //Split each word apart - ArrayList split = new ArrayList(); - split.addAll(Arrays.asList(msg.split(" "))); + public static String[] wordWrap(String msg, String prefix) { + return wordWrap(msg, prefix, LINE_LENGTH); + } - //Create an arraylist for the output - ArrayList out = new ArrayList(); - //While i is less than the length of the array of words - while (!split.isEmpty()) { - int len = 0; + public static String[] wordWrap(String msg, String prefix, int lineLength) { + //Split each word apart + ArrayList split = new ArrayList(); + split.addAll(Arrays.asList(msg.split(" "))); - //Create an arraylist to hold individual words - ArrayList words = new ArrayList(); + //Create an arraylist for the output + ArrayList out = new ArrayList(); + //While i is less than the length of the array of words + while(!split.isEmpty()) { + int len = 0; - //Loop through the words finding their length and increasing - //j, the end point for the sub string - while (!split.isEmpty() && split.get(0) != null && len <= lineLength) { - int wordLength = msgLength(split.get(0)) + 4; + //Create an arraylist to hold individual words + ArrayList words = new ArrayList(); - //If a word is too long for a line - if (wordLength > lineLength) { - String[] tempArray = wordCut(len, split.remove(0), lineLength); - words.add(tempArray[0]); + //Loop through the words finding their length and increasing + //j, the end point for the sub string + while(!split.isEmpty() && split.get(0) != null && len <= lineLength) { + int wordLength = msgLength(split.get(0)) + 4; - split.add(tempArray[1]); - } + //If a word is too long for a line + if(wordLength > lineLength) { + String[] tempArray = wordCut(len, split.remove(0), lineLength); + words.add(tempArray[0]); - //If the word is not too long to fit - len += wordLength; - if (len <= lineLength) { - words.add(split.remove(0)); - } - } - //Merge them and add them to the output array. - String lastColor = ""; - if (!out.isEmpty()) { - lastColor = lastColor(out.get(out.size() - 1)); - } - String[] stringArray = words.toArray(new String[words.size()]); - //if(stringArray.length != 0){ - out.add(lastColor - + combineSplit(0, stringArray, " ") + " "); - //} - } + split.add(tempArray[1]); + } - //Convert to an array and return - return out.toArray(new String[out.size()]); - } + //If the word is not too long to fit + len += wordLength; + if(len <= lineLength) { + words.add(split.remove(0)); + } + } + //Merge them and add them to the output array. + String lastColor = ""; + if(!out.isEmpty()) { + lastColor = lastColor(out.get(out.size() - 1)); + } + String[] stringArray = words.toArray(new String[words.size()]); + //if(stringArray.length != 0){ + out.add(lastColor + + combineSplit(0, stringArray, " ") + " "); + //} + } - //===================================================================== - //Function: msgLength - //Input: String str: The string to find the length of - //Output: int: The length on the screen of a string - //Use: Finds the length on the screen of a string. Ignores MCChatColor. - //===================================================================== - public static int msgLength(String str) { - int length = 0; - //Loop through all the characters, skipping any color characters - //and their following color codes - for (int x = 0; x < str.length(); x++) { - if (str.charAt(x) == '§' /*|| str.charAt(x) == MCChatColor.White.charAt(0)*/) { - if (x + 1 != str.length()) { - if (colorChange(str.charAt(x + 1)) != null) { - x++; - continue; - } - } - } - int len = charLength(str.charAt(x)); - length += len; - } - return length; - } + //Convert to an array and return + return out.toArray(new String[out.size()]); + } - //===================================================================== - //Function: wordCut - //Input: String str: The string to find the length of - //Output: String[]: The cut up word - //Use: Cuts apart a word that is too long to fit on one line - //===================================================================== - private static String[] wordCut(int lengthBefore, String str, int lineLength) { - int length = lengthBefore; - //Loop through all the characters, skipping any color characters - //and their following color codes - String[] output = new String[2]; - int x = 0; - while (length < lineLength && x < str.length()) { - int len = charLength(str.charAt(x)); - if (len > 0) { - length += len; - } else { - x++; - } - x++; - } - if (x > str.length()) { - x = str.length(); - } - //Add the substring to the output after cutting it - output[0] = str.substring(0, x); - //Add the last of the string to the output. - output[1] = str.substring(x); - return output; - } + //===================================================================== + //Function: msgLength + //Input: String str: The string to find the length of + //Output: int: The length on the screen of a string + //Use: Finds the length on the screen of a string. Ignores MCChatColor. + //===================================================================== + public static int msgLength(String str) { + int length = 0; + //Loop through all the characters, skipping any color characters + //and their following color codes + for(int x = 0; x < str.length(); x++) { + if(str.charAt(x) == '§' /*|| str.charAt(x) == MCChatColor.White.charAt(0)*/) { + if(x + 1 != str.length()) { + if(colorChange(str.charAt(x + 1)) != null) { + x++; + continue; + } + } + } + int len = charLength(str.charAt(x)); + length += len; + } + return length; + } - //===================================================================== - //Function: charLength - //Input: char x: The character to find the length of. - //Output: int: The length of the character - //Use: Finds the visual length of the character on the screen. - //===================================================================== - private static int charLength(char x) { - if ("i.:,;|!".indexOf(x) != -1) { - return 2; - } else if ("l'".indexOf(x) != -1) { - return 3; - } else if ("tI[]".indexOf(x) != -1) { - return 4; - } else if ("fk{}<>\"*()".indexOf(x) != -1) { - return 5; - } else if ("abcdeghjmnopqrsuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ1234567890\\/#?$%-=_+&^".indexOf(x) != -1) { - return 6; - } else if ("@~".indexOf(x) != -1) { - return 7; - } else if (x == ' ') { - return 4; - } else { - return -1; - } - } + //===================================================================== + //Function: wordCut + //Input: String str: The string to find the length of + //Output: String[]: The cut up word + //Use: Cuts apart a word that is too long to fit on one line + //===================================================================== + private static String[] wordCut(int lengthBefore, String str, int lineLength) { + int length = lengthBefore; + //Loop through all the characters, skipping any color characters + //and their following color codes + String[] output = new String[2]; + int x = 0; + while(length < lineLength && x < str.length()) { + int len = charLength(str.charAt(x)); + if(len > 0) { + length += len; + } else { + x++; + } + x++; + } + if(x > str.length()) { + x = str.length(); + } + //Add the substring to the output after cutting it + output[0] = str.substring(0, x); + //Add the last of the string to the output. + output[1] = str.substring(x); + return output; + } - //===================================================================== - //Function: colorChange - //Input: char colour: The color code to find the color for - //Output: String: The color that the code identified - //Use: Finds a color giving a color code - //===================================================================== - public static String colorChange(char colour) { - MCChatColor color; - switch (colour) { - case '0': - color = MCChatColor.BLACK; - break; - case '1': - color = MCChatColor.DARK_BLUE; - break; - case '2': - color = MCChatColor.DARK_GREEN; - break; - case '3': - color = MCChatColor.DARK_AQUA; - break; - case '4': - color = MCChatColor.DARK_RED; - break; - case '5': - color = MCChatColor.DARK_PURPLE; - break; - case '6': - color = MCChatColor.GOLD; - break; - case '7': - color = MCChatColor.GRAY; - break; - case '8': - color = MCChatColor.DARK_GRAY; - break; - case '9': - color = MCChatColor.BLUE; - break; - case 'a': - color = MCChatColor.GREEN; - break; - case 'b': - color = MCChatColor.AQUA; - break; - case 'c': - color = MCChatColor.RED; - break; - case 'd': - color = MCChatColor.LIGHT_PURPLE; - break; - case 'e': - color = MCChatColor.YELLOW; - break; - case 'f': - color = MCChatColor.WHITE; - break; - case 'A': - color = MCChatColor.GREEN; - break; - case 'B': - color = MCChatColor.AQUA; - break; - case 'C': - color = MCChatColor.RED; - break; - case 'D': - color = MCChatColor.LIGHT_PURPLE; - break; - case 'E': - color = MCChatColor.YELLOW; - break; - case 'F': - color = MCChatColor.WHITE; - break; - default: - return null; - } - return color.toString(); - } + //===================================================================== + //Function: charLength + //Input: char x: The character to find the length of. + //Output: int: The length of the character + //Use: Finds the visual length of the character on the screen. + //===================================================================== + private static int charLength(char x) { + if("i.:,;|!".indexOf(x) != -1) { + return 2; + } else if("l'".indexOf(x) != -1) { + return 3; + } else if("tI[]".indexOf(x) != -1) { + return 4; + } else if("fk{}<>\"*()".indexOf(x) != -1) { + return 5; + } else if("abcdeghjmnopqrsuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ1234567890\\/#?$%-=_+&^".indexOf(x) != -1) { + return 6; + } else if("@~".indexOf(x) != -1) { + return 7; + } else if(x == ' ') { + return 4; + } else { + return -1; + } + } + + //===================================================================== + //Function: colorChange + //Input: char colour: The color code to find the color for + //Output: String: The color that the code identified + //Use: Finds a color giving a color code + //===================================================================== + public static String colorChange(char colour) { + MCChatColor color; + switch(colour) { + case '0': + color = MCChatColor.BLACK; + break; + case '1': + color = MCChatColor.DARK_BLUE; + break; + case '2': + color = MCChatColor.DARK_GREEN; + break; + case '3': + color = MCChatColor.DARK_AQUA; + break; + case '4': + color = MCChatColor.DARK_RED; + break; + case '5': + color = MCChatColor.DARK_PURPLE; + break; + case '6': + color = MCChatColor.GOLD; + break; + case '7': + color = MCChatColor.GRAY; + break; + case '8': + color = MCChatColor.DARK_GRAY; + break; + case '9': + color = MCChatColor.BLUE; + break; + case 'a': + color = MCChatColor.GREEN; + break; + case 'b': + color = MCChatColor.AQUA; + break; + case 'c': + color = MCChatColor.RED; + break; + case 'd': + color = MCChatColor.LIGHT_PURPLE; + break; + case 'e': + color = MCChatColor.YELLOW; + break; + case 'f': + color = MCChatColor.WHITE; + break; + case 'A': + color = MCChatColor.GREEN; + break; + case 'B': + color = MCChatColor.AQUA; + break; + case 'C': + color = MCChatColor.RED; + break; + case 'D': + color = MCChatColor.LIGHT_PURPLE; + break; + case 'E': + color = MCChatColor.YELLOW; + break; + case 'F': + color = MCChatColor.WHITE; + break; + default: + return null; + } + return color.toString(); + } } diff --git a/src/main/java/com/laytonsmith/abstraction/AbstractConvertor.java b/src/main/java/com/laytonsmith/abstraction/AbstractConvertor.java index 3658b47623..24fd05dba6 100644 --- a/src/main/java/com/laytonsmith/abstraction/AbstractConvertor.java +++ b/src/main/java/com/laytonsmith/abstraction/AbstractConvertor.java @@ -1,38 +1,69 @@ package com.laytonsmith.abstraction; import com.laytonsmith.PureUtilities.DaemonManager; +import com.laytonsmith.core.events.BindableEvent; +import com.laytonsmith.core.events.Driver; +import com.laytonsmith.core.events.EventUtils; import java.util.ArrayList; +import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Logger; -/** - * - * - */ -public abstract class AbstractConvertor implements Convertor{ - - private List shutdownHooks = new ArrayList(); +public abstract class AbstractConvertor implements Convertor { + + private final List shutdownHooks = new ArrayList<>(); + private final List persistentShutdownHooks = new ArrayList<>(); @Override public void addShutdownHook(Runnable r) { shutdownHooks.add(r); } + @Override + public void addPersistentShutdownHook(Runnable r) { + persistentShutdownHooks.add(r); + } + @Override public void runShutdownHooks() { + // Fire off the shutdown event, before we shut down all the internal hooks + EventUtils.TriggerListener(Driver.SHUTDOWN, "shutdown", new BindableEvent() { + + @Override + public Object _GetObject() { + return new Object(); + } + }); Iterator iter = shutdownHooks.iterator(); - while(iter.hasNext()){ - iter.next().run(); + while(iter.hasNext()) { + try { + iter.next().run(); + } catch(Error e) { + Logger.getLogger(AbstractConvertor.class.getName()).severe(e.getMessage()); + } iter.remove(); } + + for(Runnable r : persistentShutdownHooks) { + try { + r.run(); + } catch(Error e) { + Logger.getLogger(AbstractConvertor.class.getName()).severe(e.getMessage()); + } + } } - + /** - * Runs the task either now or later. In the case of a default Convertor, - * it just runs the task now. + * Runs the task either now or later. In the case of a default Convertor, it just runs the task now. + * * @param dm - * @param r + * @param r */ @Override public void runOnMainThreadLater(DaemonManager dm, Runnable r) { @@ -40,22 +71,166 @@ public void runOnMainThreadLater(DaemonManager dm, Runnable r) { } @Override - public T runOnMainThreadAndWait(Callable callable) throws Exception{ + public T runOnMainThreadAndWait(Callable callable) throws Exception { return (T) callable.call(); - } + } @Override public MCWorldCreator getWorldCreator(String worldName) { throw new UnsupportedOperationException("Not supported."); } - + @Override public MCCommand getNewCommand(String name) { throw new UnsupportedOperationException("Not supported in this implementation."); } - + @Override public MCCommandSender GetCorrectSender(MCCommandSender unspecific) { throw new UnsupportedOperationException("Not supported in this implementation."); } + + private final Map tasks = new HashMap<>(); + private final AtomicInteger taskIDs = new AtomicInteger(0); + + @Override + public void ClearAllRunnables() { + synchronized(tasks) { + for(Task task : tasks.values()) { + task.unregister(); + } + tasks.clear(); + } + } + + @Override + public void ClearFutureRunnable(int id) { + synchronized(tasks) { + if(tasks.containsKey(id)) { + tasks.get(id).unregister(); + tasks.remove(id); + } + } + } + + @Override + public int SetFutureRepeater(DaemonManager dm, long ms, long initialDelay, final Runnable r) { + int id = taskIDs.getAndIncrement(); + Task t = new Task(id, dm, true, initialDelay, ms, new Runnable() { + + @Override + public void run() { + triggerRunnable(r); + } + }); + synchronized(tasks) { + tasks.put(id, t); + t.register(); + } + return id; + } + + @Override + public int SetFutureRunnable(DaemonManager dm, long ms, final Runnable r) { + int id = taskIDs.getAndIncrement(); + Task t = new Task(id, dm, false, ms, 0, new Runnable() { + + @Override + public void run() { + triggerRunnable(r); + } + }); + synchronized(tasks) { + tasks.put(id, t); + t.register(); + } + return id; + } + + /** + * A subclass may need to do special handling for the actual trigger of a scheduled task, though not need to do + * anything special for the scheduling itself. In this case, subclasses may override this method, and whenever a + * scheduled task is intended to be run, it will be passed to this method instead. By default, the runnable is + * simply run. + * + * @param r + */ + protected synchronized void triggerRunnable(Runnable r) { + r.run(); + } + + private class Task { + + /** + * The task id + */ + private final int id; + /** + * The DaemonManager + */ + private final DaemonManager dm; + /** + * True if this is an interval, false otherwise. + */ + private final boolean repeater; + /** + * The initial delay. For timeouts, this is just the delay. + */ + private final long initialDelay; + /** + * The delay between triggers. For intervals, this is ignored. + */ + private final long interval; + /** + * The task itself. + */ + private final Runnable task; + + private Timer timer; + + public Task(int id, DaemonManager dm, boolean repeater, long initialDelay, long interval, Runnable task) { + this.id = id; + this.dm = dm; + this.repeater = repeater; + this.initialDelay = initialDelay; + if(repeater) { + this.interval = interval; + } else { + this.interval = Long.MAX_VALUE; + } + this.task = task; + } + + public void register() { + timer = new Timer(); + timer.schedule(new TimerTask() { + + @Override + public void run() { + task.run(); + if(!repeater) { + unregister(); + synchronized(tasks) { + tasks.remove(id); + } + } + } + }, initialDelay, interval); + if(dm != null) { + dm.activateThread(null); + } + } + + public void unregister() { + timer.cancel(); + if(dm != null) { + dm.deactivateThread(null); + } + } + + public int getId() { + return id; + } + } + } diff --git a/src/main/java/com/laytonsmith/abstraction/AbstractionObject.java b/src/main/java/com/laytonsmith/abstraction/AbstractionObject.java index 0b0effdf75..821aac30a0 100644 --- a/src/main/java/com/laytonsmith/abstraction/AbstractionObject.java +++ b/src/main/java/com/laytonsmith/abstraction/AbstractionObject.java @@ -1,34 +1,35 @@ package com.laytonsmith.abstraction; /** - * All AbstractionObject implementations should know how to both return their underlying object, - * and construct a new object, given that it is a compatible type. This has the advantage of keeping - * this logic completely inside that particular object, instead of having a giant cast tree that creates - * a new concrete class for each type. Further, since supertypes can implement the getHandle method, only - * subclasses that need to implement the false constructor have to. A template implementation is given in the - * source below. - * + * All AbstractionObject implementations should know how to both return their underlying object, and construct a new + * object, given that it is a compatible type. This has the advantage of keeping this logic completely inside that + * particular object, instead of having a giant cast tree that creates a new concrete class for each type. Further, + * since supertypes can implement the getHandle method, only subclasses that need to implement the false constructor + * have to. A template implementation is given in the source below. + * */ public interface AbstractionObject { - /** - * The underlying object that the abstraction object wraps. This can be used in combination with - * instanceof MCType to determine if the wrapped type can be cast to Type. - * @return - */ - public Object getHandle(); + + /** + * The underlying object that the abstraction object wraps. This can be used in combination with instanceof MCType + * to determine if the wrapped type can be cast to Type. + * + * @return + */ + Object getHandle(); } /* public BukkitMC<>(AbstractionObject a){ - this((<>)null); - if(a instanceof MC<>){ - this.<> = ((<>)a.getHandle()); - } else { - throw new ClassCastException(); - } + this((<>)null); + if(a instanceof MC<>){ + this.<> = ((<>)a.getHandle()); + } else { + throw new ClassCastException(); + } } public Object getHandle(){ - return <>; + return <>; } */ diff --git a/src/main/java/com/laytonsmith/abstraction/Convertor.java b/src/main/java/com/laytonsmith/abstraction/Convertor.java index 56917caff2..93405d5762 100644 --- a/src/main/java/com/laytonsmith/abstraction/Convertor.java +++ b/src/main/java/com/laytonsmith/abstraction/Convertor.java @@ -1,195 +1,243 @@ - package com.laytonsmith.abstraction; import com.laytonsmith.PureUtilities.DaemonManager; import com.laytonsmith.abstraction.blocks.MCMaterial; +import com.laytonsmith.abstraction.entities.MCTransformation; +import com.laytonsmith.abstraction.enums.MCAttribute; +import com.laytonsmith.abstraction.enums.MCDyeColor; +import com.laytonsmith.abstraction.enums.MCEquipmentSlot; +import com.laytonsmith.abstraction.enums.MCEquipmentSlotGroup; +import com.laytonsmith.abstraction.enums.MCPatternShape; +import com.laytonsmith.abstraction.enums.MCPotionType; import com.laytonsmith.abstraction.enums.MCRecipeType; import com.laytonsmith.abstraction.enums.MCTone; import com.laytonsmith.commandhelper.CommandHelperPlugin; import com.laytonsmith.core.constructs.Target; -import com.laytonsmith.core.functions.Exceptions; +import com.laytonsmith.core.environments.Environment; +import com.laytonsmith.core.exceptions.CRE.CREFormatException; + import java.util.List; +import java.util.UUID; import java.util.concurrent.Callable; -import java.util.concurrent.Future; +import org.joml.Quaternionf; +import org.joml.Vector3f; /** - * This should be implemented once for each server type. It mostly wraps - * static methods, but also provides methods for getting other server specific - * things. You can get an instance of the current Convertor by looking for the - * @convert tag. StaticLayer wraps all the functionality for you - * however. - * @author layton + * This should be implemented once for each server type. It mostly wraps static methods, but also provides methods for + * getting other server specific things. You can get an instance of the current Convertor by looking for the + * @convert tag. StaticLayer wraps all the functionality for you however. */ public interface Convertor { - public MCLocation GetLocation(MCWorld w, double x, double y, double z, float yaw, float pitch); - public Class GetServerEventMixin(); + MCLocation GetLocation(MCWorld w, double x, double y, double z, float yaw, float pitch); - public MCEnchantment[] GetEnchantmentValues(); + Class GetServerEventMixin(); - /** - * Returns the enchantment, given an enchantment name (or a string'd number). - * Returns null if no such enchantment exists. - * @param name - * @return - */ - public MCEnchantment GetEnchantmentByName(String name); + MCServer GetServer(); + + MCItemStack GetItemStack(MCMaterial type, int qty); + + MCItemStack GetItemStack(String type, int qty); + + MCPotionData GetPotionData(MCPotionType type, boolean extended, boolean upgraded); + + MCAttributeModifier GetAttributeModifier(MCAttribute attr, UUID id, String name, double amt, MCAttributeModifier.Operation op, MCEquipmentSlot slot); - public MCServer GetServer(); + MCAttributeModifier GetAttributeModifier(MCAttribute attr, UUID id, String name, double amt, MCAttributeModifier.Operation op, MCEquipmentSlotGroup slot); - public MCItemStack GetItemStack(int type, int qty); + MCAttributeModifier GetAttributeModifier(MCAttribute attr, MCNamespacedKey key, double amt, MCAttributeModifier.Operation op, MCEquipmentSlot slot); - public void Startup(CommandHelperPlugin chp); + MCAttributeModifier GetAttributeModifier(MCAttribute attr, MCNamespacedKey key, double amt, MCAttributeModifier.Operation op, MCEquipmentSlotGroup slot); - public int LookupItemId(String materialName); + void Startup(CommandHelperPlugin chp); - public String LookupMaterialName(int id); + MCMaterial[] GetMaterialValues(); - public MCItemStack GetItemStack(int type, int data, int qty); + MCMaterial GetMaterialFromLegacy(String name, int data); - public MCMaterial getMaterial(int id); + MCMaterial GetMaterialFromLegacy(int id, int data); - public MCMaterial GetMaterial(String name); + MCMaterial GetMaterial(String name); - public MCMetadataValue GetMetadataValue(Object value, MCPlugin plugin); + MCMetadataValue GetMetadataValue(Object value, MCPlugin plugin); - /** - * A future runnable is run on a server accessible thread at roughly the time specified in the future. - * This is no guarantee however, as the particular server implementation may make this hard to do. The - * value returned is - * @param r - * @return - */ - public int SetFutureRunnable(DaemonManager dm, long ms, Runnable r); + /** + * A future runnable is run on a server accessible thread at roughly the time specified in the future. This is no + * guarantee however, as the particular server implementation may make this hard to do. The value returned is + * + * @param dm + * @param ms + * @param r + * @return + */ + int SetFutureRunnable(DaemonManager dm, long ms, Runnable r); - public void ClearAllRunnables(); + /** + * Clears all future runnables, but does not interrupt existing ones. + */ + void ClearAllRunnables(); - public void ClearFutureRunnable(int id); + /** + * Clears a future runnable task by id. + * + * @param id + */ + void ClearFutureRunnable(int id); - public int SetFutureRepeater(DaemonManager dm, long ms, long initialDelay, Runnable r); + /** + * Adds a future repeater + * + * @param dm + * @param ms + * @param initialDelay + * @param r + * @return + */ + int SetFutureRepeater(DaemonManager dm, long ms, long initialDelay, Runnable r); - public MCEntity GetCorrectEntity(MCEntity e); + MCEntity GetCorrectEntity(MCEntity e); - public MCItemMeta GetCorrectMeta(MCItemMeta im); + MCItemMeta GetCorrectMeta(MCItemMeta im); /** - * Returns the entities at the specified location, or null - * if no entities are in this location. + * Returns the entities at the specified location, or null if no entities are in this location. + * * @param loc * @return */ - public List GetEntitiesAt(MCLocation loc, double radius); + List GetEntitiesAt(MCLocation loc, double radius); /** - * Gets the inventory of the specified entity, or null if the entity id - * is invalid - * @param entityID + * Gets the inventory of the specified entity, or null if the entity id is invalid + * + * @param entity * @return */ - public MCInventory GetEntityInventory(int entityID); + MCInventory GetEntityInventory(MCEntity entity); /** - * Returns the inventory of the block at the specified location, if it is - * an inventory type block, or null if otherwise invalid. + * Returns the inventory of the block at the specified location, if it is an inventory type block, or null if + * otherwise invalid. + * * @param location * @return */ - public MCInventory GetLocationInventory(MCLocation location); + MCInventory GetLocationInventory(MCLocation location); + + MCInventoryHolder CreateInventoryHolder(String id, String title); + + /** + * Runs whenever the server is shutting down (or reloading). There is no guarantee provided as to what thread the + * runnables actually run on, so you should ensure that the runnable executes it's actions on the appropriate thread + * yourself. Note that this shutdown hook will only run once, so if multiple reload events occur, this will not + * be registered for the second run, unless you specifically add it yourself. If you need a shutdown hook to run + * every time, and only want to register it once, see {@link #addPersistentShutdownHook}. + * + * @param r + */ + void addShutdownHook(Runnable r); /** - * Run whenever the server is shutting down (or restarting). There is no - * guarantee provided as to what thread the runnables actually run on, so you should - * ensure that the runnable executes it's actions on the appropriate thread - * yourself. + * Runs whenever the server is shutting down (or reloading). There is no guarantee provided as to what thread the + * runnables actually run on, so you should ensure that the runnable executes it's actions on the appropriate thread + * yourself. Note that this shutdown hook will never dequeue, so if multiple reload events occur, this will run + * every time, and so you should only call this once (i.e. from a static context). If you need a shutdown hook to + * dequeue after run, see {@link #addShutdownHook}. * @param r */ - public void addShutdownHook(Runnable r); + void addPersistentShutdownHook(Runnable r); /** - * Runs all the registered shutdown hooks. This should only be called by the shutdown mechanism. - * After running, each Runnable will be removed from the queue. + * Runs all the registered shutdown hooks. This should only be called by the shutdown mechanism. After running, each + * Runnable will be removed from the queue. */ - public void runShutdownHooks(); + void runShutdownHooks(); /** - * Runs some task on the "main" thread, possibly now, possibly in the future, and - * possibly even on this thread. However, if the platform needs some critical action - * to happen on one thread, (for instance, UI updates on the UI thread) those actions - * will occur here. + * Runs some task on the "main" thread, possibly now, possibly in the future, and possibly even on this thread. + * However, if the platform needs some critical action to happen on one thread, (for instance, UI updates on the UI + * thread) those actions will occur here. + * * @param dm * @param r */ - public void runOnMainThreadLater(DaemonManager dm, Runnable r); + void runOnMainThreadLater(DaemonManager dm, Runnable r); /** * Works like runOnMainThreadLater, but waits for the task to finish. + * * @param * @param callable * @return * @throws java.lang.Exception */ - public T runOnMainThreadAndWait(Callable callable) throws Exception; + T runOnMainThreadAndWait(Callable callable) throws Exception; /** * Returns a MCWorldCreator object for the given world name. + * * @param worldName * @return */ - public MCWorldCreator getWorldCreator(String worldName); + MCWorldCreator getWorldCreator(String worldName); /** * Gets a note object, which can be used to play a sound + * * @param octave May be 0-2 * @param tone * @param sharp If the note should be a sharp (only applies to some tones) * @return */ - public MCNote GetNote(int octave, MCTone tone, boolean sharp); + MCNote GetNote(int octave, MCTone tone, boolean sharp); /** - * Returns the max block ID number supported by this server. - * @return - */ - public int getMaxBlockID(); - - /** - * Returns the max item ID number supported by this server. - * @return - */ - public int getMaxItemID(); - - /** - * Returns the max record ID number supported by this server. + * Returns a color object for this server. + * + * @param red + * @param green + * @param blue * @return */ - public int getMaxRecordID(); + MCColor GetColor(int red, int green, int blue); /** - * Returns a color object for this server. + * Returns a transparent color object for this server. + * * @param red * @param green * @param blue + * @param alpha * @return */ - public MCColor GetColor(int red, int green, int blue); + MCColor GetColor(int red, int green, int blue, int alpha); /** - * Returns a color object given the color name. The color - * name must come from the standard color types, or a + * Returns a color object given the color name. The color name must come from the standard color types, or a * FormatException is thrown. + * * @param colorName * @param t * @return */ - public MCColor GetColor(String colorName, Target t) throws Exceptions.FormatException; + MCColor GetColor(String colorName, Target t) throws CREFormatException; + + /** + * Returns a pattern object + * + * @param color + * @param shape + */ + MCPattern GetPattern(MCDyeColor color, MCPatternShape shape); /** * Returns an MCFirework which can be built. + * * @return */ - public MCFireworkBuilder GetFireworkBuilder(); + MCFireworkBuilder GetFireworkBuilder(); - public MCPluginMeta GetPluginMeta(); + MCPluginMeta GetPluginMeta(); /** * Creates a new properly typed recipe instance @@ -198,7 +246,7 @@ public interface Convertor { * @param result the itemstack the recipe will result in * @return */ - public MCRecipe GetNewRecipe(MCRecipeType type, MCItemStack result); + MCRecipe GetNewRecipe(String key, MCRecipeType type, MCItemStack result); /** * Used to convert a generic recipe into the correct type @@ -206,31 +254,72 @@ public interface Convertor { * @param unspecific type * @return specific type */ - public MCRecipe GetRecipe(MCRecipe unspecific); + MCRecipe GetRecipe(MCRecipe unspecific); /** * * @param name * @return a new MCCommand instance */ - public MCCommand getNewCommand(String name); + MCCommand getNewCommand(String name); /** * - * @param an ambiguous MCCommandSender + * @param unspecific an ambiguous MCCommandSender * @return a properly typed MCCommandSender */ - public MCCommandSender GetCorrectSender(MCCommandSender unspecific); + MCCommandSender GetCorrectSender(MCCommandSender unspecific); /** * Returns the name of CommandHelper by parsing the plugin.yml file. + * * @return */ - public String GetPluginName(); + String GetPluginName(); /** * Returns a MCPlugin instance of CommandHelper. + * + * @return + */ + MCPlugin GetPlugin(); + + /** + * Returns the name of the current user, or null if this doesn't make sense in the given platform. + * + * @param env The runtime environment, in case the convertor needs it + * @return The username + */ + String GetUser(Environment env); + + /** + * Returns a Minecraft namespaced key object from a string. + * The key can only alphanumeric characters, dots, underscores, and dashes. + * A preceding namespace can be delimited with a single colon, which can also have forward slashes. + * Example: "path/commandhelper:my_tag". + * If no namespace is given, it will default to "commandhelper". + * + * @param key a string formatted key + * @return a key object + */ + MCNamespacedKey GetNamespacedKey(String key); + + /** + * Returns a new Transformation object. + * @param leftRotation + * @param rightRotation + * @param scale + * @param translation + * @return + */ + public MCTransformation GetTransformation(Quaternionf leftRotation, Quaternionf rightRotation, Vector3f scale, Vector3f translation); + + /** + * Returns true if this is the main thread of the application. This is only applicable in some managed environments, + * in other environments where this doesn't matter, this will always return false (i.e. all threads are considered + * equally important/unimportant). If this returns true, this means that the current thread is for instance the + * UI thread, and thus should not be blocked on. * @return */ - public MCPlugin GetPlugin(); + public boolean IsMainThread(); } diff --git a/src/main/java/com/laytonsmith/abstraction/ConvertorHelper.java b/src/main/java/com/laytonsmith/abstraction/ConvertorHelper.java index f2b552beb2..e9da89a452 100644 --- a/src/main/java/com/laytonsmith/abstraction/ConvertorHelper.java +++ b/src/main/java/com/laytonsmith/abstraction/ConvertorHelper.java @@ -1,20 +1,20 @@ package com.laytonsmith.abstraction; import com.laytonsmith.core.constructs.Target; -import com.laytonsmith.core.functions.Exceptions; +import com.laytonsmith.core.exceptions.CRE.CREFormatException; /** - * Some methods in Convertor can be abstract regardless of the server implementation, - * and so can therefore be abstracted out. However, since Convertor is an interface, - * a helper is here, and subclasses can just call these static methods directly. + * Some methods in Convertor can be abstract regardless of the server implementation, and so can therefore be abstracted + * out. However, since Convertor is an interface, a helper is here, and subclasses can just call these static methods + * directly. */ public class ConvertorHelper { - public static MCColor GetColor(String colorName, Target t) throws Exceptions.FormatException { - if(MCColor.STANDARD_COLORS.containsKey(colorName.toUpperCase())){ - return MCColor.STANDARD_COLORS.get(colorName.toUpperCase()); + public static MCColor GetColor(String colorName, Target t) throws CREFormatException { + if(MCColor.STANDARD_COLORS.containsKey(colorName.toUpperCase())) { + return MCColor.STANDARD_COLORS.get(colorName.toUpperCase()); } else { - throw new Exceptions.FormatException("Unknown color type: " + colorName, t); + throw new CREFormatException("Unknown color type: " + colorName, t); } } diff --git a/src/main/java/com/laytonsmith/abstraction/Implementation.java b/src/main/java/com/laytonsmith/abstraction/Implementation.java index 6e20c411ef..df9b2b6afd 100644 --- a/src/main/java/com/laytonsmith/abstraction/Implementation.java +++ b/src/main/java/com/laytonsmith/abstraction/Implementation.java @@ -2,18 +2,21 @@ import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery; import com.laytonsmith.PureUtilities.Common.ReflectionUtils; +import com.laytonsmith.PureUtilities.Common.ReflectionUtils.ReflectionException; import com.laytonsmith.abstraction.enums.EnumConvertor; +import com.laytonsmith.abstraction.enums.MCVersion; import com.laytonsmith.annotations.abstractionenum; -import com.laytonsmith.core.Prefs; -import java.lang.reflect.InvocationTargetException; +import com.laytonsmith.core.LogLevel; +import com.laytonsmith.core.MSLog; +import com.laytonsmith.core.MSLog.Tags; +import com.laytonsmith.core.constructs.Target; + import java.lang.reflect.Method; -import java.util.Set; /** - * This class dynamically detects the server version being run, using various - * checks as needed. + * This class dynamically detects the server version being run, using various checks as needed. + * * - * */ public final class Implementation { @@ -24,143 +27,165 @@ private Implementation() { } private static Implementation.Type serverType = null; private static boolean useAbstractEnumThread = true; - + /** - * Sets whether or not we should verify enums when setServerType is called. - * Defaults to true. - * @param on + * Sets whether or not we should verify enums when setServerType is called. Defaults to true. + * + * @param on */ - public static void useAbstractEnumThread(boolean on){ + public static void useAbstractEnumThread(boolean on) { useAbstractEnumThread = on; } - + /** - * This method works like setServerType, except it does not check to - * see that the server type wasn't already set. This should only be - * used by the embedded tools or other meta code, not during normal - * execution. This does not trigger the abstract enum thread. - * @param type + * This method works like setServerType, except it does not check to see that the server type wasn't already set. + * This should only be used by the embedded tools or other meta code, not during normal execution. This does not + * trigger the abstract enum thread. + * + * @param type */ - public static void forceServerType(Implementation.Type type){ + public static void forceServerType(Implementation.Type type) { serverType = type; } + /** + * Sets the server type in normal usage. In normal usage, this can only be called once, and additional calls + * are an error. While {@link #forceServerType(com.laytonsmith.abstraction.Implementation.Type)} does exist, and + * can be used to bypass this, and it may be tempting to check server if {@link #GetServerType} throws an exception + * before setting this, these temptations should be avoided (except where explicitly allowed) as fixing your code + * in any other way will inevitably lead to other problems down the road. This code should only be called once, + * with the correct type, since the code makes assumptions based on the first type sent to it. Additionally, setting + * the type twice with the same type, while it would not directly cause an error, is still a serious code smell, and + * indicates a larger code organization issue, and so temptation should still be avoided to modifying this code + * to allow the same server type to be set the second time, or causing the second call to just be ignored. + * @param type The server type to set. + */ public static void setServerType(Implementation.Type type) { - if (serverType == null) { + if(serverType == null) { serverType = type; } else { - if (type != Type.TEST) { //This could potentially happen, but we don't care in the case that we + if(type != Type.TEST) { //This could potentially happen, but we don't care in the case that we //are testing, so don't error out here. (Failures may occur elsewhere though... :() throw new RuntimeException("Server type is already set! Cannot re-set!"); } } + if(type == Type.TEST || type == Type.SHELL || !useAbstractEnumThread) { + return; + } //Fire off our abstractionenum checks in a new Thread - if (type != Type.TEST && type != Type.SHELL && useAbstractEnumThread) { - Thread abstractionenumsThread; - abstractionenumsThread = new Thread(new Runnable() { - @Override - public void run() { + Thread abstractionenumsThread = new Thread(() -> { + try { + try { + //Let the server startup data blindness go by first, so we display any error messages prominently, + //since an Error is a case of very bad code that shouldn't have been released to begin with. + Thread.sleep(15000); + } catch (InterruptedException ex) { + // + } + for(Class c : ClassDiscovery.getDefaultInstance().loadClassesWithAnnotation(abstractionenum.class)) { + abstractionenum annotation = (abstractionenum) c.getAnnotation(abstractionenum.class); + if(!EnumConvertor.class.isAssignableFrom(c)) { + throw new Error("Only classes that extend EnumConvertor may use @abstractionenum. " + + c.getName() + " does not, yet it uses the annotation."); + } + //Now, if this is not the current server type, skip it + if(annotation.implementation() != serverType) { + continue; + } + EnumConvertor convertor; try { - try { - //Let the server startup data blindness go by first, so we display any error messages prominently, - //since an Error is a case of very bad code that shouldn't have been released to begin with. - Thread.sleep(15000); - } catch (InterruptedException ex) { - // - } - StaticLayer.GetConvertor().getMaxBlockID(); - Set abstractionenums = ClassDiscovery.getDefaultInstance().loadClassesWithAnnotation(abstractionenum.class); - for (Class c : abstractionenums) { - abstractionenum annotation = (abstractionenum) c.getAnnotation(abstractionenum.class); - if (EnumConvertor.class.isAssignableFrom(c)) { - EnumConvertor convertor; - try { - //Now, if this is not the current server type, skip it - if (annotation.implementation() != serverType) { - continue; - } - //Next, verify usage of the annotation (it is an error if not used properly) - //All EnumConvertor subclasses should have public static getConvertor methods, let's grab it now - Method m = c.getDeclaredMethod("getConvertor"); - convertor = (EnumConvertor) m.invoke(null); - //Go through and check for a proper mapping both ways, from concrete to abstract, and vice versa. - //At this point, if there is an error, it is only a warning, NOT an error. - Class abstractEnum = annotation.forAbstractEnum(); - Class concreteEnum = annotation.forConcreteEnum(); - checkEnumConvertors(convertor, abstractEnum, concreteEnum, false); - checkEnumConvertors(convertor, concreteEnum, abstractEnum, true); - - } catch (IllegalAccessException ex) { - throw new Error(ex); - } catch (IllegalArgumentException ex) { - throw new Error(ex); - } catch (InvocationTargetException ex) { - throw new Error(ex); - } catch (NoSuchMethodException ex) { - throw new Error(serverType.getBranding() + ": The method with signature public static " + c.getName() + " getConvertor() was not found in " + c.getName() - + " Please add the following code: \n" - + "private static " + c.getName() + " instance;\n" - + "public static " + c.getName() + " getConvertor(){\n" - + "\tif(instance == null){\n" - + "\t\tinstance = new " + c.getName() + "();\n" - + "\t}\n" - + "\treturn instance;\n" - + "}\n" - + "If you do not know what error is, please report this to the developers."); - } - } else { - throw new Error("Only classes that extend EnumConvertor may use @abstractionenum. " + c.getName() + " does not, yet it uses the annotation."); - } - - } - } catch (Exception e) { - boolean debugMode; - try { - debugMode = Prefs.DebugMode(); - } catch(RuntimeException ex){ - //Set it to true if we fail to load prefs, which can happen - //with a buggy front end. - debugMode = true; - } - if (debugMode) { - //If we're in debug mode, sure, go ahead and print the stack trace, - //but otherwise we don't want to bother the user. - e.printStackTrace(); - } + //Next, verify usage of the annotation (it is an error if not used properly) + //All EnumConvertor subclasses should have public static getConvertor methods, let's grab it now + Method m = c.getDeclaredMethod("getConvertor"); + convertor = (EnumConvertor) m.invoke(null); + } catch (NoSuchMethodException ex) { + throw new Error("The method with signature public static " + c.getName() + + " getConvertor() was not found in " + c.getName() + "." + + " Please add the following code: \n" + + "private static " + c.getName() + " instance;\n" + + "public static " + c.getName() + " getConvertor(){\n" + + "\tif(instance == null){\n" + + "\t\tinstance = new " + c.getName() + "();\n" + + "\t}\n" + + "\treturn instance;\n" + + "}\n" + + "If you do not know what error is, please report this to the developers."); + } + //Go through and check for a proper mapping both ways, from concrete to abstract, and vice versa. + //At this point, if there is an error, it is only a warning, NOT an error. + if(MSLog.GetLogger().WillLog(Tags.GENERAL, LogLevel.WARNING)) { + Class abstractEnum = annotation.forAbstractEnum(); + Class concreteEnum = annotation.forConcreteEnum(); + checkAbstractEnumConversion(convertor, abstractEnum, concreteEnum); + checkConcreteEnumConversion(convertor, concreteEnum, abstractEnum); } } - }, "Abstraction Enum Verification Thread"); - abstractionenumsThread.setPriority(Thread.MIN_PRIORITY); - abstractionenumsThread.setDaemon(true); - abstractionenumsThread.start(); + } catch (Exception e) { + MSLog.GetLogger().e(Tags.GENERAL, e, Target.UNKNOWN); + } + }, "Abstraction Enum Verification Thread"); + abstractionenumsThread.setPriority(Thread.MIN_PRIORITY); + abstractionenumsThread.setDaemon(true); + abstractionenumsThread.start(); + } + + private static void checkAbstractEnumConversion(EnumConvertor convertor, Class abstracted, Class concrete) { + for(Enum abstractValue : abstracted.getEnumConstants()) { + Enum enumConcrete; + Deprecated deprecated; + try { + enumConcrete = ReflectionUtils.invokeMethod(convertor, "getConcreteEnumCustom", abstractValue); + deprecated = concrete.getField(enumConcrete.name()).getAnnotation(Deprecated.class); + } catch (NoSuchFieldException | ReflectionException ex) { + // Log missing concrete values for existing abstract values. + // These can mean implementation differences, removed values, or we're running an older MC version. + MSLog.GetLogger().w(Tags.GENERAL, abstracted.getSimpleName() + "." + abstractValue.name() + + " cannot be converted to " + concrete.getSimpleName(), Target.UNKNOWN); + continue; + } + // Log deprecations of concrete values. + if(deprecated == null) { + continue; + } + if(deprecated.since().isEmpty()) { + MSLog.GetLogger().i(Tags.GENERAL, concrete.getSimpleName() + "." + enumConcrete.name() + + " is deprecated", Target.UNKNOWN); + } else if(MCVersion.match(deprecated.since().split("\\.")).lte(MCVersion.EARLIEST_SUPPORTED)) { + MSLog.GetLogger().w(Tags.GENERAL, concrete.getSimpleName() + "." + enumConcrete.name() + + " is deprecated since " + deprecated.since(), Target.UNKNOWN); + } else { + MSLog.GetLogger().i(Tags.GENERAL, concrete.getSimpleName() + "." + enumConcrete.name() + + " is deprecated since " + deprecated.since(), Target.UNKNOWN); + } } } - private static void checkEnumConvertors(EnumConvertor convertor, Class to, Class from, boolean isToConcrete) { - for (Object enumConst : from.getEnumConstants()) { - ReflectionUtils.set(EnumConvertor.class, convertor, "useError", false); + private static void checkConcreteEnumConversion(EnumConvertor convertor, Class concrete, Class abstracted) { + for(Enum concreteValue : concrete.getEnumConstants()) { try { - if (isToConcrete) { - convertor.getConcreteEnum((Enum) enumConst); - } else { - convertor.getAbstractedEnum((Enum) enumConst); - } - } catch (IllegalArgumentException e) { - //Ignored, it should have already logged it + ReflectionUtils.invokeMethod(convertor, "getAbstractedEnumCustom", concreteValue); + } catch (ReflectionException ex) { + try { + // Log missing abstract values for concrete values that are not deprecated. + if(concrete.getField(concreteValue.name()).getAnnotation(Deprecated.class) == null) { + MSLog.GetLogger().w(Tags.GENERAL, concrete.getSimpleName() + "." + concreteValue.name() + + " cannot be converted to " + abstracted.getSimpleName(), Target.UNKNOWN); + } + } catch (NoSuchFieldException ignore) {} } - ReflectionUtils.set(EnumConvertor.class, convertor, "useError", true); } } /** * These are all the supported server types */ - public static enum Type { + public enum Type { TEST("test-backend"), BUKKIT("CommandHelper"), - SHELL("MethodScript"); + SHELL("MethodScript"), + SPONGE("CommandHelper"); //GLOWSTONE, //SINGLE_PLAYER private final String branding; @@ -169,7 +194,7 @@ public static enum Type { * * @param branding This MUST be a universally acceptable folder name. */ - private Type(String branding) { + Type(String branding) { this.branding = branding; } @@ -189,7 +214,7 @@ public String getBranding() { * @return */ public static Type GetServerType() { - if (serverType == null) { + if(serverType == null) { throw new RuntimeException("Server type has not been set yet! Please call Implementation.setServerType with the appropriate implementation."); } return serverType; diff --git a/src/main/java/com/laytonsmith/abstraction/MCAgeable.java b/src/main/java/com/laytonsmith/abstraction/MCAgeable.java deleted file mode 100644 index 6a1a9ef05b..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/MCAgeable.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.laytonsmith.abstraction; - -/** - * - * @author jb_aero - */ -public interface MCAgeable extends MCLivingEntity { - - public boolean getCanBreed(); - public void setCanBreed(boolean breed); - public int getAge(); - public void setAge(int age); - public boolean getAgeLock(); - public void setAgeLock(boolean lock); - public boolean isAdult(); - public void setAdult(); - public void setBaby(); - -} diff --git a/src/main/java/com/laytonsmith/abstraction/MCAnimalTamer.java b/src/main/java/com/laytonsmith/abstraction/MCAnimalTamer.java index af3873ed26..420de1ac53 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCAnimalTamer.java +++ b/src/main/java/com/laytonsmith/abstraction/MCAnimalTamer.java @@ -1,10 +1,10 @@ - package com.laytonsmith.abstraction; -/** - * - * - */ -public interface MCAnimalTamer extends AbstractionObject{ - String getName(); +import java.util.UUID; + +public interface MCAnimalTamer extends AbstractionObject { + + String getName(); + + UUID getUniqueID(); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCAnvilInventory.java b/src/main/java/com/laytonsmith/abstraction/MCAnvilInventory.java new file mode 100644 index 0000000000..b708798505 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCAnvilInventory.java @@ -0,0 +1,18 @@ +package com.laytonsmith.abstraction; + +public interface MCAnvilInventory extends MCInventory { + MCItemStack getFirstItem(); + MCItemStack getSecondItem(); + MCItemStack getResult(); + int getMaximumRepairCost(); + int getRepairCost(); + int getRepairCostAmount(); + String getRenameText(); + + void setFirstItem(MCItemStack stack); + void setSecondItem(MCItemStack stack); + void setResult(MCItemStack stack); + void setMaximumRepairCost(int levels); + void setRepairCost(int levels); + void setRepairCostAmount(int levels); +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCArmorMeta.java b/src/main/java/com/laytonsmith/abstraction/MCArmorMeta.java new file mode 100644 index 0000000000..7a01e0aa1a --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCArmorMeta.java @@ -0,0 +1,11 @@ +package com.laytonsmith.abstraction; + +import com.laytonsmith.abstraction.enums.MCTrimMaterial; +import com.laytonsmith.abstraction.enums.MCTrimPattern; + +public interface MCArmorMeta extends MCItemMeta { + boolean hasTrim(); + void setTrim(MCTrimPattern pattern, MCTrimMaterial material); + MCTrimPattern getTrimPattern(); + MCTrimMaterial getTrimMaterial(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCAttributeModifier.java b/src/main/java/com/laytonsmith/abstraction/MCAttributeModifier.java new file mode 100644 index 0000000000..35f6d78c73 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCAttributeModifier.java @@ -0,0 +1,26 @@ +package com.laytonsmith.abstraction; + +import com.laytonsmith.abstraction.enums.MCAttribute; +import com.laytonsmith.abstraction.enums.MCEquipmentSlot; +import com.laytonsmith.abstraction.enums.MCEquipmentSlotGroup; +import com.laytonsmith.annotations.MEnum; + +import java.util.UUID; + +public interface MCAttributeModifier extends AbstractionObject { + MCNamespacedKey getKey(); + String getAttributeName(); + MCAttribute getAttribute(); + MCEquipmentSlot getEquipmentSlot(); + MCEquipmentSlotGroup getEquipmentSlotGroup(); + Operation getOperation(); + double getAmount(); + UUID getUniqueId(); + + @MEnum("com.commandhelper.AttributeModifierOperation") + enum Operation { + ADD_NUMBER, + ADD_SCALAR, + MULTIPLY_SCALAR_1 + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCAxolotlBucketMeta.java b/src/main/java/com/laytonsmith/abstraction/MCAxolotlBucketMeta.java new file mode 100644 index 0000000000..9630972d91 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCAxolotlBucketMeta.java @@ -0,0 +1,11 @@ +package com.laytonsmith.abstraction; + +import com.laytonsmith.abstraction.enums.MCAxolotlType; + +public interface MCAxolotlBucketMeta extends MCItemMeta { + + MCAxolotlType getAxolotlType(); + + void setAxolotlType(MCAxolotlType type); + +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCBannerMeta.java b/src/main/java/com/laytonsmith/abstraction/MCBannerMeta.java new file mode 100644 index 0000000000..1ffe3c97f7 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCBannerMeta.java @@ -0,0 +1,21 @@ +package com.laytonsmith.abstraction; + +import java.util.List; + +public interface MCBannerMeta extends MCItemMeta { + + void addPattern(MCPattern pattern); + + MCPattern getPattern(int i); + + List getPatterns(); + + int numberOfPatterns(); + + void removePattern(int i); + + void setPattern(int i, MCPattern pattern); + + void setPatterns(List patterns); + +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCBlockCommandSender.java b/src/main/java/com/laytonsmith/abstraction/MCBlockCommandSender.java index be748de194..e697d6af49 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCBlockCommandSender.java +++ b/src/main/java/com/laytonsmith/abstraction/MCBlockCommandSender.java @@ -2,10 +2,7 @@ import com.laytonsmith.abstraction.blocks.MCBlock; -/** - * - * - */ public interface MCBlockCommandSender extends MCCommandSender { + MCBlock getBlock(); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCBlockStateMeta.java b/src/main/java/com/laytonsmith/abstraction/MCBlockStateMeta.java new file mode 100644 index 0000000000..2d5d4d0236 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCBlockStateMeta.java @@ -0,0 +1,15 @@ +package com.laytonsmith.abstraction; + +import com.laytonsmith.abstraction.blocks.MCBlockState; + +public interface MCBlockStateMeta extends MCItemMeta { + + boolean hasBlockState(); + + MCBlockState getBlockState(); + + MCBlockState getBlockState(boolean copy); + + void setBlockState(MCBlockState state); + +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCBookMeta.java b/src/main/java/com/laytonsmith/abstraction/MCBookMeta.java index 2b41ff713d..eda3e01fdd 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCBookMeta.java +++ b/src/main/java/com/laytonsmith/abstraction/MCBookMeta.java @@ -2,25 +2,40 @@ import java.util.List; -/** - * - * @author jb_aero - */ public interface MCBookMeta extends MCItemMeta { - public boolean hasTitle(); - public boolean hasAuthor(); - public boolean hasPages(); - - public String getTitle(); - public String getAuthor(); - public List getPages(); - public int getPageCount(); - - public void addPage(String... pages); - - public boolean setTitle(String title); - public void setAuthor(String author); - public void setPages(List pages); - public void setPages(String... pages); + boolean hasTitle(); + + boolean hasAuthor(); + + boolean hasPages(); + + String getTitle(); + + String getAuthor(); + + List getPages(); + + int getPageCount(); + + void addPage(String... pages); + + boolean setTitle(String title); + + void setAuthor(String author); + + void setPages(List pages); + + void setPages(String... pages); + + Generation getGeneration(); + + void setGeneration(Generation gen); + + enum Generation { + ORIGINAL, + COPY_OF_ORIGINAL, + COPY_OF_COPY, + TATTERED + } } diff --git a/src/main/java/com/laytonsmith/abstraction/MCBossBar.java b/src/main/java/com/laytonsmith/abstraction/MCBossBar.java new file mode 100644 index 0000000000..602c05d8c6 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCBossBar.java @@ -0,0 +1,38 @@ +package com.laytonsmith.abstraction; + +import com.laytonsmith.abstraction.enums.MCBarColor; +import com.laytonsmith.abstraction.enums.MCBarStyle; + +import java.util.List; + +public interface MCBossBar extends AbstractionObject { + + String getTitle(); + + void setTitle(String title); + + MCBarColor getColor(); + + void setColor(MCBarColor color); + + MCBarStyle getStyle(); + + void setStyle(MCBarStyle style); + + double getProgress(); + + void setProgress(double progress); + + void addPlayer(MCPlayer player); + + void removePlayer(MCPlayer player); + + void removeAllPlayers(); + + List getPlayers(); + + boolean isVisible(); + + void setVisible(boolean visible); + +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCBrewerInventory.java b/src/main/java/com/laytonsmith/abstraction/MCBrewerInventory.java new file mode 100644 index 0000000000..2b6f19cb4a --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCBrewerInventory.java @@ -0,0 +1,18 @@ +package com.laytonsmith.abstraction; + +import com.laytonsmith.abstraction.blocks.MCBrewingStand; + +public interface MCBrewerInventory extends MCInventory { + MCItemStack getFuel(); + MCItemStack getIngredient(); + MCItemStack getLeftBottle(); + MCItemStack getMiddleBottle(); + MCItemStack getRightBottle(); + void setFuel(MCItemStack stack); + void setIngredient(MCItemStack stack); + void setLeftBottle(MCItemStack stack); + void setMiddleBottle(MCItemStack stack); + void setRightBottle(MCItemStack stack); + @Override + MCBrewingStand getHolder(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCBundleMeta.java b/src/main/java/com/laytonsmith/abstraction/MCBundleMeta.java new file mode 100644 index 0000000000..eec846b13c --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCBundleMeta.java @@ -0,0 +1,8 @@ +package com.laytonsmith.abstraction; + +import java.util.List; + +public interface MCBundleMeta extends MCItemMeta { + List getItems(); + void addItem(MCItemStack item); +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCChunk.java b/src/main/java/com/laytonsmith/abstraction/MCChunk.java index 1c1bb9c727..2bbc2b48ed 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCChunk.java +++ b/src/main/java/com/laytonsmith/abstraction/MCChunk.java @@ -1,13 +1,12 @@ - package com.laytonsmith.abstraction; -/** - * - * @author import - */ public interface MCChunk extends AbstractionObject { - public int getX(); - public int getZ(); - public MCWorld getWorld(); - public MCEntity[] getEntities(); + + int getX(); + + int getZ(); + + MCWorld getWorld(); + + MCEntity[] getEntities(); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCColor.java b/src/main/java/com/laytonsmith/abstraction/MCColor.java index 6433283cdf..71705c084e 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCColor.java +++ b/src/main/java/com/laytonsmith/abstraction/MCColor.java @@ -1,116 +1,113 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ package com.laytonsmith.abstraction; import java.util.Collections; import java.util.HashMap; import java.util.Map; -/** - * - * - */ public interface MCColor { /** - * White, or (0xFF,0xFF,0xFF) in (R,G,B) - */ - public static final MCColor WHITE = StaticLayer.GetConvertor().GetColor(0xFF, 0xFF, 0xFF); - - /** - * Silver, or (0xC0,0xC0,0xC0) in (R,G,B) - */ - public static final MCColor SILVER = StaticLayer.GetConvertor().GetColor(0xC0, 0xC0, 0xC0); - - /** - * Gray, or (0x80,0x80,0x80) in (R,G,B) - */ - public static final MCColor GRAY = StaticLayer.GetConvertor().GetColor(0x80, 0x80, 0x80); - - /** - * Black, or (0x00,0x00,0x00) in (R,G,B) - */ - public static final MCColor BLACK = StaticLayer.GetConvertor().GetColor(0x00, 0x00, 0x00); - - /** - * Red, or (0xFF,0x00,0x00) in (R,G,B) - */ - public static final MCColor RED = StaticLayer.GetConvertor().GetColor(0xFF, 0x00, 0x00); - - /** - * Maroon, or (0x80,0x00,0x00) in (R,G,B) - */ - public static final MCColor MAROON = StaticLayer.GetConvertor().GetColor(0x80, 0x00, 0x00); - - /** - * Yellow, or (0xFF,0xFF,0x00) in (R,G,B) - */ - public static final MCColor YELLOW = StaticLayer.GetConvertor().GetColor(0xFF, 0xFF, 0x00); - - /** - * Olive, or (0x80,0x80,0x00) in (R,G,B) - */ - public static final MCColor OLIVE = StaticLayer.GetConvertor().GetColor(0x80, 0x80, 0x00); - - /** - * Lime, or (0x00,0xFF,0x00) in (R,G,B) - */ - public static final MCColor LIME = StaticLayer.GetConvertor().GetColor(0x00, 0xFF, 0x00); - - /** - * Green, or (0x00,0x80,0x00) in (R,G,B) - */ - public static final MCColor GREEN = StaticLayer.GetConvertor().GetColor(0x00, 0x80, 0x00); - - /** - * Aqua, or (0x00,0xFF,0xFF) in (R,G,B) - */ - public static final MCColor AQUA = StaticLayer.GetConvertor().GetColor(0x00, 0xFF, 0xFF); - - /** - * Teal, or (0x00,0x80,0x80) in (R,G,B) - */ - public static final MCColor TEAL = StaticLayer.GetConvertor().GetColor(0x00, 0x80, 0x80); - - /** - * Blue, or (0x00,0x00,0xFF) in (R,G,B) - */ - public static final MCColor BLUE = StaticLayer.GetConvertor().GetColor(0x00, 0x00, 0xFF); - - /** - * Navy, or (0x00,0x00,0x80) in (R,G,B) - */ - public static final MCColor NAVY = StaticLayer.GetConvertor().GetColor(0x00, 0x00, 0x80); - - /** - * Fuchsia, or (0xFF,0x00,0xFF) in (R,G,B) - */ - public static final MCColor FUCHSIA = StaticLayer.GetConvertor().GetColor(0xFF, 0x00, 0xFF); - - /** - * Purple, or (0x80,0x00,0x80) in (R,G,B) - */ - public static final MCColor PURPLE = StaticLayer.GetConvertor().GetColor(0x80, 0x00, 0x80); - - /** - * Orange, or (0xFF,0xA5,0x00) in (R,G,B) - */ - public static final MCColor ORANGE = StaticLayer.GetConvertor().GetColor(0xFF, 0xA5, 0x00); - - /** - * A mapping of string color name values to the standard MCColor objects. - * Note that the map is immutable, so any attempts to write to it will fail. - */ - public static final Map STANDARD_COLORS = Internal.buildColors(); + * White, or (0xFF,0xFF,0xFF) in (R,G,B) + */ + MCColor WHITE = StaticLayer.GetConvertor().GetColor(0xFF, 0xFF, 0xFF); + + /** + * Silver, or (0xC0,0xC0,0xC0) in (R,G,B) + */ + MCColor SILVER = StaticLayer.GetConvertor().GetColor(0xC0, 0xC0, 0xC0); + + /** + * Gray, or (0x80,0x80,0x80) in (R,G,B) + */ + MCColor GRAY = StaticLayer.GetConvertor().GetColor(0x80, 0x80, 0x80); + + /** + * Black, or (0x00,0x00,0x00) in (R,G,B) + */ + MCColor BLACK = StaticLayer.GetConvertor().GetColor(0x00, 0x00, 0x00); + + /** + * Red, or (0xFF,0x00,0x00) in (R,G,B) + */ + MCColor RED = StaticLayer.GetConvertor().GetColor(0xFF, 0x00, 0x00); + + /** + * Maroon, or (0x80,0x00,0x00) in (R,G,B) + */ + MCColor MAROON = StaticLayer.GetConvertor().GetColor(0x80, 0x00, 0x00); + + /** + * Yellow, or (0xFF,0xFF,0x00) in (R,G,B) + */ + MCColor YELLOW = StaticLayer.GetConvertor().GetColor(0xFF, 0xFF, 0x00); + + /** + * Olive, or (0x80,0x80,0x00) in (R,G,B) + */ + MCColor OLIVE = StaticLayer.GetConvertor().GetColor(0x80, 0x80, 0x00); + + /** + * Lime, or (0x00,0xFF,0x00) in (R,G,B) + */ + MCColor LIME = StaticLayer.GetConvertor().GetColor(0x00, 0xFF, 0x00); + + /** + * Green, or (0x00,0x80,0x00) in (R,G,B) + */ + MCColor GREEN = StaticLayer.GetConvertor().GetColor(0x00, 0x80, 0x00); + + /** + * Aqua, or (0x00,0xFF,0xFF) in (R,G,B) + */ + MCColor AQUA = StaticLayer.GetConvertor().GetColor(0x00, 0xFF, 0xFF); + + /** + * Teal, or (0x00,0x80,0x80) in (R,G,B) + */ + MCColor TEAL = StaticLayer.GetConvertor().GetColor(0x00, 0x80, 0x80); + + /** + * Blue, or (0x00,0x00,0xFF) in (R,G,B) + */ + MCColor BLUE = StaticLayer.GetConvertor().GetColor(0x00, 0x00, 0xFF); + + /** + * Navy, or (0x00,0x00,0x80) in (R,G,B) + */ + MCColor NAVY = StaticLayer.GetConvertor().GetColor(0x00, 0x00, 0x80); + + /** + * Fuchsia, or (0xFF,0x00,0xFF) in (R,G,B) + */ + MCColor FUCHSIA = StaticLayer.GetConvertor().GetColor(0xFF, 0x00, 0xFF); + + /** + * Purple, or (0x80,0x00,0x80) in (R,G,B) + */ + MCColor PURPLE = StaticLayer.GetConvertor().GetColor(0x80, 0x00, 0x80); + + /** + * Orange, or (0xFF,0xA5,0x00) in (R,G,B) + */ + MCColor ORANGE = StaticLayer.GetConvertor().GetColor(0xFF, 0xA5, 0x00); + + /** + * A mapping of string color name values to the standard MCColor objects. Note that the map is immutable, so any + * attempts to write to it will fail. + */ + Map STANDARD_COLORS = Internal.buildColors(); + + int getAlpha(); int getRed(); + int getGreen(); + int getBlue(); /** * Returns a NEW instance of a color, given the specified RGB values. + * * @param red * @param green * @param blue @@ -118,8 +115,20 @@ public interface MCColor { */ MCColor build(int red, int green, int blue); - static class Internal { - private static Map buildColors(){ + /** + * Returns a NEW instance of a color, given the specified ARGB values. + * + * @param alpha + * @param red + * @param green + * @param blue + * @return + */ + MCColor build(int alpha, int red, int green, int blue); + + class Internal { + + private static Map buildColors() { Map map = new HashMap<>(); map.put("AQUA", AQUA); map.put("BLACK", BLACK); diff --git a/src/main/java/com/laytonsmith/abstraction/MCColorableArmorMeta.java b/src/main/java/com/laytonsmith/abstraction/MCColorableArmorMeta.java new file mode 100644 index 0000000000..cdb72fd549 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCColorableArmorMeta.java @@ -0,0 +1,4 @@ +package com.laytonsmith.abstraction; + +public interface MCColorableArmorMeta extends MCLeatherArmorMeta, MCArmorMeta { +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCCommand.java b/src/main/java/com/laytonsmith/abstraction/MCCommand.java index 3a8159adaa..13caa3d422 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCCommand.java +++ b/src/main/java/com/laytonsmith/abstraction/MCCommand.java @@ -4,53 +4,55 @@ public interface MCCommand extends AbstractionObject { - public List getAliases(); - - public String getDescription(); - - public String getLabel(); - - public String getName(); - - public String getPermission(); - - public String getPermissionMessage(); - - public String getUsage(); - - public MCCommand setAliases(List aliases); - - public MCCommand setDescription(String desc); - - public MCCommand setLabel(String name); - - public MCCommand setPermission(String perm); - - public MCCommand setPermissionMessage(String permmsg); - - public MCCommand setUsage(String example); - - public boolean testPermission(MCCommandSender target); - - public boolean testPermissionSilent(MCCommandSender target); - - public boolean register(MCCommandMap map); - - public boolean isRegistered(); - - public boolean unregister(MCCommandMap map); - - public MCPlugin getPlugin(); - - public MCPlugin getExecutor(); - - public MCPlugin getTabCompleter(); - - public void setExecutor(MCPlugin plugin); - - public void setTabCompleter(MCPlugin plugin); - - public List handleTabComplete(MCCommandSender sender, String alias, String[] args); - - public boolean handleCustomCommand(MCCommandSender sender, String label, String[] args); + List getAliases(); + + String getDescription(); + + String getLabel(); + + String getName(); + + String getPermission(); + + String getPermissionMessage(); + + String getUsage(); + + MCCommand setAliases(List aliases); + + MCCommand setDescription(String desc); + + MCCommand setLabel(String name); + + MCCommand setPermission(String perm); + + MCCommand setPermissionMessage(String permmsg); + + MCCommand setUsage(String example); + + boolean testPermission(MCCommandSender target); + + boolean testPermissionSilent(MCCommandSender target); + + boolean register(MCCommandMap map); + + boolean isRegistered(); + + boolean unregister(MCCommandMap map); + + MCPlugin getPlugin(); + + MCPlugin getExecutor(); + + MCPlugin getTabCompleter(); + + void setExecutor(MCPlugin plugin); + + void setTabCompleter(MCPlugin plugin); + + List tabComplete(MCCommandSender sender, String alias, String[] args); + + List handleTabComplete(MCCommandSender sender, String alias, String[] args); + + boolean handleCustomCommand(MCCommandSender sender, String label, String[] args); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCCommandException.java b/src/main/java/com/laytonsmith/abstraction/MCCommandException.java index 7ceeae3432..81e5450def 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCCommandException.java +++ b/src/main/java/com/laytonsmith/abstraction/MCCommandException.java @@ -1,23 +1,19 @@ - package com.laytonsmith.abstraction; -/** - * - * - */ public class MCCommandException extends RuntimeException { - /** - * Creates a new instance of MCCommandException without detail message. - */ - public MCCommandException() { - } + /** + * Creates a new instance of MCCommandException without detail message. + */ + public MCCommandException() { + } - /** - * Constructs an instance of MCCommandException with the specified detail message. - * @param msg the detail message. - */ - public MCCommandException(String msg) { - super(msg); - } + /** + * Constructs an instance of MCCommandException with the specified detail message. + * + * @param msg the detail message. + */ + public MCCommandException(String msg) { + super(msg); + } } diff --git a/src/main/java/com/laytonsmith/abstraction/MCCommandMap.java b/src/main/java/com/laytonsmith/abstraction/MCCommandMap.java index 99d80b2e5f..139ac3398c 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCCommandMap.java +++ b/src/main/java/com/laytonsmith/abstraction/MCCommandMap.java @@ -4,35 +4,37 @@ public interface MCCommandMap extends AbstractionObject { - public void clearCommands(); - - public boolean isCommand(String name); - + void clearCommands(); + + boolean isCommand(String name); + /** - * + * * @param name name of the command * @return a command if found, or null if one isn't */ - public MCCommand getCommand(String name); - - public List getCommands(); - + MCCommand getCommand(String name); + + List getCommands(); + /** - * + * * @param fallback the text added to the start of the command if the chosen name is already taken * @param cmd * @return */ - public boolean register(String fallback, MCCommand cmd); - + boolean register(String fallback, MCCommand cmd); + /** - * + * * @param label * @param fallback the text added to the start of the command if the chosen name is already taken * @param cmd * @return */ - public boolean register(String label, String fallback, MCCommand cmd); - - public boolean unregister(MCCommand cmd); + boolean register(String label, String fallback, MCCommand cmd); + + boolean unregister(MCCommand cmd); + + boolean unregister(String label); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCCommandSender.java b/src/main/java/com/laytonsmith/abstraction/MCCommandSender.java index 34819ccdc9..ce6fd05c0b 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCCommandSender.java +++ b/src/main/java/com/laytonsmith/abstraction/MCCommandSender.java @@ -1,17 +1,22 @@ - package com.laytonsmith.abstraction; -/** - * - * - */ -public interface MCCommandSender extends AbstractionObject{ - public void sendMessage(String string); +import java.util.List; + +public interface MCCommandSender extends AbstractionObject { + + void sendMessage(String string); + + MCServer getServer(); + + String getName(); + + boolean isOp(); + + boolean hasPermission(String perm); - public MCServer getServer(); + boolean isPermissionSet(String perm); - public String getName(); - - public boolean isOp(); + List getGroups(); + boolean inGroup(String groupName); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCCompassMeta.java b/src/main/java/com/laytonsmith/abstraction/MCCompassMeta.java new file mode 100644 index 0000000000..67489cc02e --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCCompassMeta.java @@ -0,0 +1,12 @@ +package com.laytonsmith.abstraction; + +public interface MCCompassMeta extends MCItemMeta { + + MCLocation getTargetLocation(); + + void setTargetLocation(MCLocation location); + + boolean isLodestoneTracked(); + + void setLodestoneTracked(boolean tracked); +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCComplexRecipe.java b/src/main/java/com/laytonsmith/abstraction/MCComplexRecipe.java new file mode 100644 index 0000000000..568f94fc25 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCComplexRecipe.java @@ -0,0 +1,4 @@ +package com.laytonsmith.abstraction; + +public interface MCComplexRecipe extends MCRecipe { +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCConsoleCommandSender.java b/src/main/java/com/laytonsmith/abstraction/MCConsoleCommandSender.java index e8817258db..c05d972925 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCConsoleCommandSender.java +++ b/src/main/java/com/laytonsmith/abstraction/MCConsoleCommandSender.java @@ -1,10 +1,4 @@ - package com.laytonsmith.abstraction; -/** - * - * - */ public interface MCConsoleCommandSender extends MCCommandSender { - } diff --git a/src/main/java/com/laytonsmith/abstraction/MCCookingRecipe.java b/src/main/java/com/laytonsmith/abstraction/MCCookingRecipe.java new file mode 100644 index 0000000000..f6e5abb016 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCCookingRecipe.java @@ -0,0 +1,14 @@ +package com.laytonsmith.abstraction; + +import com.laytonsmith.abstraction.blocks.MCMaterial; + +public interface MCCookingRecipe extends MCRecipe { + MCRecipeChoice getInput(); + void setInput(MCItemStack input); + void setInput(MCMaterial mat); + void setInput(MCRecipeChoice choice); + int getCookingTime(); + void setCookingTime(int ticks); + float getExperience(); + void setExperience(float exp); +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCCooldownComponent.java b/src/main/java/com/laytonsmith/abstraction/MCCooldownComponent.java new file mode 100644 index 0000000000..f781727e77 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCCooldownComponent.java @@ -0,0 +1,8 @@ +package com.laytonsmith.abstraction; + +public interface MCCooldownComponent extends AbstractionObject { + float getSeconds(); + void setSeconds(float seconds); + String getCooldownGroup(); + void setCooldownGroup(String cooldownGroup); +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCCraftingInventory.java b/src/main/java/com/laytonsmith/abstraction/MCCraftingInventory.java index 686b1ed1fe..4236a5fa32 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCCraftingInventory.java +++ b/src/main/java/com/laytonsmith/abstraction/MCCraftingInventory.java @@ -2,9 +2,13 @@ public interface MCCraftingInventory extends MCInventory { - public MCItemStack[] getMatrix(); - public MCRecipe getRecipe(); - public MCItemStack getResult(); - public void setMatrix(MCItemStack[] contents); - public void setResult(MCItemStack result); + MCItemStack[] getMatrix(); + + MCRecipe getRecipe(); + + MCItemStack getResult(); + + void setMatrix(MCItemStack[] contents); + + void setResult(MCItemStack result); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCCreatureSpawner.java b/src/main/java/com/laytonsmith/abstraction/MCCreatureSpawner.java index d58eb84c30..1d6d48a8c9 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCCreatureSpawner.java +++ b/src/main/java/com/laytonsmith/abstraction/MCCreatureSpawner.java @@ -1,15 +1,23 @@ - package com.laytonsmith.abstraction; import com.laytonsmith.abstraction.blocks.MCBlockState; import com.laytonsmith.abstraction.enums.MCEntityType; -/** - * - * - */ public interface MCCreatureSpawner extends MCBlockState { - MCEntityType getSpawnedType(); void setSpawnedType(MCEntityType type); + int getDelay(); + void setDelay(int delay); + int getMinDelay(); + void setMinDelay(int delay); + int getMaxDelay(); + void setMaxDelay(int delay); + int getSpawnCount(); + void setSpawnCount(int count); + int getMaxNearbyEntities(); + void setMaxNearbyEntities(int max); + int getPlayerRange(); + void setPlayerRange(int range); + int getSpawnRange(); + void setSpawnRange(int range); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCCrossbowMeta.java b/src/main/java/com/laytonsmith/abstraction/MCCrossbowMeta.java new file mode 100644 index 0000000000..f3598c4632 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCCrossbowMeta.java @@ -0,0 +1,9 @@ +package com.laytonsmith.abstraction; + +import java.util.List; + +public interface MCCrossbowMeta extends MCItemMeta { + boolean hasChargedProjectiles(); + List getChargedProjectiles(); + void setChargedProjectiles(List projectiles); +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCDoubleChest.java b/src/main/java/com/laytonsmith/abstraction/MCDoubleChest.java new file mode 100644 index 0000000000..ef9b59c0c3 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCDoubleChest.java @@ -0,0 +1,5 @@ +package com.laytonsmith.abstraction; + +public interface MCDoubleChest extends MCInventoryHolder { + MCLocation getLocation(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCEgg.java b/src/main/java/com/laytonsmith/abstraction/MCEgg.java deleted file mode 100644 index ba2a4c088b..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/MCEgg.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.laytonsmith.abstraction; - -public interface MCEgg extends MCProjectile { - -} diff --git a/src/main/java/com/laytonsmith/abstraction/MCEnchantment.java b/src/main/java/com/laytonsmith/abstraction/MCEnchantment.java deleted file mode 100644 index f07091520f..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/MCEnchantment.java +++ /dev/null @@ -1,12 +0,0 @@ - -package com.laytonsmith.abstraction; - -/** - * - * - */ -public interface MCEnchantment extends AbstractionObject{ - public boolean canEnchantItem(MCItemStack is); - public int getMaxLevel(); - public String getName(); -} diff --git a/src/main/java/com/laytonsmith/abstraction/MCEnchantmentOffer.java b/src/main/java/com/laytonsmith/abstraction/MCEnchantmentOffer.java new file mode 100644 index 0000000000..23a040e705 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCEnchantmentOffer.java @@ -0,0 +1,17 @@ +package com.laytonsmith.abstraction; + +import com.laytonsmith.abstraction.enums.MCEnchantment; + +public interface MCEnchantmentOffer { + MCEnchantment getEnchantment(); + + void setEnchantment(MCEnchantment enchant); + + int getEnchantmentLevel(); + + void setEnchantmentLevel(int level); + + int getCost(); + + void setCost(int cost); +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCEnchantmentStorageMeta.java b/src/main/java/com/laytonsmith/abstraction/MCEnchantmentStorageMeta.java index 9fef9ba147..16c8359e04 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCEnchantmentStorageMeta.java +++ b/src/main/java/com/laytonsmith/abstraction/MCEnchantmentStorageMeta.java @@ -1,18 +1,20 @@ package com.laytonsmith.abstraction; +import com.laytonsmith.abstraction.enums.MCEnchantment; + import java.util.Map; -/** - * - * @author jb_aero - */ public interface MCEnchantmentStorageMeta extends MCItemMeta { - public boolean addStoredEnchant(MCEnchantment ench, int level, boolean ignoreRestriction); - public int getStoredEnchantLevel(MCEnchantment ench); - public Map getStoredEnchants(); - public boolean hasStoredEnchant(MCEnchantment ench); - public boolean hasStoredEnchants(); - public boolean removeStoredEnchant(MCEnchantment ench); - + boolean addStoredEnchant(MCEnchantment ench, int level, boolean ignoreRestriction); + + int getStoredEnchantLevel(MCEnchantment ench); + + Map getStoredEnchants(); + + boolean hasStoredEnchant(MCEnchantment ench); + + boolean hasStoredEnchants(); + + boolean removeStoredEnchant(MCEnchantment ench); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCEnderCrystal.java b/src/main/java/com/laytonsmith/abstraction/MCEnderCrystal.java deleted file mode 100644 index 17b6e4fe19..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/MCEnderCrystal.java +++ /dev/null @@ -1,10 +0,0 @@ - -package com.laytonsmith.abstraction; - -/** - * - * - */ -public interface MCEnderCrystal extends MCEntity { - -} diff --git a/src/main/java/com/laytonsmith/abstraction/MCEntity.java b/src/main/java/com/laytonsmith/abstraction/MCEntity.java index b202523362..38ccef0c21 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCEntity.java +++ b/src/main/java/com/laytonsmith/abstraction/MCEntity.java @@ -1,91 +1,131 @@ package com.laytonsmith.abstraction; -import com.laytonsmith.abstraction.enums.MCDamageCause; +import com.laytonsmith.PureUtilities.Vector3D; import com.laytonsmith.abstraction.enums.MCEntityEffect; import com.laytonsmith.abstraction.enums.MCEntityType; +import com.laytonsmith.abstraction.enums.MCPose; import com.laytonsmith.abstraction.enums.MCTeleportCause; import com.laytonsmith.abstraction.events.MCEntityDamageEvent; import java.util.List; +import java.util.Set; import java.util.UUID; -/** - * - * - */ public interface MCEntity extends MCMetadatable { - public boolean eject(); + boolean eject(); - public void fireEntityDamageEvent(MCDamageCause dc); + float getFallDistance(); - public int getEntityId(); + int getFireTicks(); - public float getFallDistance(); + MCEntityDamageEvent getLastDamageCause(); - public int getFireTicks(); + MCLocation getLocation(); - public MCEntityDamageEvent getLastDamageCause(); + int getMaxFireTicks(); - public MCLocation getLocation(); + List getNearbyEntities(double x, double y, double z); - /** - * Unlike {@see MCEntity#getLocation}, this will work when not run on the server - * thread, but this does mean that the data recieved may be slightly outdated. - * @return - */ - public MCLocation asyncGetLocation(); + List getPassengers(); - public int getMaxFireTicks(); + MCServer getServer(); - public List getNearbyEntities(double x, double y, double z); + int getTicksLived(); - public MCEntity getPassenger(); + MCEntityType getType(); - public MCServer getServer(); + UUID getUniqueId(); - public int getTicksLived(); + MCEntity getVehicle(); - public MCEntityType getType(); + Vector3D getVelocity(); - public UUID getUniqueId(); + void setVelocity(Vector3D v); - public MCEntity getVehicle(); + MCWorld getWorld(); - public Velocity getVelocity(); - - public void setVelocity(Velocity v); + boolean isDead(); - public MCWorld getWorld(); + boolean isEmpty(); - public boolean isDead(); + boolean isInsideVehicle(); - public boolean isEmpty(); + boolean isOnGround(); - public boolean isInsideVehicle(); - - public boolean isOnGround(); + boolean leaveVehicle(); - public boolean leaveVehicle(); + void playEffect(MCEntityEffect type); - public void playEffect(MCEntityEffect type); + void remove(); - public void remove(); + boolean savesOnUnload(); - public void setFallDistance(float distance); + void setSavesOnUnload(boolean remove); - public void setFireTicks(int ticks); + void setFallDistance(float distance); - public void setLastDamageCause(MCEntityDamageEvent event); + void setFireTicks(int ticks); - public boolean setPassenger(MCEntity passenger); + boolean setPassenger(MCEntity passenger); - public void setTicksLived(int value); + void setTicksLived(int value); - public boolean teleport(MCEntity destination); + boolean teleport(MCEntity destination); - public boolean teleport(MCEntity destination, MCTeleportCause cause); + boolean teleport(MCEntity destination, MCTeleportCause cause); - public boolean teleport(MCLocation location); + boolean teleport(MCLocation location); + + boolean teleport(MCLocation location, MCTeleportCause cause); + + void setCustomName(String name); + + String getCustomName(); + + void setCustomNameVisible(boolean visible); + + boolean isCustomNameVisible(); + + boolean isGlowing(); + + void setGlowing(Boolean glow); + + boolean hasGravity(); + + void setHasGravity(boolean gravity); + + boolean isSilent(); + + void setSilent(boolean silent); + + boolean isInvulnerable(); + + void setInvulnerable(boolean invulnerable); + + Set getScoreboardTags(); + + boolean hasScoreboardTag(String tag); + + boolean addScoreboardTag(String tag); + + boolean removeScoreboardTag(String tag); + + int getFreezingTicks(); + + void setFreezingTicks(int ticks); + + int getEntityId(); + + boolean isInWater(); + + void setRotation(float yaw, float pitch); + + boolean isVisibleByDefault(); + + void setVisibleByDefault(boolean visible); + + MCPose getPose(); + + void setPose(MCPose pose, boolean fixed); - public boolean teleport(MCLocation location, MCTeleportCause cause); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCEntityEquipment.java b/src/main/java/com/laytonsmith/abstraction/MCEntityEquipment.java index fc0e6ae542..8a02bb2b0b 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCEntityEquipment.java +++ b/src/main/java/com/laytonsmith/abstraction/MCEntityEquipment.java @@ -3,44 +3,65 @@ import com.laytonsmith.abstraction.enums.MCEquipmentSlot; import java.util.Map; -/** - * - * @author jb_aero - */ public interface MCEntityEquipment { - public void clearEquipment(); - public int getSize(); - public MCEntity getHolder(); - - public Map getAllEquipment(); - public void setAllEquipment(Map stackmap); - - public Map getAllDropChances(); - public void setAllDropChances(Map slots); - - public MCItemStack getWeapon(); - public MCItemStack getHelmet(); - public MCItemStack getChestplate(); - public MCItemStack getLeggings(); - public MCItemStack getBoots(); - - public void setWeapon(MCItemStack stack); - public void setHelmet(MCItemStack stack); - public void setChestplate(MCItemStack stack); - public void setLeggings(MCItemStack stack); - public void setBoots(MCItemStack stack); - - public float getWeaponDropChance(); - public float getHelmetDropChance(); - public float getChestplateDropChance(); - public float getLeggingsDropChance(); - public float getBootsDropChance(); - - public void setWeaponDropChance(float chance); - public void setHelmetDropChance(float chance); - public void setChestplateDropChance(float chance); - public void setLeggingsDropChance(float chance); - public void setBootsDropChance(float chance); - + void clearEquipment(); + + MCEntity getHolder(); + + Map getAllEquipment(); + + void setAllEquipment(Map stackmap); + + Map getAllDropChances(); + + void setAllDropChances(Map slots); + + MCItemStack getWeapon(); + + MCItemStack getItemInOffHand(); + + MCItemStack getHelmet(); + + MCItemStack getChestplate(); + + MCItemStack getLeggings(); + + MCItemStack getBoots(); + + void setWeapon(MCItemStack stack); + + void setItemInOffHand(MCItemStack stack); + + void setHelmet(MCItemStack stack); + + void setChestplate(MCItemStack stack); + + void setLeggings(MCItemStack stack); + + void setBoots(MCItemStack stack); + + float getWeaponDropChance(); + + float getOffHandDropChance(); + + float getHelmetDropChance(); + + float getChestplateDropChance(); + + float getLeggingsDropChance(); + + float getBootsDropChance(); + + void setWeaponDropChance(float chance); + + void setOffHandDropChance(float chance); + + void setHelmetDropChance(float chance); + + void setChestplateDropChance(float chance); + + void setLeggingsDropChance(float chance); + + void setBootsDropChance(float chance); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCEquippableComponent.java b/src/main/java/com/laytonsmith/abstraction/MCEquippableComponent.java new file mode 100644 index 0000000000..56847624c0 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCEquippableComponent.java @@ -0,0 +1,27 @@ +package com.laytonsmith.abstraction; + +import com.laytonsmith.abstraction.enums.MCEntityType; +import com.laytonsmith.abstraction.enums.MCEquipmentSlot; + +import java.util.Collection; + +public interface MCEquippableComponent extends AbstractionObject { + MCEquipmentSlot getSlot(); + void setSlot(MCEquipmentSlot slot); + Collection getAllowedEntities(); + void setAllowedEntities(Collection types); + String getCameraOverlay(); + void setCameraOverlay(String overlay); + String getAssetId(); + void setAssetId(String assetId); + String getEquipSound(); + void setEquipSound(String sound); + boolean isDispensable(); + void setDispensable(boolean dispensable); + boolean isEquipOnInteract(); + void setEquipOnInteract(boolean equipOnInteract); + boolean isSwappable(); + void setSwappable(boolean swappable); + boolean isDamageOnHurt(); + void setDamageOnHurt(boolean damagedOnHurt); +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCExperienceOrb.java b/src/main/java/com/laytonsmith/abstraction/MCExperienceOrb.java deleted file mode 100644 index b41d342172..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/MCExperienceOrb.java +++ /dev/null @@ -1,12 +0,0 @@ - -package com.laytonsmith.abstraction; - -/** - * - * @author Jim - */ -public interface MCExperienceOrb extends MCEntity{ - - public int getExperience(); - public void setExperience(int amount); -} diff --git a/src/main/java/com/laytonsmith/abstraction/MCFireball.java b/src/main/java/com/laytonsmith/abstraction/MCFireball.java deleted file mode 100644 index 6a9ef200ea..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/MCFireball.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.laytonsmith.abstraction; - -public interface MCFireball extends MCProjectile { - public Velocity getDirection(); - public void setDirection(Velocity vector); -} diff --git a/src/main/java/com/laytonsmith/abstraction/MCFireworkBuilder.java b/src/main/java/com/laytonsmith/abstraction/MCFireworkBuilder.java index 142a48b340..bc64e85e21 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCFireworkBuilder.java +++ b/src/main/java/com/laytonsmith/abstraction/MCFireworkBuilder.java @@ -1,64 +1,51 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ package com.laytonsmith.abstraction; import com.laytonsmith.abstraction.enums.MCFireworkType; -/** - * - * - */ public interface MCFireworkBuilder { /** * Sets whether or not this firework has a flicker + * * @param flicker * @return */ MCFireworkBuilder setFlicker(boolean flicker); + /** * Sets whether or not this firework has a trail */ MCFireworkBuilder setTrail(boolean trail); + /** * Adds a primary color + * * @param color * @return */ MCFireworkBuilder addColor(MCColor color); + /** * Adds a secondary color + * * @param color * @return */ MCFireworkBuilder addFadeColor(MCColor color); - /** - * Sets the launch strength of the firework - * @param i - * @return - */ - MCFireworkBuilder setStrength(int i); /** * Sets the firework type of the firework. + * * @param type * @return */ MCFireworkBuilder setType(MCFireworkType type); - /** - * Launches the firework from the specified location, upward. - * @param l - * @return entityID of the firework - */ - int launch(MCLocation l); /** - * Given the entered builder data, create a firework meta with these attributes - * for a given MCFireworkMeta - * @return + * Returns the firework effect object created by this builder + * + * @return MCFireworkEffect */ - void createFireworkMeta(MCFireworkMeta meta); + MCFireworkEffect build(); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCFireworkEffect.java b/src/main/java/com/laytonsmith/abstraction/MCFireworkEffect.java new file mode 100644 index 0000000000..cf93bbfbde --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCFireworkEffect.java @@ -0,0 +1,19 @@ +package com.laytonsmith.abstraction; + +import com.laytonsmith.abstraction.enums.MCFireworkType; + +import java.util.List; + +public interface MCFireworkEffect extends AbstractionObject { + + boolean hasFlicker(); + + boolean hasTrail(); + + List getColors(); + + List getFadeColors(); + + MCFireworkType getType(); + +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCFireworkEffectMeta.java b/src/main/java/com/laytonsmith/abstraction/MCFireworkEffectMeta.java new file mode 100644 index 0000000000..b817368424 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCFireworkEffectMeta.java @@ -0,0 +1,11 @@ +package com.laytonsmith.abstraction; + +public interface MCFireworkEffectMeta extends MCItemMeta { + + boolean hasEffect(); + + MCFireworkEffect getEffect(); + + void setEffect(MCFireworkEffect effect); + +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCFireworkMeta.java b/src/main/java/com/laytonsmith/abstraction/MCFireworkMeta.java index c9b55496fa..afa6c93088 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCFireworkMeta.java +++ b/src/main/java/com/laytonsmith/abstraction/MCFireworkMeta.java @@ -1,21 +1,17 @@ package com.laytonsmith.abstraction; -import com.laytonsmith.abstraction.enums.MCFireworkType; import java.util.List; public interface MCFireworkMeta extends MCItemMeta { int getStrength(); - void setStrength(int strength); - - boolean getFlicker(); - boolean getTrail(); + void setStrength(int strength); - List getColors(); + List getEffects(); - List getFadeColors(); + void addEffect(MCFireworkEffect effect); - MCFireworkType getType(); + void clearEffects(); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCFoodComponent.java b/src/main/java/com/laytonsmith/abstraction/MCFoodComponent.java new file mode 100644 index 0000000000..ed7409e98b --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCFoodComponent.java @@ -0,0 +1,10 @@ +package com.laytonsmith.abstraction; + +public interface MCFoodComponent extends AbstractionObject { + int getNutrition(); + void setNutrition(int nutrition); + float getSaturation(); + void setSaturation(float saturation); + boolean getCanAlwaysEat(); + void setCanAlwaysEat(boolean canAlwaysEat); +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCFurnaceInventory.java b/src/main/java/com/laytonsmith/abstraction/MCFurnaceInventory.java new file mode 100644 index 0000000000..dbaa89493f --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCFurnaceInventory.java @@ -0,0 +1,14 @@ +package com.laytonsmith.abstraction; + +import com.laytonsmith.abstraction.blocks.MCFurnace; + +public interface MCFurnaceInventory extends MCInventory { + MCItemStack getResult(); + MCItemStack getFuel(); + MCItemStack getSmelting(); + void setFuel(MCItemStack stack); + void setResult(MCItemStack stack); + void setSmelting(MCItemStack stack); + @Override + MCFurnace getHolder(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCFurnaceRecipe.java b/src/main/java/com/laytonsmith/abstraction/MCFurnaceRecipe.java deleted file mode 100644 index 6e9b1f2df6..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/MCFurnaceRecipe.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.laytonsmith.abstraction; - -public interface MCFurnaceRecipe extends MCRecipe { - - public MCItemStack getInput(); - - public MCFurnaceRecipe setInput(MCItemStack input); - - public MCFurnaceRecipe setInput(int type, int data); -} diff --git a/src/main/java/com/laytonsmith/abstraction/MCGrindstoneInventory.java b/src/main/java/com/laytonsmith/abstraction/MCGrindstoneInventory.java new file mode 100644 index 0000000000..068cf44274 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCGrindstoneInventory.java @@ -0,0 +1,11 @@ +package com.laytonsmith.abstraction; + +public interface MCGrindstoneInventory extends MCInventory { + MCItemStack getUpperItem(); + MCItemStack getLowerItem(); + MCItemStack getResult(); + + void setUpperItem(MCItemStack i); + void setLowerItem(MCItemStack i); + void setResult(MCItemStack i); +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCHanging.java b/src/main/java/com/laytonsmith/abstraction/MCHanging.java deleted file mode 100644 index 683f468ba4..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/MCHanging.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.laytonsmith.abstraction; - -import com.laytonsmith.abstraction.blocks.MCBlockFace; - -/** - * - * @author jb_aero - */ -public interface MCHanging extends MCEntity { - - public MCBlockFace getFacing(); - public void setFacingDirection(MCBlockFace direction); - public boolean setFacingDirection(MCBlockFace direction, boolean force); - -} \ No newline at end of file diff --git a/src/main/java/com/laytonsmith/abstraction/MCHumanEntity.java b/src/main/java/com/laytonsmith/abstraction/MCHumanEntity.java index 7ac27f96c1..2f76b8039c 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCHumanEntity.java +++ b/src/main/java/com/laytonsmith/abstraction/MCHumanEntity.java @@ -1,30 +1,51 @@ package com.laytonsmith.abstraction; +import com.laytonsmith.abstraction.blocks.MCMaterial; import com.laytonsmith.abstraction.enums.MCGameMode; -/** - * - * - */ -public interface MCHumanEntity extends MCInventoryHolder, MCLivingEntity, MCAnimalTamer{ - public void closeInventory(); - public MCGameMode getGameMode(); - public MCItemStack getItemInHand(); - public MCItemStack getItemOnCursor(); +public interface MCHumanEntity extends MCInventoryHolder, MCLivingEntity, MCAnimalTamer { + + void closeInventory(); + + MCGameMode getGameMode(); + + MCItemStack getItemInHand(); + + MCItemStack getItemOnCursor(); + @Override - public String getName(); - public int getSleepTicks(); - public boolean isBlocking(); - public boolean isSleeping(); - public MCInventoryView openEnchanting(MCLocation location, boolean force); - public MCInventoryView openInventory(MCInventory inventory); - public MCInventoryView getOpenInventory(); - public MCInventory getEnderChest(); - public MCInventoryView openWorkbench(MCLocation loc, boolean force); - //public MCInventoryView openWorkbench(MCLocation location, boolean force); - void setGameMode(MCGameMode mode); - void setItemInHand(MCItemStack item); - void setItemOnCursor(MCItemStack item); - - //public boolean setWindowProperty(MCInventoryView.Property prop, int value); + String getName(); + + int getSleepTicks(); + + boolean isBlocking(); + + MCInventoryView openEnchanting(MCLocation location, boolean force); + + MCInventoryView openInventory(MCInventory inventory); + + MCInventoryView getOpenInventory(); + + MCInventory getEnderChest(); + + MCInventoryView openWorkbench(MCLocation loc, boolean force); + + MCInventoryView openMerchant(MCMerchant merchant, boolean force); + + void setGameMode(MCGameMode mode); + + void setItemInHand(MCItemStack item); + + void setItemOnCursor(MCItemStack item); + + int getCooldown(MCMaterial material); + + void setCooldown(MCMaterial material, int ticks); + //boolean setWindowProperty(MCInventoryView.Property prop, int value); + + float getAttackCooldown(); + + boolean discoverRecipe(MCNamespacedKey recipe); + + boolean hasDiscoveredRecipe(MCNamespacedKey recipe); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCInventory.java b/src/main/java/com/laytonsmith/abstraction/MCInventory.java index 6c95bcfa82..461d68ecf2 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCInventory.java +++ b/src/main/java/com/laytonsmith/abstraction/MCInventory.java @@ -4,19 +4,27 @@ import java.util.List; import java.util.Map; -/** - * - * - */ public interface MCInventory extends AbstractionObject { - public Map addItem(MCItemStack stack); - public MCInventoryType getType(); - public int getSize(); - public MCItemStack getItem(int index); - public void setItem(int index, MCItemStack stack); - public List getViewers(); - public void clear(); - public void clear(int index); - public MCInventoryHolder getHolder(); - public String getTitle(); + + Map addItem(MCItemStack stack); + + MCInventoryType getType(); + + int getSize(); + + MCItemStack getItem(int index); + + void setItem(int index, MCItemStack stack); + + List getViewers(); + + void updateViewers(); + + void clear(); + + void clear(int index); + + MCInventoryHolder getHolder(); + + String getTitle(); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCInventoryHolder.java b/src/main/java/com/laytonsmith/abstraction/MCInventoryHolder.java index ede2c7595d..28a54a27ee 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCInventoryHolder.java +++ b/src/main/java/com/laytonsmith/abstraction/MCInventoryHolder.java @@ -1,9 +1,6 @@ package com.laytonsmith.abstraction; -/** - * - * @author Jason Unger - */ public interface MCInventoryHolder extends AbstractionObject { - public MCInventory getInventory(); + + MCInventory getInventory(); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCInventoryView.java b/src/main/java/com/laytonsmith/abstraction/MCInventoryView.java index ade35ae050..23e73f45b7 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCInventoryView.java +++ b/src/main/java/com/laytonsmith/abstraction/MCInventoryView.java @@ -2,31 +2,27 @@ import com.laytonsmith.abstraction.enums.MCInventoryType; -/** - * - * - */ public interface MCInventoryView { - public MCInventory getBottomInventory(); + MCInventory getBottomInventory(); - public MCInventory getTopInventory(); + MCInventory getTopInventory(); - public void close(); + void close(); - public int countSlots(); + int countSlots(); - public int convertSlot(int rawSlot); + int convertSlot(int rawSlot); - public MCItemStack getItem(int slot); + MCItemStack getItem(int slot); - public MCHumanEntity getPlayer(); + MCHumanEntity getPlayer(); - public String getTitle(); - - public MCInventoryType getType(); + String getTitle(); - public void setCursor(MCItemStack item); + MCInventoryType getType(); - public void setItem(int slot, MCItemStack item); + void setCursor(MCItemStack item); + + void setItem(int slot, MCItemStack item); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCItem.java b/src/main/java/com/laytonsmith/abstraction/MCItem.java deleted file mode 100644 index 19207fa05c..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/MCItem.java +++ /dev/null @@ -1,14 +0,0 @@ - -package com.laytonsmith.abstraction; - -/** - * - * - */ -public interface MCItem extends MCEntity { - - public MCItemStack getItemStack(); - public int getPickupDelay(); - public void setItemStack(MCItemStack stack); - public void setPickupDelay(int delay); -} diff --git a/src/main/java/com/laytonsmith/abstraction/MCItemFactory.java b/src/main/java/com/laytonsmith/abstraction/MCItemFactory.java index 34fb376d30..0274776932 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCItemFactory.java +++ b/src/main/java/com/laytonsmith/abstraction/MCItemFactory.java @@ -2,18 +2,19 @@ import com.laytonsmith.abstraction.blocks.MCMaterial; -/** - * - * @author jb_aero - */ public interface MCItemFactory { - public MCItemMeta asMetaFor(MCItemMeta meta, MCItemStack stack); - public MCItemMeta asMetaFor(MCItemMeta meta, MCMaterial material); - public boolean equals(MCItemMeta meta1, MCItemMeta meta2); - public MCColor getDefaultLeatherColor(); - public MCItemMeta getItemMeta(MCMaterial material); - public boolean isApplicable(MCItemMeta meta, MCItemStack stack); - public boolean isApplicable(MCItemMeta meta, MCMaterial material); - + MCItemMeta asMetaFor(MCItemMeta meta, MCItemStack stack); + + MCItemMeta asMetaFor(MCItemMeta meta, MCMaterial material); + + boolean equals(MCItemMeta meta1, MCItemMeta meta2); + + MCColor getDefaultLeatherColor(); + + MCItemMeta getItemMeta(MCMaterial material); + + boolean isApplicable(MCItemMeta meta, MCItemStack stack); + + boolean isApplicable(MCItemMeta meta, MCMaterial material); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCItemMeta.java b/src/main/java/com/laytonsmith/abstraction/MCItemMeta.java index db03f1ba19..1ef9c52c6a 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCItemMeta.java +++ b/src/main/java/com/laytonsmith/abstraction/MCItemMeta.java @@ -1,94 +1,229 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ package com.laytonsmith.abstraction; +import com.laytonsmith.abstraction.blocks.MCBlockData; +import com.laytonsmith.abstraction.blocks.MCMaterial; +import com.laytonsmith.abstraction.enums.MCEnchantment; +import com.laytonsmith.abstraction.enums.MCItemFlag; +import com.laytonsmith.abstraction.enums.MCItemRarity; + import java.util.List; import java.util.Map; +import java.util.Set; -/** - * - * - */ public interface MCItemMeta extends AbstractionObject { - /** - * Checks for existence of a display name - * - * @return true if this has a display name - */ - boolean hasDisplayName(); - - /** - * Gets the display name that is set - * - * @return the display name that is set - */ - String getDisplayName(); - - /** - * Sets the display name - * - * @param name the name to set - */ - void setDisplayName(String name); - - /** - * Checks for existence of lore - * - * @return true if this has lore - */ - boolean hasLore(); - - /** - * Gets the lore that is set - * - * @return a list of lore that is set - */ - List getLore(); - - /** - * Sets the lore for this item - * - * @param lore the lore that will be set - */ - void setLore(List lore); - - /** - * Checks if this item has any enchantments - * - * @return true if there are enchantments - */ - boolean hasEnchants(); - - /** - * Gets the enchantments on this items - * - * @return a map of MCEnchantment keys and enchantLevel values - */ - Map getEnchants(); - - /** - * Adds a given enchantment to this meta - * - * @param ench The type of enchantment to add - * @param level The level of enchantment - * @param ignoreLevelRestriction Should adding an outrageous level be allowed? - * @return whether the enchantment was added successfully - */ - boolean addEnchant(MCEnchantment ench, int level, boolean ignoreLevelRestriction); - - /** - * Removes a given enchantment from this meta - * - * @param ench The type of enchantment to remove - * @return whether the enchantment was removed successfully - */ - boolean removeEnchant(MCEnchantment ench); - - boolean hasRepairCost(); - - int getRepairCost(); - - void setRepairCost(int cost); + + /** + * Checks for existence of a display name + * + * @return true if this has a display name + */ + boolean hasDisplayName(); + + /** + * Gets the display name that is set + * + * @return the display name that is set + */ + String getDisplayName(); + + /** + * Sets the display name + * + * @param name the name to set + */ + void setDisplayName(String name); + + /** + * Checks for existence of lore + * + * @return true if this has lore + */ + boolean hasLore(); + + /** + * Gets the lore that is set + * + * @return a list of lore that is set + */ + List getLore(); + + /** + * Sets the lore for this item + * + * @param lore the lore that will be set + */ + void setLore(List lore); + + /** + * Checks if this item has any enchantments + * + * @return true if there are enchantments + */ + boolean hasEnchants(); + + /** + * Gets the enchantments on this items + * + * @return a map of MCEnchantment keys and enchantLevel values + */ + Map getEnchants(); + + /** + * Adds a given enchantment to this meta + * + * @param ench The type of enchantment to add + * @param level The level of enchantment + * @param ignoreLevelRestriction Should adding an outrageous level be + * allowed? + * @return whether the enchantment was added successfully + */ + boolean addEnchant(MCEnchantment ench, int level, boolean ignoreLevelRestriction); + + /** + * Set itemflags which should be ignored when rendering a MCItemStack in the + * Client. + * + * @param flags The flags to ignore. + */ + void addItemFlags(MCItemFlag... flags); + + /** + * Get current set itemFlags. + * + * @return A set of all itemFlags set + */ + Set getItemFlags(); + + /** + * Removes a given enchantment from this meta + * + * @param ench The type of enchantment to remove + * @return whether the enchantment was removed successfully + */ + boolean removeEnchant(MCEnchantment ench); + + boolean hasRepairCost(); + + int getRepairCost(); + + void setRepairCost(int cost); + + boolean isUnbreakable(); + + void setUnbreakable(boolean unbreakable); + + boolean hasDamage(); + + int getDamage(); + + void setDamage(int damage); + + boolean hasMaxDamage(); + + int getMaxDamage(); + + void setMaxDamage(Integer damage); + + MCBlockData getBlockData(MCMaterial material); + + Map getExistingBlockData(); + + boolean hasBlockData(); + + void setBlockData(MCBlockData blockData); + + boolean hasCustomModelData(); + + int getCustomModelData(); + + void setCustomModelData(int id); + + List getAttributeModifiers(); + + void setAttributeModifiers(List modifiers); + + boolean hasCustomTags(); + + MCTagContainer getCustomTags(); + + boolean hasItemName(); + + String getItemName(); + + void setItemName(String name); + + boolean isHideTooltip(); + + void setHideTooltip(boolean hide); + + boolean hasEnchantmentGlintOverride(); + + boolean getEnchantmentGlintOverride(); + + void setEnchantmentGlintOverride(boolean glint); + + boolean hasMaxStackSize(); + + int getMaxStackSize(); + + void setMaxStackSize(Integer size); + + boolean hasRarity(); + + MCItemRarity getRarity(); + + void setRarity(MCItemRarity rarity); + + boolean hasEnchantable(); + + int getEnchantable(); + + void setEnchantable(Integer enchantability); + + boolean hasJukeboxPlayable(); + + String getJukeboxPlayable(); + + void setJukeboxPlayable(String playable); + + boolean isGlider(); + + void setGlider(boolean glider); + + boolean hasUseRemainder(); + + MCItemStack getUseRemainder(); + + void setUseRemainder(MCItemStack remainder); + + boolean hasFood(); + + MCFoodComponent getFood(); + + void setFood(MCFoodComponent component); + + boolean hasItemModel(); + + String getItemModel(); + + void setItemModel(String key); + + boolean hasTooltipStyle(); + + String getTooltipStyle(); + + void setTooltipStyle(String key); + + boolean hasUseCooldown(); + + MCCooldownComponent getUseCooldown(); + + void setUseCooldown(MCCooldownComponent component); + + boolean hasEquippable(); + + MCEquippableComponent getEquippable(); + + void setEquippable(MCEquippableComponent component); + } diff --git a/src/main/java/com/laytonsmith/abstraction/MCItemStack.java b/src/main/java/com/laytonsmith/abstraction/MCItemStack.java index bf964c9a34..68b4c0014a 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCItemStack.java +++ b/src/main/java/com/laytonsmith/abstraction/MCItemStack.java @@ -1,32 +1,61 @@ - package com.laytonsmith.abstraction; import com.laytonsmith.abstraction.blocks.MCMaterial; +import com.laytonsmith.abstraction.enums.MCEnchantment; + import java.util.Map; -/** - * - * - */ -public interface MCItemStack extends AbstractionObject{ - public MCMaterialData getData(); - public short getDurability(); - public int getTypeId(); - public void setDurability(short data); - public void addEnchantment(MCEnchantment e, int level); - public void addUnsafeEnchantment(MCEnchantment e, int level); - public Map getEnchantments(); - public void removeEnchantment(MCEnchantment e); - public MCMaterial getType(); - public void setTypeId(int type); - - public int maxStackSize(); - - public int getAmount(); - - public void setData(int data); - - public boolean hasItemMeta(); - public MCItemMeta getItemMeta(); - public void setItemMeta(MCItemMeta im); +public interface MCItemStack extends AbstractionObject { + + /** + * Returns the item type in this stack. May return air if empty. + * + * @return the item type + */ + MCMaterial getType(); + + /** + * Returns the default maximum stack size for the item type in this stack, or if the item has + * a max_stack_size item data component, that component's value will be returned instead. + * Use {@link MCMaterial#getMaxStackSize()} to always get the default max stack size for this item type. + * + * @return the effective maximum stack size + */ + int maxStackSize(); + + /** + * Returns the quantity of items in this stack. + * + * @return the stack quantity + */ + int getAmount(); + + /** + * Sets the quantity of items in this stack. Setting to zero makes this stack empty. + * + * @param amt the stack quantity + */ + void setAmount(int amt); + + /** + * Returns if the stack has any valid items in it. + * This should be checked first before using most other methods. + * + * @return true if not empty + */ + boolean isEmpty(); + + void addEnchantment(MCEnchantment e, int level); + + void addUnsafeEnchantment(MCEnchantment e, int level); + + Map getEnchantments(); + + void removeEnchantment(MCEnchantment e); + + boolean hasItemMeta(); + + MCItemMeta getItemMeta(); + + void setItemMeta(MCItemMeta im); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCKnowledgeBookMeta.java b/src/main/java/com/laytonsmith/abstraction/MCKnowledgeBookMeta.java new file mode 100644 index 0000000000..15d0e44652 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCKnowledgeBookMeta.java @@ -0,0 +1,13 @@ +package com.laytonsmith.abstraction; + +import java.util.List; + +public interface MCKnowledgeBookMeta extends MCItemMeta { + + boolean hasRecipes(); + + List getRecipes(); + + void setRecipes(List recipes); + +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCLeashable.java b/src/main/java/com/laytonsmith/abstraction/MCLeashable.java new file mode 100644 index 0000000000..c47b93ff6b --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCLeashable.java @@ -0,0 +1,6 @@ +package com.laytonsmith.abstraction; +public interface MCLeashable { + MCEntity getLeashHolder(); + boolean isLeashed(); + void setLeashHolder(MCEntity holder); +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCLeatherArmorMeta.java b/src/main/java/com/laytonsmith/abstraction/MCLeatherArmorMeta.java index d3d163b402..1a3b0065dc 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCLeatherArmorMeta.java +++ b/src/main/java/com/laytonsmith/abstraction/MCLeatherArmorMeta.java @@ -1,26 +1,18 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ package com.laytonsmith.abstraction; - -/** - * - * - */ public interface MCLeatherArmorMeta extends MCItemMeta { - /** - * Gets the color of the armor. If it has not been set otherwise, it will be {@link ItemFactory#getDefaultLeatherColor()}. - * - * @return the color of the armor, never null - */ - MCColor getColor(); - /** - * Sets the color of the armor. - * - * @param color the color to set. Setting it to null is equivalent to setting it to {@link ItemFactory#getDefaultLeatherColor()}. - */ - void setColor(MCColor color); + /** + * Gets the color of the armor. + * + * @return the color of the armor, never null + */ + MCColor getColor(); + + /** + * Sets the color of the armor. + * + * @param color the color to set or default if null. + */ + void setColor(MCColor color); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCLightningStrike.java b/src/main/java/com/laytonsmith/abstraction/MCLightningStrike.java deleted file mode 100644 index d2233d7a7f..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/MCLightningStrike.java +++ /dev/null @@ -1,11 +0,0 @@ - -package com.laytonsmith.abstraction; - -/** - * - * @author Jim - */ -public interface MCLightningStrike extends MCEntity{ - - public boolean isEffect(); -} diff --git a/src/main/java/com/laytonsmith/abstraction/MCLivingEntity.java b/src/main/java/com/laytonsmith/abstraction/MCLivingEntity.java index 9985f3fbe2..2fdf5cc96f 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCLivingEntity.java +++ b/src/main/java/com/laytonsmith/abstraction/MCLivingEntity.java @@ -1,99 +1,176 @@ - package com.laytonsmith.abstraction; import com.laytonsmith.abstraction.blocks.MCBlock; +import com.laytonsmith.abstraction.blocks.MCMaterial; +import com.laytonsmith.abstraction.enums.MCAttribute; +import com.laytonsmith.abstraction.enums.MCPotionEffectType; import com.laytonsmith.core.constructs.Target; + import java.util.HashSet; import java.util.List; -/** - * - * - */ -public interface MCLivingEntity extends MCEntity, MCProjectileSource { +public interface MCLivingEntity extends MCEntity, MCLeashable, MCProjectileSource { + + boolean addEffect(MCPotionEffectType type, int strength, int ticks, boolean ambient, boolean particles, boolean icon); + + boolean removeEffect(MCPotionEffectType type); + + void removeEffects(); + + List getEffects(); + + void damage(double amount); + + void damage(double amount, MCEntity source); + + boolean getCanPickupItems(); + + boolean getRemoveWhenFarAway(); - public void addEffect(int potionID, int strength, int seconds, boolean ambient, Target t); - public boolean removeEffect(int potionID); /** - * Returns the maximum effect id, inclusive. - * @return + * With the addition of ArmorStands, this can be null. At the time of this writing, ArmorStands are the only + * LivingEntity with such a limitation, and a workaround has been added. */ - public int getMaxEffect(); - public List getEffects(); - public void damage(double amount); - public void damage(double amount, MCEntity source); - public boolean getCanPickupItems(); - public boolean getRemoveWhenFarAway(); - public String getCustomName(); - public MCEntityEquipment getEquipment(); - public double getEyeHeight(); - public double getEyeHeight(boolean ignoreSneaking); - public MCLocation getEyeLocation(); - public double getHealth(); - public MCPlayer getKiller(); - public double getLastDamage(); - public MCEntity getLeashHolder(); - public MCLivingEntity getTarget(Target t); - public MCBlock getTargetBlock(HashSet transparent, int maxDistance, boolean castToByte); - public MCBlock getTargetBlock(HashSet transparent, int maxDistance); - public List getLastTwoTargetBlocks(HashSet transparent, int maxDistance); - public List getLineOfSight(HashSet transparent, int maxDistance); - public boolean hasLineOfSight(MCEntity other); - public double getMaxHealth(); - public int getMaximumAir(); - public int getMaximumNoDamageTicks(); - public int getNoDamageTicks(); - public int getRemainingAir(); - public boolean isCustomNameVisible(); - public boolean isLeashed(); - public void resetMaxHealth(); - public void setCanPickupItems(boolean pickup); - public void setRemoveWhenFarAway(boolean remove); - public void setCustomName(String name); - public void setCustomNameVisible(boolean visible); - public void setHealth(double health); - public void setLastDamage(double damage); - public void setLeashHolder(MCEntity holder); - public void setMaxHealth(double health); - public void setMaximumAir(int ticks); - public void setMaximumNoDamageTicks(int ticks); - public void setNoDamageTicks(int ticks); - public void setRemainingAir(int ticks); - public void setTarget(MCLivingEntity target, Target t); + MCEntityEquipment getEquipment(); + + double getEyeHeight(); + + double getEyeHeight(boolean ignoreSneaking); + + MCLocation getEyeLocation(); + + double getHealth(); + + MCPlayer getKiller(); + + void setKiller(MCPlayer killer); + + double getLastDamage(); + + MCLivingEntity getTarget(Target t); + + MCBlock getTargetBlock(HashSet transparent, int maxDistance); + + MCBlock getTargetSpace(int maxDistance); + + List getLineOfSight(HashSet transparent, int maxDistance); + + boolean hasLineOfSight(MCEntity other); + + double getMaxHealth(); + + int getMaximumAir(); + + int getNoDamageTicks(); + + int getRemainingAir(); + + boolean isGliding(); + + boolean hasAI(); + + void resetMaxHealth(); + + void setCanPickupItems(boolean pickup); + + void setRemoveWhenFarAway(boolean remove); + + void setHealth(double health); + + void setLastDamage(double damage); + + void setMaxHealth(double health); + + void setMaximumAir(int ticks); + + void setNoDamageTicks(int ticks); + + void setRemainingAir(int ticks); + + void setTarget(MCLivingEntity target, Target t); + + void setGliding(Boolean glide); + + void setAI(Boolean ai); + + boolean isCollidable(); + + void setCollidable(boolean collidable); + /** - * Kills the entity. In some cases, this will be equivalent to setHealth(0), but - * may not be, so this method should be used instead. + * Kills the entity. In some cases, this will be equivalent to setHealth(0), but may not be, so this method should + * be used instead. */ - public void kill(); + void kill(); + + boolean isTameable(); + + double getAttributeValue(MCAttribute attr); + + double getAttributeDefault(MCAttribute attr); + + double getAttributeBase(MCAttribute attr); - public static class MCEffect{ + void setAttributeBase(MCAttribute attr, double base); - private int potionID; + void resetAttributeBase(MCAttribute attr); + + List getAttributeModifiers(MCAttribute attr); + + void addAttributeModifier(MCAttributeModifier modifier); + + void removeAttributeModifier(MCAttributeModifier modifier); + + boolean isSleeping(); + + class MCEffect { + + private MCPotionEffectType type; private int strength; - private int secondsRemaining; + private int ticksRemaining; private boolean ambient; - public MCEffect(int potionID, int strength, int secondsRemaining, boolean ambient){ - this.potionID = potionID; + private boolean particles; + private boolean icon; + + public MCEffect(MCPotionEffectType type, int strength, int ticks, boolean ambient) { + this(type, strength, ticks, ambient, true, true); + } + + public MCEffect(MCPotionEffectType type, int strength, int ticks, boolean ambient, boolean particles) { + this(type, strength, ticks, ambient, particles, particles); + } + + public MCEffect(MCPotionEffectType type, int strength, int ticks, boolean ambient, boolean particles, boolean icon) { + this.type = type; this.strength = strength; - this.secondsRemaining = secondsRemaining; + this.ticksRemaining = ticks; this.ambient = ambient; + this.particles = particles; + this.icon = icon; } - public int getPotionID() { - return potionID; + public MCPotionEffectType getPotionEffectType() { + return type; } public int getStrength() { return strength; } - public int getSecondsRemaining() { - return secondsRemaining; + public int getTicksRemaining() { + return ticksRemaining; } - + public boolean isAmbient() { return ambient; } + public boolean hasParticles() { + return particles; + } + + public boolean showIcon() { + return icon; + } } } diff --git a/src/main/java/com/laytonsmith/abstraction/MCLocation.java b/src/main/java/com/laytonsmith/abstraction/MCLocation.java index a0109acde8..40ffb16376 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCLocation.java +++ b/src/main/java/com/laytonsmith/abstraction/MCLocation.java @@ -1,43 +1,63 @@ - package com.laytonsmith.abstraction; +import com.laytonsmith.PureUtilities.Vector3D; import com.laytonsmith.abstraction.blocks.MCBlock; -/** - * - * - */ -public interface MCLocation extends AbstractionObject{ - public double getX(); - public double getY(); - public double getZ(); - public double distance(MCLocation o); - public double distanceSquared(MCLocation o); - public MCWorld getWorld(); - public float getYaw(); - public float getPitch(); - public int getBlockX(); - public int getBlockY(); - public int getBlockZ(); - public MCChunk getChunk(); - public MCBlock getBlock(); - public MCLocation add(MCLocation vec); - public MCLocation add(Velocity vec); - public MCLocation add(double x, double y, double z); - public MCLocation multiply(double m); - public Velocity toVector(); - public MCLocation subtract(MCLocation vec); - public MCLocation subtract(Velocity vec); - public MCLocation subtract(double x, double y, double z); - - public void setX(double x); - public void setY(double y); - public void setZ(double z); - public void setPitch(float p); - public void setYaw(float y); - public void breakBlock(); - - public Velocity getDirection(); - - public MCLocation clone(); +public interface MCLocation extends AbstractionObject { + + double getX(); + + double getY(); + + double getZ(); + + double distance(MCLocation o); + + double distanceSquared(MCLocation o); + + MCWorld getWorld(); + + float getYaw(); + + float getPitch(); + + int getBlockX(); + + int getBlockY(); + + int getBlockZ(); + + MCChunk getChunk(); + + MCBlock getBlock(); + + MCLocation add(MCLocation vec); + + MCLocation add(Vector3D vec); + + MCLocation add(double x, double y, double z); + + MCLocation multiply(double m); + + Vector3D toVector(); + + MCLocation subtract(MCLocation vec); + + MCLocation subtract(Vector3D vec); + + MCLocation subtract(double x, double y, double z); + + void setX(double x); + + void setY(double y); + + void setZ(double z); + + void setPitch(float p); + + void setYaw(float y); + + Vector3D getDirection(); + + MCLocation clone(); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCMapMeta.java b/src/main/java/com/laytonsmith/abstraction/MCMapMeta.java new file mode 100644 index 0000000000..07bba6279a --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCMapMeta.java @@ -0,0 +1,15 @@ +package com.laytonsmith.abstraction; + +public interface MCMapMeta extends MCItemMeta { + + boolean hasMapId(); + + int getMapId(); + + void setMapId(int id); + + MCColor getColor(); + + void setColor(MCColor color); + +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCMaterialData.java b/src/main/java/com/laytonsmith/abstraction/MCMaterialData.java deleted file mode 100644 index 7f9f4b0f0b..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/MCMaterialData.java +++ /dev/null @@ -1,13 +0,0 @@ - -package com.laytonsmith.abstraction; - -import com.laytonsmith.abstraction.blocks.MCMaterial; - -/** - * - * - */ -public interface MCMaterialData extends AbstractionObject{ - public int getData(); - public MCMaterial getMaterial(); -} diff --git a/src/main/java/com/laytonsmith/abstraction/MCMerchant.java b/src/main/java/com/laytonsmith/abstraction/MCMerchant.java new file mode 100644 index 0000000000..746fd4ac90 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCMerchant.java @@ -0,0 +1,16 @@ +package com.laytonsmith.abstraction; + +import java.util.List; + +public interface MCMerchant extends AbstractionObject { + + boolean isTrading(); + + MCHumanEntity getTrader(); + + List getRecipes(); + + void setRecipes(List recipes); + + String getTitle(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCMerchantRecipe.java b/src/main/java/com/laytonsmith/abstraction/MCMerchantRecipe.java new file mode 100644 index 0000000000..c8105ee4cf --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCMerchantRecipe.java @@ -0,0 +1,22 @@ +package com.laytonsmith.abstraction; + +import java.util.List; + +public interface MCMerchantRecipe extends MCRecipe { + + int getMaxUses(); + + void setMaxUses(int maxUses); + + int getUses(); + + void setUses(int uses); + + boolean hasExperienceReward(); + + void setHasExperienceReward(boolean flag); + + List getIngredients(); + + void setIngredients(List ingredients); +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCMetadataValue.java b/src/main/java/com/laytonsmith/abstraction/MCMetadataValue.java index ccb4e45aac..5ec31a7374 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCMetadataValue.java +++ b/src/main/java/com/laytonsmith/abstraction/MCMetadataValue.java @@ -1,27 +1,28 @@ package com.laytonsmith.abstraction; public interface MCMetadataValue { - public boolean asBoolean(); - public byte asByte(); + boolean asBoolean(); - public double asDouble(); + byte asByte(); - public float asFloat(); + double asDouble(); - public int asInt(); + float asFloat(); - public long asLong(); + int asInt(); - public short asShort(); + long asLong(); - public String asString(); + short asShort(); - public MCPlugin getOwningPlugin(); + String asString(); - public void invalidate(); + MCPlugin getOwningPlugin(); - public Object value(); + void invalidate(); - public Object getHandle(); + Object value(); + + Object getHandle(); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCMetadatable.java b/src/main/java/com/laytonsmith/abstraction/MCMetadatable.java index 6980ac45ce..76758efb16 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCMetadatable.java +++ b/src/main/java/com/laytonsmith/abstraction/MCMetadatable.java @@ -3,11 +3,12 @@ import java.util.List; public interface MCMetadatable extends AbstractionObject { - public List getMetadata(String metadataKey); - public boolean hasMetadata(String metadataKey); + List getMetadata(String metadataKey); - public void removeMetadata(String metadataKey, MCPlugin owningPlugin); + boolean hasMetadata(String metadataKey); - public void setMetadata(String metadataKey, MCMetadataValue newMetadataValue); + void removeMetadata(String metadataKey, MCPlugin owningPlugin); + + void setMetadata(String metadataKey, MCMetadataValue newMetadataValue); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCMusicInstrumentMeta.java b/src/main/java/com/laytonsmith/abstraction/MCMusicInstrumentMeta.java new file mode 100644 index 0000000000..37be7b8be5 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCMusicInstrumentMeta.java @@ -0,0 +1,6 @@ +package com.laytonsmith.abstraction; + +public interface MCMusicInstrumentMeta extends MCItemMeta { + String getInstrument(); + void setInstrument(String instrument); +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCNamespacedKey.java b/src/main/java/com/laytonsmith/abstraction/MCNamespacedKey.java new file mode 100644 index 0000000000..0f4b54d7ab --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCNamespacedKey.java @@ -0,0 +1,4 @@ +package com.laytonsmith.abstraction; + +public interface MCNamespacedKey extends AbstractionObject { +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCNote.java b/src/main/java/com/laytonsmith/abstraction/MCNote.java index 6002ffe8e4..794eba9990 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCNote.java +++ b/src/main/java/com/laytonsmith/abstraction/MCNote.java @@ -1,10 +1,12 @@ - package com.laytonsmith.abstraction; -/** - * - * - */ +import com.laytonsmith.abstraction.enums.MCTone; + public interface MCNote extends AbstractionObject { + MCTone getTone(); + + int getOctave(); + + boolean isSharped(); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCObjective.java b/src/main/java/com/laytonsmith/abstraction/MCObjective.java index d829f76629..b669e002b9 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCObjective.java +++ b/src/main/java/com/laytonsmith/abstraction/MCObjective.java @@ -2,19 +2,25 @@ import com.laytonsmith.abstraction.enums.MCDisplaySlot; -/** - * - * @author jb_aero - */ public interface MCObjective { - public String getCriteria(); - public String getDisplayName(); - public MCDisplaySlot getDisplaySlot(); - public String getName(); - public MCScore getScore(String entry); - public MCScoreboard getScoreboard(); - public boolean isModifiable(); - public void setDisplayName(String displayName); - public void setDisplaySlot(MCDisplaySlot slot); - public void unregister(); + + String getCriteria(); + + String getDisplayName(); + + MCDisplaySlot getDisplaySlot(); + + String getName(); + + MCScore getScore(String entry); + + MCScoreboard getScoreboard(); + + boolean isModifiable(); + + void setDisplayName(String displayName); + + void setDisplaySlot(MCDisplaySlot slot); + + void unregister(); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCOfflinePlayer.java b/src/main/java/com/laytonsmith/abstraction/MCOfflinePlayer.java index 52b5073c58..9c44b84b32 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCOfflinePlayer.java +++ b/src/main/java/com/laytonsmith/abstraction/MCOfflinePlayer.java @@ -1,72 +1,68 @@ - package com.laytonsmith.abstraction; -/** - * - * - */ -public interface MCOfflinePlayer extends MCAnimalTamer{ - public long getFirstPlayed(); +import java.util.UUID; + +public interface MCOfflinePlayer extends MCAnimalTamer { + + long getFirstPlayed(); - public long getLastPlayed(); + long getLastPlayed(); - /** - * Returns the name of this player - * - * @return Player name - */ + /** + * Returns the name of this player + * + * @return Player name + */ @Override - public String getName(); + String getName(); - /** - * Gets a {@link Player} object that this represents, if there is one - *

- * If the player is online, this will return that player. Otherwise, - * it will return null. - * - * @return Online player - */ - public MCPlayer getPlayer(); + /** + * Gets a Player object that this represents, if there is one + *

+ * If the player is online, this will return that player. Otherwise, it will return null. + * + * @return Online player + */ + MCPlayer getPlayer(); - public boolean hasPlayedBefore(); + boolean hasPlayedBefore(); - /** - * Checks if this player is banned or not - * - * @return true if banned, otherwise false - */ - public boolean isBanned(); + /** + * Checks if this player is banned or not + * + * @return true if banned, otherwise false + */ + boolean isBanned(); - /** - * Checks if this player is currently online - * - * @return true if they are online - */ - public boolean isOnline(); + /** + * Checks if this player is currently online + * + * @return true if they are online + */ + boolean isOnline(); - /** - * Checks if this player is whitelisted or not - * - * @return true if whitelisted - */ - public boolean isWhitelisted(); - /** - * Bans or unbans this player - * - * @param banned true if banned - */ - public void setBanned(boolean banned); - /** - * Sets if this player is whitelisted or not - * - * @param value true if whitelisted - */ - public void setWhitelisted(boolean value); + /** + * Checks if this player is whitelisted or not + * + * @return true if whitelisted + */ + boolean isWhitelisted(); /** - * Gets the Location where the player will spawn at their bed, null if they have not slept in one or their current bed spawn is invalid. - * - * @return Bed Spawn Location if bed exists, otherwise null. - */ - public MCLocation getBedSpawnLocation(); + * Sets if this player is whitelisted or not. Depending on how the server retrieved the player, this may or may not + * work. + * + * @param value true if whitelisted + */ + void setWhitelisted(boolean value); + + /** + * Gets the Location where the player will spawn at their bed, null if they have not slept in one or their current + * bed spawn is invalid. + * + * @return Bed Spawn Location if bed exists, otherwise null. + */ + MCLocation getBedSpawnLocation(); + + UUID getUniqueID(); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCOminousBottleMeta.java b/src/main/java/com/laytonsmith/abstraction/MCOminousBottleMeta.java new file mode 100644 index 0000000000..d30a38216c --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCOminousBottleMeta.java @@ -0,0 +1,7 @@ +package com.laytonsmith.abstraction; + +public interface MCOminousBottleMeta extends MCItemMeta { + boolean hasAmplifier(); + int getAmplifier(); + void setAmplifier(int value); +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCParticleData.java b/src/main/java/com/laytonsmith/abstraction/MCParticleData.java new file mode 100644 index 0000000000..beea53a43d --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCParticleData.java @@ -0,0 +1,100 @@ +package com.laytonsmith.abstraction; + +public class MCParticleData { + + public static class DustTransition { + MCColor from; + MCColor to; + + public DustTransition(MCColor from, MCColor to) { + this.from = from; + this.to = to; + } + + public MCColor from() { + return this.from; + } + + public MCColor to() { + return this.to; + } + } + + public static class Trail { + MCLocation location; + MCColor color; + int duration; + + public Trail(MCLocation location, MCColor color, int duration) { + this.location = location; + this.color = color; + this.duration = duration; + } + + public MCLocation location() { + return this.location; + } + + public MCColor color() { + return this.color; + } + + public int duration() { + return this.duration; + } + } + + public static class VibrationBlockDestination { + MCLocation location; + int arrivalTime; + + public VibrationBlockDestination(MCLocation location, int arrivalTime) { + this.location = location; + this.arrivalTime = arrivalTime; + } + + public MCLocation location() { + return this.location; + } + + public int arrivalTime() { + return this.arrivalTime; + } + } + + public static class VibrationEntityDestination { + MCEntity entity; + int arrivalTime; + + public VibrationEntityDestination(MCEntity entity, int arrivalTime) { + this.entity = entity; + this.arrivalTime = arrivalTime; + } + + public MCEntity entity() { + return this.entity; + } + + public int arrivalTime() { + return this.arrivalTime; + } + } + + public static class Spell { + float power; + MCColor color; + + public Spell(MCColor color, float power) { + this.power = power; + this.color = color; + } + + public float power() { + return this.power; + } + + public MCColor color() { + return this.color; + } + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCPattern.java b/src/main/java/com/laytonsmith/abstraction/MCPattern.java new file mode 100644 index 0000000000..ce36051460 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCPattern.java @@ -0,0 +1,11 @@ +package com.laytonsmith.abstraction; + +import com.laytonsmith.abstraction.enums.MCDyeColor; +import com.laytonsmith.abstraction.enums.MCPatternShape; + +public interface MCPattern extends AbstractionObject { + + MCDyeColor getColor(); + + MCPatternShape getShape(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCPlayer.java b/src/main/java/com/laytonsmith/abstraction/MCPlayer.java index 2dc6a808e8..0cb4cf2cd4 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCPlayer.java +++ b/src/main/java/com/laytonsmith/abstraction/MCPlayer.java @@ -1,151 +1,215 @@ - package com.laytonsmith.abstraction; +import com.laytonsmith.abstraction.blocks.MCBlockData; +import com.laytonsmith.abstraction.blocks.MCMaterial; +import com.laytonsmith.abstraction.blocks.MCSign; +import com.laytonsmith.abstraction.enums.MCEntityType; +import com.laytonsmith.abstraction.enums.MCEquipmentSlot; import com.laytonsmith.abstraction.enums.MCInstrument; +import com.laytonsmith.abstraction.enums.MCParticle; +import com.laytonsmith.abstraction.enums.MCPlayerStatistic; +import com.laytonsmith.abstraction.enums.MCPotionEffectType; import com.laytonsmith.abstraction.enums.MCSound; +import com.laytonsmith.abstraction.enums.MCSoundCategory; import com.laytonsmith.abstraction.enums.MCWeather; import java.net.InetSocketAddress; -/** - * - * - */ -public interface MCPlayer extends MCCommandSender, MCHumanEntity, - MCOfflinePlayer { +public interface MCPlayer extends MCCommandSender, MCHumanEntity, MCOfflinePlayer { + + void setSleepingIgnored(boolean value); + boolean isSleepingIgnored(); - public boolean canSee(MCPlayer p); + boolean canSee(MCPlayer p); - public void chat(String chat); + void chat(String chat); - public InetSocketAddress getAddress(); + InetSocketAddress getAddress(); - public boolean getAllowFlight(); + boolean getAllowFlight(); - public MCLocation getCompassTarget(); + MCLocation getCompassTarget(); - public String getDisplayName(); + String getDisplayName(); - public float getExp(); + float getExp(); - public float getFlySpeed(); - - public void setFlySpeed(float speed); + float getFlySpeed(); - public int getFoodLevel(); + void setFlySpeed(float speed); @Override - public MCPlayerInventory getInventory(); + MCPlayerInventory getInventory(); + + MCItemStack getItemAt(Integer slot); + + int getLevel(); + + String getPlayerListName(); - public MCItemStack getItemAt(Integer slot); + String getPlayerListHeader(); - public int getLevel(); + String getPlayerListFooter(); - public String getPlayerListName(); + long getPlayerTime(); - public long getPlayerTime(); + MCWeather getPlayerWeather(); - public MCWeather getPlayerWeather(); + int getRemainingFireTicks(); - public int getRemainingFireTicks(); + MCScoreboard getScoreboard(); - public MCScoreboard getScoreboard(); + int getTotalExperience(); - public int getTotalExperience(); + int getExpToLevel(); - public float getWalkSpeed(); - - public void setWalkSpeed(float speed); + int getExpAtLevel(); - public void giveExp(int xp); + MCEntity getSpectatorTarget(); - public boolean isSneaking(); - - public boolean isSprinting(); + float getWalkSpeed(); - public void kickPlayer(String message); + void setWalkSpeed(float speed); + + void giveExp(int xp); + + boolean isSneaking(); + + boolean isSprinting(); + + void kickPlayer(String message); @Override - public boolean removeEffect(int effect); + boolean removeEffect(MCPotionEffectType type); - public void resetPlayerTime(); + void resetPlayerTime(); - public void resetPlayerWeather(); + void resetPlayerWeather(); - public void sendTexturePack(String url); + void sendResourcePack(String url); - public void sendResourcePack(String url); + void sendTitle(String title, String subtitle, int fadein, int stay, int fadeout); - public void setAllowFlight(boolean flight); + void sendActionMessage(String message); - public void setCompassTarget(MCLocation l); + void setAllowFlight(boolean flight); - public void setDisplayName(String name); + void setCompassTarget(MCLocation l); - public void setExp(float i); + void setDisplayName(String name); - public void setFlying(boolean flight); + void setExp(float i); - public void setFoodLevel(int f); + void setFlying(boolean flight); - public void setLevel(int xp); + void setLevel(int xp); - public void setPlayerListName(String listName); + void setPlayerListName(String listName); - public void setPlayerTime(Long time, boolean relative); + void setPlayerListHeader(String header); - public void setPlayerWeather(MCWeather type); + void setPlayerListFooter(String footer); - public void setRemainingFireTicks(int i); + void setPlayerTime(Long time, boolean relative); - public void setScoreboard(MCScoreboard board); + void setPlayerWeather(MCWeather type); - public void setTempOp(Boolean value) throws Exception; + void setRemainingFireTicks(int i); - public void setTotalExperience(int total); + void setScoreboard(MCScoreboard board); - public void setVanished(boolean set, MCPlayer to); + void setSpectatorTarget(MCEntity entity); - public boolean isNewPlayer(); + void setTempOp(Boolean value) throws Exception; - public String getHost(); + void setTotalExperience(int total); - public void sendBlockChange(MCLocation loc, int material, byte data); + void setVanished(boolean set, MCPlayer to); - public void sendSignTextChange(MCLocation loc, String[] lines); + void hideEntity(MCEntity entity); - /** - * Unlike {@see MCEntity#getLocation}, this will work when not run on the server - * thread, but this does mean that the data recieved may be slightly outdated. - * @return - */ - @Override - public MCLocation asyncGetLocation(); + void showEntity(MCEntity entity); + + boolean canSeeEntity(MCEntity entity); + + boolean isNewPlayer(); + + String getHost(); + + void sendBlockChange(MCLocation loc, MCBlockData data); + + void sendBlockDamage(MCLocation loc, float progress, MCEntity entity); + + void sendSignTextChange(MCLocation loc, String[] lines); + + void sendSignTextChange(MCSign sign); + + void playNote(MCLocation loc, MCInstrument instrument, MCNote note); + + void playSound(MCLocation l, MCSound sound, MCSoundCategory category, float volume, float pitch, Long seed); + + void playSound(MCEntity ent, MCSound sound, MCSoundCategory category, float volume, float pitch, Long seed); - public void playNote(MCLocation loc, MCInstrument instrument, MCNote note); - - public void playSound(MCLocation l, MCSound sound, float volume, float pitch); - - public void playSound(MCLocation l, String sound, float volume, float pitch); + void playSound(MCLocation l, String sound, MCSoundCategory category, float volume, float pitch, Long seed); - public int getHunger(); + void playSound(MCEntity ent, String sound, MCSoundCategory category, float volume, float pitch, Long seed); - public void setHunger(int h); + void stopSound(MCSound sound, MCSoundCategory category); - public float getSaturation(); + void stopSound(String sound, MCSoundCategory category); - public void setSaturation(float s); + void stopSound(MCSoundCategory category); - public void setBedSpawnLocation(MCLocation l); + void spawnParticle(MCLocation l, MCParticle pa, int count, double offsetX, double offsetY, double offsetZ, double velocity, boolean force, Object data); - public void sendPluginMessage(String channel, byte[] message); + int getFoodLevel(); + + void setFoodLevel(int f); + + float getSaturation(); + + void setSaturation(float s); + + float getExhaustion(); + + void setExhaustion(float e); + + void setBedSpawnLocation(MCLocation l, boolean forced); + + void sendPluginMessage(String channel, byte[] message); @Override - public boolean isOp(); + boolean isOp(); + + void setOp(boolean bln); + + boolean isFlying(); + + void updateInventory(); + + int getStatistic(MCPlayerStatistic stat); + + int getStatistic(MCPlayerStatistic stat, MCEntityType type); + + int getStatistic(MCPlayerStatistic stat, MCMaterial type); + + void setStatistic(MCPlayerStatistic stat, int amount); + + void setStatistic(MCPlayerStatistic stat, MCEntityType type, int amount); + + void setStatistic(MCPlayerStatistic stat, MCMaterial type, int amount); + + MCWorldBorder getWorldBorder(); + + void setWorldBorder(MCWorldBorder border); + + String getLocale(); + + void respawn(); - public void setOp(boolean bln); + void sendEquipmentChange(MCLivingEntity entity, MCEquipmentSlot slot, MCItemStack item); - public boolean isFlying(); + int getPing(); - public void updateInventory(); + MCPlayerInput getCurrentInput(); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCPlayerInput.java b/src/main/java/com/laytonsmith/abstraction/MCPlayerInput.java new file mode 100644 index 0000000000..32b5a9f4c3 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCPlayerInput.java @@ -0,0 +1,11 @@ +package com.laytonsmith.abstraction; + +public interface MCPlayerInput { + boolean forward(); + boolean backward(); + boolean left(); + boolean right(); + boolean jump(); + boolean sneak(); + boolean sprint(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCPlayerInventory.java b/src/main/java/com/laytonsmith/abstraction/MCPlayerInventory.java index 8120ddc72a..eefc5f1a99 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCPlayerInventory.java +++ b/src/main/java/com/laytonsmith/abstraction/MCPlayerInventory.java @@ -1,19 +1,32 @@ - package com.laytonsmith.abstraction; -/** - * - * - */ public interface MCPlayerInventory extends MCInventory { - public void setHelmet(MCItemStack stack); - public void setChestplate(MCItemStack stack); - public void setLeggings(MCItemStack stack); - public void setBoots(MCItemStack stack); - public MCItemStack getHelmet(); - public MCItemStack getChestplate(); - public MCItemStack getLeggings(); - public MCItemStack getBoots(); - public int getHeldItemSlot(); - public void setHeldItemSlot(int slot); + + void setHelmet(MCItemStack stack); + + void setChestplate(MCItemStack stack); + + void setLeggings(MCItemStack stack); + + void setBoots(MCItemStack stack); + + void setItemInMainHand(MCItemStack stack); + + void setItemInOffHand(MCItemStack stack); + + MCItemStack getHelmet(); + + MCItemStack getChestplate(); + + MCItemStack getLeggings(); + + MCItemStack getBoots(); + + MCItemStack getItemInMainHand(); + + MCItemStack getItemInOffHand(); + + int getHeldItemSlot(); + + void setHeldItemSlot(int slot); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCPlayerProfile.java b/src/main/java/com/laytonsmith/abstraction/MCPlayerProfile.java new file mode 100644 index 0000000000..5e1986ad0e --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCPlayerProfile.java @@ -0,0 +1,17 @@ +package com.laytonsmith.abstraction; + +import java.util.UUID; + +public interface MCPlayerProfile extends AbstractionObject { + + String getName(); + + UUID getId(); + + MCProfileProperty getProperty(String key); + + void setProperty(MCProfileProperty property); + + boolean removeProperty(String key); + +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCPlugin.java b/src/main/java/com/laytonsmith/abstraction/MCPlugin.java index ff9fdbd2fa..cd84e8116c 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCPlugin.java +++ b/src/main/java/com/laytonsmith/abstraction/MCPlugin.java @@ -1,12 +1,10 @@ - package com.laytonsmith.abstraction; -/** - * - * - */ -public interface MCPlugin extends AbstractionObject{ - public boolean isEnabled(); - public boolean isInstanceOf(Class c); - public String getName(); +public interface MCPlugin extends AbstractionObject { + + boolean isEnabled(); + + boolean isInstanceOf(Class c); + + String getName(); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCPluginManager.java b/src/main/java/com/laytonsmith/abstraction/MCPluginManager.java index 301d32d4b6..0b5ca41a03 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCPluginManager.java +++ b/src/main/java/com/laytonsmith/abstraction/MCPluginManager.java @@ -1,13 +1,10 @@ - package com.laytonsmith.abstraction; import java.util.List; -/** - * - * - */ -public interface MCPluginManager extends AbstractionObject{ - public MCPlugin getPlugin(String name); - public List getPlugins(); +public interface MCPluginManager extends AbstractionObject { + + MCPlugin getPlugin(String name); + + List getPlugins(); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCPluginMeta.java b/src/main/java/com/laytonsmith/abstraction/MCPluginMeta.java index 0608fa3732..1fa8534e22 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCPluginMeta.java +++ b/src/main/java/com/laytonsmith/abstraction/MCPluginMeta.java @@ -3,89 +3,88 @@ import java.util.ArrayList; import java.util.List; -/** - * - * - */ public abstract class MCPluginMeta { - - private List openOutgoingChannels = new ArrayList(); - private List openIncomingChannels = new ArrayList(); - protected MCPluginMeta(){ + + private List openOutgoingChannels = new ArrayList<>(); + private List openIncomingChannels = new ArrayList<>(); + + protected MCPluginMeta() { StaticLayer.GetConvertor().addShutdownHook(new Runnable() { @Override public void run() { - List copyOutgoing = new ArrayList(openOutgoingChannels); - for(String s : copyOutgoing){ + List copyOutgoing = new ArrayList<>(openOutgoingChannels); + for(String s : copyOutgoing) { closeOutgoingChannel(s); } - List copyIncoming = new ArrayList(openIncomingChannels); - for(String s : copyIncoming){ + List copyIncoming = new ArrayList<>(openIncomingChannels); + for(String s : copyIncoming) { closeIncomingChannel(s); } } }); } - - - public void closeOutgoingChannel(String channel){ - if(openOutgoingChannels.contains(channel)){ + + public void closeOutgoingChannel(String channel) { + if(openOutgoingChannels.contains(channel)) { closeOutgoingChannel0(channel); openOutgoingChannels.remove(channel); } } - - public void openOutgoingChannel(String channel){ - if(!openOutgoingChannels.contains(channel)){ + + public void openOutgoingChannel(String channel) { + if(!openOutgoingChannels.contains(channel)) { openOutgoingChannel0(channel); openOutgoingChannels.add(channel); } } - - public void registerChannelListener(String channel, PluginMessageListener listener){ + + public void registerChannelListener(String channel, PluginMessageListener listener) { throw new UnsupportedOperationException("Not yet implemented"); } - - protected void triggerOnMessage(MCPlayer player, String channel, byte[] message){ + + protected void triggerOnMessage(MCPlayer player, String channel, byte[] message) { //TODO } - + public abstract void closeOutgoingChannel0(String channel); + public abstract void openOutgoingChannel0(String channel); - - public void closeIncomingChannel(String channel){ - if(openIncomingChannels.contains(channel)){ + + public void closeIncomingChannel(String channel) { + if(openIncomingChannels.contains(channel)) { closeIncomingChannel0(channel); openIncomingChannels.remove(channel); } } - - public void openIncomingChannel(String channel){ - if(!openIncomingChannels.contains(channel)){ + + public void openIncomingChannel(String channel) { + if(!openIncomingChannels.contains(channel)) { openIncomingChannel0(channel); openIncomingChannels.add(channel); } } - + /** - * Sends a message to the given player. If the channel specified is not opened, - * it will be opened first. + * Sends a message to the given player. If the channel specified is not opened, it will be opened first. + * * @param from * @param channel - * @param message + * @param message */ - public final void fakeIncomingMessage(MCPlayer from, String channel, byte[] message){ + public final void fakeIncomingMessage(MCPlayer from, String channel, byte[] message) { openOutgoingChannel(channel); sendIncomingMessage0(from, channel, message); } - + public abstract void closeIncomingChannel0(String channel); + public abstract void openIncomingChannel0(String channel); + protected abstract void sendIncomingMessage0(MCPlayer player, String channel, byte[] message); - + public static interface PluginMessageListener { + void trigger(MCPlayer player, byte[] message); } - } diff --git a/src/main/java/com/laytonsmith/abstraction/MCPotionData.java b/src/main/java/com/laytonsmith/abstraction/MCPotionData.java new file mode 100644 index 0000000000..cccda5fdf0 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCPotionData.java @@ -0,0 +1,12 @@ +package com.laytonsmith.abstraction; + +import com.laytonsmith.abstraction.enums.MCPotionType; + +public interface MCPotionData extends AbstractionObject { + + MCPotionType getType(); + + boolean isExtended(); + + boolean isUpgraded(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCPotionMeta.java b/src/main/java/com/laytonsmith/abstraction/MCPotionMeta.java index fc38c4bf60..5484b54483 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCPotionMeta.java +++ b/src/main/java/com/laytonsmith/abstraction/MCPotionMeta.java @@ -1,16 +1,36 @@ package com.laytonsmith.abstraction; import com.laytonsmith.abstraction.MCLivingEntity.MCEffect; +import com.laytonsmith.abstraction.enums.MCPotionEffectType; +import com.laytonsmith.abstraction.enums.MCPotionType; import com.laytonsmith.core.constructs.Target; import java.util.List; public interface MCPotionMeta extends MCItemMeta { - public boolean addCustomEffect(int potionID, int strength, int seconds, boolean ambient, boolean overwrite, Target t); - public boolean clearCustomEffects(); - public List getCustomEffects(); - public boolean hasCustomEffect(int id); - public boolean hasCustomEffects(); - public boolean removeCustomEffect(int id); - public boolean setMainEffect(int id); + MCPotionData getBasePotionData(); + + void setBasePotionData(MCPotionData pd); + + MCPotionType getBasePotionType(); + + void setBasePotionType(MCPotionType pt); + + boolean addCustomEffect(MCPotionEffectType type, int strength, int ticks, boolean ambient, boolean particles, boolean icon, boolean force, Target t); + + boolean clearCustomEffects(); + + List getCustomEffects(); + + boolean hasCustomEffect(MCPotionEffectType type); + + boolean hasCustomEffects(); + + boolean removeCustomEffect(MCPotionEffectType type); + + boolean hasColor(); + + MCColor getColor(); + + void setColor(MCColor color); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCProfileProperty.java b/src/main/java/com/laytonsmith/abstraction/MCProfileProperty.java new file mode 100644 index 0000000000..2b904f15ab --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCProfileProperty.java @@ -0,0 +1,27 @@ +package com.laytonsmith.abstraction; + +public class MCProfileProperty { + + private final String name; + private final String value; + private final String signature; + + public MCProfileProperty(String name, String value, String signature) { + this.name = name; + this.value = value; + this.signature = signature; + } + + public String getName() { + return this.name; + } + + public String getValue() { + return this.value; + } + + public String getSignature() { + return this.signature; + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCProjectile.java b/src/main/java/com/laytonsmith/abstraction/MCProjectile.java deleted file mode 100644 index 416412215d..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/MCProjectile.java +++ /dev/null @@ -1,13 +0,0 @@ - -package com.laytonsmith.abstraction; - -/** - * - * - */ -public interface MCProjectile extends MCEntity, MCMetadatable { - public boolean doesBounce(); - public MCProjectileSource getShooter(); - public void setBounce(boolean doesBounce); - public void setShooter(MCProjectileSource shooter); -} diff --git a/src/main/java/com/laytonsmith/abstraction/MCProjectileSource.java b/src/main/java/com/laytonsmith/abstraction/MCProjectileSource.java index af05299fbe..1b4347492e 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCProjectileSource.java +++ b/src/main/java/com/laytonsmith/abstraction/MCProjectileSource.java @@ -1,13 +1,12 @@ package com.laytonsmith.abstraction; -import com.laytonsmith.abstraction.enums.MCProjectileType; +import com.laytonsmith.PureUtilities.Vector3D; +import com.laytonsmith.abstraction.entities.MCProjectile; +import com.laytonsmith.abstraction.enums.MCEntityType; -/** - * - * @author jb_aero - */ public interface MCProjectileSource extends AbstractionObject { - - public MCProjectile launchProjectile(MCProjectileType projectile); - public MCProjectile launchProjectile(MCProjectileType projectile, Velocity init); + + MCProjectile launchProjectile(MCEntityType projectile); + + MCProjectile launchProjectile(MCEntityType projectile, Vector3D init); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCRecipe.java b/src/main/java/com/laytonsmith/abstraction/MCRecipe.java index 36882ba82f..e43fbc167d 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCRecipe.java +++ b/src/main/java/com/laytonsmith/abstraction/MCRecipe.java @@ -3,6 +3,9 @@ import com.laytonsmith.abstraction.enums.MCRecipeType; public interface MCRecipe extends AbstractionObject { - public MCItemStack getResult(); - public MCRecipeType getRecipeType(); + MCItemStack getResult(); + MCRecipeType getRecipeType(); + String getKey(); + String getGroup(); + void setGroup(String group); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCRecipeChoice.java b/src/main/java/com/laytonsmith/abstraction/MCRecipeChoice.java new file mode 100644 index 0000000000..568352b520 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCRecipeChoice.java @@ -0,0 +1,33 @@ +package com.laytonsmith.abstraction; + +import com.laytonsmith.abstraction.blocks.MCMaterial; + +import java.util.ArrayList; +import java.util.List; + +public interface MCRecipeChoice { + + class MaterialChoice implements MCRecipeChoice { + List choices = new ArrayList<>(); + + public void addMaterial(MCMaterial material) { + choices.add(material); + } + + public List getMaterials() { + return choices; + } + } + + class ExactChoice implements MCRecipeChoice { + List choices = new ArrayList<>(); + + public void addItem(MCItemStack itemStack) { + choices.add(itemStack); + } + + public List getItems() { + return choices; + } + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCRemoteCommandSender.java b/src/main/java/com/laytonsmith/abstraction/MCRemoteCommandSender.java new file mode 100644 index 0000000000..84b6c73e94 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCRemoteCommandSender.java @@ -0,0 +1,8 @@ +package com.laytonsmith.abstraction; + +/** + * + */ +public interface MCRemoteCommandSender extends MCConsoleCommandSender { + +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCScore.java b/src/main/java/com/laytonsmith/abstraction/MCScore.java index 28e6c49391..9384657293 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCScore.java +++ b/src/main/java/com/laytonsmith/abstraction/MCScore.java @@ -1,13 +1,12 @@ package com.laytonsmith.abstraction; -/** - * - * @author jb_aero - */ public interface MCScore { - public MCObjective getObjective(); - public MCOfflinePlayer getPlayer(); - public int getScore(); - public MCScoreboard getScoreboard(); - public void setScore(int score); + + MCObjective getObjective(); + + int getScore(); + + MCScoreboard getScoreboard(); + + void setScore(int score); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCScoreboard.java b/src/main/java/com/laytonsmith/abstraction/MCScoreboard.java index 95755ae2d5..7a6d7ff8a6 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCScoreboard.java +++ b/src/main/java/com/laytonsmith/abstraction/MCScoreboard.java @@ -4,25 +4,38 @@ import java.util.Set; public interface MCScoreboard { - public void clearSlot(MCDisplaySlot slot); - public MCObjective getObjective(MCDisplaySlot slot); - public MCObjective getObjective(String name); + + void clearSlot(MCDisplaySlot slot); + + MCObjective getObjective(MCDisplaySlot slot); + + MCObjective getObjective(String name); + /** * * @return Set of all objectives on this scoreboard */ - public Set getObjectives(); - public Set getObjectivesByCriteria(String criteria); + Set getObjectives(); + + Set getObjectivesByCriteria(String criteria); + /** * * @return Set of all players tracked by this scoreboard */ - public Set getEntries(); - public MCTeam getPlayerTeam(MCOfflinePlayer player); - public Set getScores(String entry); - public MCTeam getTeam(String teamName); - public Set getTeams(); - public MCObjective registerNewObjective(String name, String criteria); - public MCTeam registerNewTeam(String name); - public void resetScores(String entry); + Set getEntries(); + + MCTeam getPlayerTeam(String entry); + + Set getScores(String entry); + + MCTeam getTeam(String teamName); + + Set getTeams(); + + MCObjective registerNewObjective(String name, String criteria); + + MCTeam registerNewTeam(String name); + + void resetScores(String entry); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCServer.java b/src/main/java/com/laytonsmith/abstraction/MCServer.java index 9230437561..4b0ee5b91e 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCServer.java +++ b/src/main/java/com/laytonsmith/abstraction/MCServer.java @@ -1,81 +1,186 @@ package com.laytonsmith.abstraction; +import com.laytonsmith.abstraction.blocks.MCBlockData; +import com.laytonsmith.abstraction.enums.MCBarColor; +import com.laytonsmith.abstraction.enums.MCBarStyle; import com.laytonsmith.abstraction.enums.MCInventoryType; +import com.laytonsmith.abstraction.enums.MCVersion; import com.laytonsmith.abstraction.pluginmessages.MCMessenger; + import java.util.Collection; import java.util.List; import java.util.Set; -import net.milkbowl.vault.economy.Economy; - - -/** - * - * - */ -public interface MCServer extends AbstractionObject{ - public String getName(); - public Collection getOnlinePlayers(); - public boolean dispatchCommand(MCCommandSender cs, String string) throws MCCommandException; - public MCPluginManager getPluginManager(); - public MCPlayer getPlayer(String name); - public MCWorld getWorld(String name); - public List getWorlds(); - public void broadcastMessage(String message); - public void broadcastMessage(String message, String permission); - public MCConsoleCommandSender getConsole(); - public MCItemFactory getItemFactory(); - public MCCommandMap getCommandMap(); - public MCInventory createInventory(MCInventoryHolder owner, MCInventoryType type); - public MCInventory createInventory(MCInventoryHolder owner, int size, String title); - public MCInventory createInventory(MCInventoryHolder owner, int size); - - public MCOfflinePlayer getOfflinePlayer(String player); - public MCOfflinePlayer[] getOfflinePlayers(); - - /* Boring information get methods -.- */ - public String getServerName(); - public String getModVersion(); - public String getVersion(); - public int getPort(); - public Boolean getAllowEnd(); - public Boolean getAllowFlight(); - public Boolean getAllowNether(); - public Boolean getOnlineMode(); - public String getWorldContainer(); - public int getMaxPlayers(); - public List getBannedPlayers(); - public List getWhitelistedPlayers(); - public List getOperators(); - - public void banIP(String address); - public Set getIPBans(); - public void unbanIP(String address); - - public MCScoreboard getMainScoreboard(); - public MCScoreboard getNewScoreboard(); - - public Economy getEconomy(); - - public void runasConsole(String cmd); - public MCMessenger getMessenger(); - - public boolean unloadWorld(MCWorld world, boolean save); - - public boolean addRecipe(MCRecipe recipe); - public List getRecipesFor(MCItemStack result); - public List allRecipes(); - public void clearRecipes(); - public void resetRecipes(); - - - public void shutdown(); +import java.util.UUID; + +public interface MCServer extends AbstractionObject { + + String getName(); + + Collection getOnlinePlayers(); + + boolean dispatchCommand(MCCommandSender cs, String string) throws MCCommandException; + + MCPluginManager getPluginManager(); + + MCPlayer getPlayerExact(String name); + + MCPlayer getPlayer(String name); + + MCPlayer getPlayer(UUID uuid); + + MCEntity getEntity(UUID uuid); + + MCWorld getWorld(String name); + + List getWorlds(); + + /** + * Broadcasts a message to all online players and console. + * @param message - The message to broadcast. + */ + void broadcastMessage(String message); + + /** + * Broadcasts a message to all online players with a given permission and console. + * @param message - The message to broadcast. + * @param permission - The required permission to receive the message. + */ + void broadcastMessage(String message, String permission); + + /** + * Broadcasts a message to a list of recipients. + * {@link MCConsoleCommandSender Console} has to be included in this list to receive the broadcast. + * @param message - The message to broadcast. + * @param recipients - A list of {@link MCCommandSender command senders} to send the message to. + */ + void broadcastMessage(String message, Set recipients); + + MCConsoleCommandSender getConsole(); + + MCItemFactory getItemFactory(); + + MCCommandMap getCommandMap(); + + MCInventory createInventory(MCInventoryHolder owner, MCInventoryType type, String title); + + MCInventory createInventory(MCInventoryHolder owner, int size, String title); + + /** + * Gets a player profile with the specified UUID or name. If id is null, name will be used. + * A profile provides MC account information including name, UUID, and properties like skin texture data. + * Returns null if player doesn't exist (or server implementation does not support this feature). + * + * @param id The user UUID + * @param name The player name + * @return The player profile for this user + */ + MCPlayerProfile getPlayerProfile(UUID id, String name); /** - * Dispatches a command like {@link #dispatchCommand(com.laytonsmith.abstraction.MCCommandSender, java.lang.String) }, but - * attempts to capture the output of the command, and returns that. + * Provides access to local user data associated with a name. Depending on the implementation, a web lookup with the + * official API may or may not be performed. + * + * @param player The name to lookup + * @return An object containing any info that can be accessed regardless of a connected player. + */ + MCOfflinePlayer getOfflinePlayer(String player); + + /** + * Provides access to local user data associated with a UUID. Depending on the implementation, a web lookup with the + * official API may or may not be performed. + * + * @param uuid The UUID to lookup + * @return An object containing any info that can be accessed regardless of a connected player. + */ + MCOfflinePlayer getOfflinePlayer(UUID uuid); + + MCOfflinePlayer[] getOfflinePlayers(); + + String getMotd(); + + String getAPIVersion(); + + String getServerVersion(); + + MCVersion getMinecraftVersion(); + + int getPort(); + + String getIp(); + + boolean getAllowEnd(); + + boolean getAllowFlight(); + + boolean getAllowNether(); + + boolean getOnlineMode(); + + int getViewDistance(); + + String getWorldContainer(); + + int getMaxPlayers(); + + List getBannedPlayers(); + + List getWhitelistedPlayers(); + + List getOperators(); + + void banName(String name, String reason, String source); + + void unbanName(String name); + + void banIP(String address); + + Set getIPBans(); + + void unbanIP(String address); + + MCScoreboard getMainScoreboard(); + + MCScoreboard getNewScoreboard(); + + void runasConsole(String cmd); + + MCMessenger getMessenger(); + + boolean unloadWorld(MCWorld world, boolean save); + + boolean addRecipe(MCRecipe recipe); + + boolean removeRecipe(String key); + + List getRecipesFor(MCItemStack result); + + List allRecipes(); + + void clearRecipes(); + + void resetRecipes(); + + void savePlayers(); + + void shutdown(); + + /** + * Dispatches a command like {@link #dispatchCommand(com.laytonsmith.abstraction.MCCommandSender, java.lang.String) + * }, but attempts to capture the output of the command, and returns that. + * * @param commandSender The command sender * @param cmd The command * @return The command's captured output, if possible, otherwise an empty string, never null. */ - public String dispatchAndCaptureCommand(MCCommandSender commandSender, String cmd); + String dispatchAndCaptureCommand(MCCommandSender commandSender, String cmd); + + MCBossBar createBossBar(String title, MCBarColor color, MCBarStyle style); + + MCBlockData createBlockData(String data); + + MCMerchant createMerchant(String title); + + MCWorldBorder createWorldBorder(); + + List selectEntites(MCCommandSender sender, String selector); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCShapedRecipe.java b/src/main/java/com/laytonsmith/abstraction/MCShapedRecipe.java index cb531344e7..9efd2d9047 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCShapedRecipe.java +++ b/src/main/java/com/laytonsmith/abstraction/MCShapedRecipe.java @@ -1,21 +1,14 @@ package com.laytonsmith.abstraction; +import com.laytonsmith.abstraction.blocks.MCMaterial; + import java.util.Map; public interface MCShapedRecipe extends MCRecipe { - - public Map getIngredientMap(); - - @Override - public MCItemStack getResult(); - - public String[] getShape(); - - public MCShapedRecipe setIngredient(char key, MCItemStack ingredient); - - public MCShapedRecipe setIngredient(char key, int type, int data); - - public MCShapedRecipe setIngredient(char key, MCMaterialData data); - - public MCShapedRecipe setShape(String[] shape); + Map getIngredientMap(); + String[] getShape(); + void setIngredient(char key, MCItemStack ingredient); + void setIngredient(char key, MCRecipeChoice ingredient); + void setIngredient(char key, MCMaterial ingredient); + void setShape(String[] shape); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCShapelessRecipe.java b/src/main/java/com/laytonsmith/abstraction/MCShapelessRecipe.java index 7b284cd8c6..f0c6618efa 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCShapelessRecipe.java +++ b/src/main/java/com/laytonsmith/abstraction/MCShapelessRecipe.java @@ -1,20 +1,12 @@ package com.laytonsmith.abstraction; +import com.laytonsmith.abstraction.blocks.MCMaterial; + import java.util.List; -/** - * - * @author jb_aero - */ public interface MCShapelessRecipe extends MCRecipe { - - public MCShapelessRecipe addIngredient(MCItemStack ingredient); - - public MCShapelessRecipe addIngredient(int type, int data, int amount); - - public List getIngredients(); - - public MCShapelessRecipe removeIngredient(MCItemStack ingredient); - - public MCShapelessRecipe removeIngredient(int type, int data, int amount); + void addIngredient(MCMaterial ingredient, int amount); + void addIngredient(MCMaterial ingredient); + void addIngredient(MCRecipeChoice choice); + List getIngredients(); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCSkullMeta.java b/src/main/java/com/laytonsmith/abstraction/MCSkullMeta.java index 80ad3793cf..1c83af5e86 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCSkullMeta.java +++ b/src/main/java/com/laytonsmith/abstraction/MCSkullMeta.java @@ -1,13 +1,23 @@ package com.laytonsmith.abstraction; -/** - * - * @author jb_aero - */ public interface MCSkullMeta extends MCItemMeta { - - public boolean hasOwner(); - public String getOwner(); - public boolean setOwner(String owner); - + + boolean hasOwner(); + + String getOwner(); + + MCOfflinePlayer getOwningPlayer(); + + boolean setOwner(String owner); + + void setOwningPlayer(MCOfflinePlayer player); + + MCPlayerProfile getProfile(); + + void setProfile(MCPlayerProfile profile); + + String getNoteBlockSound(); + + void setNoteBlockSound(String noteBlockSound); + } diff --git a/src/main/java/com/laytonsmith/abstraction/MCSmithingInventory.java b/src/main/java/com/laytonsmith/abstraction/MCSmithingInventory.java new file mode 100644 index 0000000000..4c09d75868 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCSmithingInventory.java @@ -0,0 +1,14 @@ +package com.laytonsmith.abstraction; + +public interface MCSmithingInventory extends MCInventory { + MCItemStack getInputEquipment(); + MCItemStack getInputMaterial(); + MCItemStack getInputTemplate(); + MCRecipe getRecipe(); + MCItemStack getResult(); + + void setInputEquipment(MCItemStack stack); + void setInputMaterial(MCItemStack stack); + void setInputTemplate(MCItemStack stack); + void setResult(MCItemStack stack); +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCSmithingRecipe.java b/src/main/java/com/laytonsmith/abstraction/MCSmithingRecipe.java new file mode 100644 index 0000000000..48d9176ff4 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCSmithingRecipe.java @@ -0,0 +1,8 @@ +package com.laytonsmith.abstraction; + +import com.laytonsmith.abstraction.blocks.MCMaterial; + +public interface MCSmithingRecipe extends MCRecipe { + MCMaterial[] getBase(); + MCMaterial[] getAddition(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCSnowball.java b/src/main/java/com/laytonsmith/abstraction/MCSnowball.java deleted file mode 100644 index 5458237458..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/MCSnowball.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.laytonsmith.abstraction; - -public interface MCSnowball extends MCProjectile { - -} diff --git a/src/main/java/com/laytonsmith/abstraction/MCStonecuttingRecipe.java b/src/main/java/com/laytonsmith/abstraction/MCStonecuttingRecipe.java new file mode 100644 index 0000000000..dbd5f39e2f --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCStonecuttingRecipe.java @@ -0,0 +1,10 @@ +package com.laytonsmith.abstraction; + +import com.laytonsmith.abstraction.blocks.MCMaterial; + +public interface MCStonecuttingRecipe extends MCRecipe { + MCRecipeChoice getInput(); + void setInput(MCItemStack input); + void setInput(MCMaterial mat); + void setInput(MCRecipeChoice choice); +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCSuspiciousStewMeta.java b/src/main/java/com/laytonsmith/abstraction/MCSuspiciousStewMeta.java new file mode 100644 index 0000000000..8306c4fb57 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCSuspiciousStewMeta.java @@ -0,0 +1,15 @@ +package com.laytonsmith.abstraction; + +import com.laytonsmith.abstraction.MCLivingEntity.MCEffect; +import com.laytonsmith.abstraction.enums.MCPotionEffectType; +import com.laytonsmith.core.constructs.Target; + +import java.util.List; + +public interface MCSuspiciousStewMeta extends MCItemMeta { + + boolean addCustomEffect(MCPotionEffectType type, int strength, int ticks, boolean ambient, boolean particles, boolean icon, boolean force, Target t); + + List getCustomEffects(); + +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCTNT.java b/src/main/java/com/laytonsmith/abstraction/MCTNT.java deleted file mode 100644 index 2f413e60c5..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/MCTNT.java +++ /dev/null @@ -1,12 +0,0 @@ - -package com.laytonsmith.abstraction; - -/** - * - * - */ -public interface MCTNT extends MCEntity { - MCEntity getSource(); - int getFuseTicks(); - void setFuseTicks(int ticks); -} diff --git a/src/main/java/com/laytonsmith/abstraction/MCTagContainer.java b/src/main/java/com/laytonsmith/abstraction/MCTagContainer.java new file mode 100644 index 0000000000..a28f41a1d1 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCTagContainer.java @@ -0,0 +1,65 @@ +package com.laytonsmith.abstraction; + +import com.laytonsmith.abstraction.enums.MCTagType; + +import java.util.Set; + +/** + * Minecraft NBT containers that can be used to read and modify tags in supported game objects. + * This includes item meta, entities, block entities, chunks, worlds, etc. + */ +public interface MCTagContainer extends AbstractionObject { + + /** + * Returns whether the tag container does not contain any tags. + * @return whether container is empty + */ + boolean isEmpty(); + + /** + * Gets a set of namespaced keys for each tag that exists in this container. (e.g. "namespace:key") + * @return a set of keys + */ + Set getKeys(); + + /** + * Returns the tag type with the given key. + * MCTagType can be used to convert tags to and from MethodScript constructs. + * Returns null if a tag with that key does not exist. + * @param key the tag key + * @return the type for the tag + */ + MCTagType getType(MCNamespacedKey key); + + /** + * Returns the tag value with the given key and tag type. + * Returns null if a tag with that key and type does not exist. + * @param key the tag key + * @param type the tag type + * @return the value for the tag + */ + Object get(MCNamespacedKey key, MCTagType type); + + /** + * Sets the tag value with the given key and tag type. + * Throws an IllegalArgumentException if the type and value do not match. + * @param key the tag key + * @param type the tag type + * @param value the tag value + */ + void set(MCNamespacedKey key, MCTagType type, Object value); + + /** + * Deletes the tag with the given key from this container. + * @param key the tag key + */ + void remove(MCNamespacedKey key); + + /** + * Creates a new tag container from this container context. + * This can then be used to nest a tag container with the {@link #set} method. + * @return a new tag container + */ + MCTagContainer newContainer(); + +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCTameable.java b/src/main/java/com/laytonsmith/abstraction/MCTameable.java deleted file mode 100644 index 5b12fd237e..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/MCTameable.java +++ /dev/null @@ -1,16 +0,0 @@ - -package com.laytonsmith.abstraction; - -/** - * - * - */ -public interface MCTameable extends MCAgeable { - public boolean isTamed(); - - public void setTamed(boolean bln); - - public MCAnimalTamer getOwner(); - - public void setOwner(MCAnimalTamer at); -} diff --git a/src/main/java/com/laytonsmith/abstraction/MCTeam.java b/src/main/java/com/laytonsmith/abstraction/MCTeam.java index 0104252f8b..afd0e6434e 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCTeam.java +++ b/src/main/java/com/laytonsmith/abstraction/MCTeam.java @@ -1,28 +1,54 @@ package com.laytonsmith.abstraction; +import com.laytonsmith.abstraction.enums.MCChatColor; +import com.laytonsmith.abstraction.enums.MCOption; +import com.laytonsmith.abstraction.enums.MCOptionStatus; + import java.util.Set; -/** - * - * @author jb_aero - */ public interface MCTeam { - public void addPlayer(MCOfflinePlayer player); - public boolean allowFriendlyFire(); - public boolean canSeeFriendlyInvisibles(); - public String getDisplayName(); - public String getName(); - public Set getPlayers(); - public String getPrefix(); - public MCScoreboard getScoreboard(); - public int getSize(); - public String getSuffix(); - public boolean hasPlayer(MCOfflinePlayer player); - public boolean removePlayer(MCOfflinePlayer player); - public void setAllowFriendlyFire(boolean enabled); - public void setCanSeeFriendlyInvisibles(boolean enabled); - public void setDisplayName(String displayName); - public void setPrefix(String prefix); - public void setSuffix(String suffix); - public void unregister(); + + void addEntry(String entry); + + boolean allowFriendlyFire(); + + boolean canSeeFriendlyInvisibles(); + + String getDisplayName(); + + String getName(); + + MCOptionStatus getOption(MCOption option); + + Set getEntries(); + + String getPrefix(); + + MCScoreboard getScoreboard(); + + int getSize(); + + String getSuffix(); + + MCChatColor getColor(); + + boolean hasEntry(String entry); + + boolean removeEntry(String entry); + + void setAllowFriendlyFire(boolean enabled); + + void setCanSeeFriendlyInvisibles(boolean enabled); + + void setDisplayName(String displayName); + + void setOption(MCOption option, MCOptionStatus status); + + void setPrefix(String prefix); + + void setSuffix(String suffix); + + void setColor(MCChatColor color); + + void unregister(); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCTravelAgent.java b/src/main/java/com/laytonsmith/abstraction/MCTravelAgent.java deleted file mode 100644 index e16ae4eee6..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/MCTravelAgent.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.laytonsmith.abstraction; - -/** - * - * @author MariuszT - */ -public interface MCTravelAgent extends AbstractionObject { - - public boolean createPortal(MCLocation location); - public MCLocation findOrCreate(MCLocation location); - public MCLocation findPortal(MCLocation location); - - public boolean getCanCreatePortal(); - public void setCanCreatePortal(boolean create); - - public int getCreationRadius(); - public MCTravelAgent setCreationRadius(int radius); - - public int getSearchRadius(); - public MCTravelAgent setSearchRadius(int radius); -} diff --git a/src/main/java/com/laytonsmith/abstraction/MCTropicalFishBucketMeta.java b/src/main/java/com/laytonsmith/abstraction/MCTropicalFishBucketMeta.java new file mode 100644 index 0000000000..bc42b59bdf --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCTropicalFishBucketMeta.java @@ -0,0 +1,16 @@ +package com.laytonsmith.abstraction; + +import com.laytonsmith.abstraction.entities.MCTropicalFish; +import com.laytonsmith.abstraction.enums.MCDyeColor; + +public interface MCTropicalFishBucketMeta extends MCItemMeta { + + MCDyeColor getPatternColor(); + void setPatternColor(MCDyeColor color); + MCDyeColor getBodyColor(); + void setBodyColor(MCDyeColor color); + MCTropicalFish.MCPattern getPattern(); + void setPattern(MCTropicalFish.MCPattern pattern); + boolean hasVariant(); + +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCVehicle.java b/src/main/java/com/laytonsmith/abstraction/MCVehicle.java deleted file mode 100644 index a1b9f0466d..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/MCVehicle.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.laytonsmith.abstraction; - -/** - * - * @author jb_aero - */ -public interface MCVehicle extends MCEntity { - // Look at ALL the functions! -} diff --git a/src/main/java/com/laytonsmith/abstraction/MCVirtualInventoryHolder.java b/src/main/java/com/laytonsmith/abstraction/MCVirtualInventoryHolder.java new file mode 100644 index 0000000000..1128687fe1 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCVirtualInventoryHolder.java @@ -0,0 +1,5 @@ +package com.laytonsmith.abstraction; + +public interface MCVirtualInventoryHolder extends MCInventoryHolder { + String getID(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCWorld.java b/src/main/java/com/laytonsmith/abstraction/MCWorld.java index 72a6afa5c2..4818a345e4 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCWorld.java +++ b/src/main/java/com/laytonsmith/abstraction/MCWorld.java @@ -1,108 +1,182 @@ - - package com.laytonsmith.abstraction; import com.laytonsmith.abstraction.blocks.MCBlock; -import com.laytonsmith.abstraction.blocks.MCFallingBlock; +import com.laytonsmith.abstraction.blocks.MCBlockData; +import com.laytonsmith.abstraction.entities.MCFallingBlock; +import com.laytonsmith.abstraction.entities.MCFirework; +import com.laytonsmith.abstraction.entities.MCItem; +import com.laytonsmith.abstraction.entities.MCLightningStrike; import com.laytonsmith.abstraction.enums.MCBiomeType; import com.laytonsmith.abstraction.enums.MCDifficulty; import com.laytonsmith.abstraction.enums.MCEffect; import com.laytonsmith.abstraction.enums.MCEntityType; -import com.laytonsmith.abstraction.enums.MCGameRule; -import com.laytonsmith.abstraction.enums.MCMobs; +import com.laytonsmith.abstraction.enums.MCParticle; import com.laytonsmith.abstraction.enums.MCSound; +import com.laytonsmith.abstraction.enums.MCSoundCategory; import com.laytonsmith.abstraction.enums.MCTreeType; import com.laytonsmith.abstraction.enums.MCWorldEnvironment; import com.laytonsmith.abstraction.enums.MCWorldType; -import com.laytonsmith.core.constructs.CArray; -import com.laytonsmith.core.constructs.Target; +import com.laytonsmith.core.constructs.CClosure; + import java.util.List; +import java.util.UUID; -/** - * - * - */ public interface MCWorld extends MCMetadatable { - public List getPlayers(); - public List getEntities(); - public List getLivingEntities(); - public String getName(); - public long getSeed(); - public MCWorldEnvironment getEnvironment(); - public String getGenerator(); - public MCWorldType getWorldType(); + List getPlayers(); + + List getEntities(); + + List getLivingEntities(); + + String getName(); + + UUID getUniqueID(); + + long getSeed(); + + MCWorldEnvironment getEnvironment(); + + String getGenerator(); + + MCWorldType getWorldType(); + + int getSeaLevel(); + + int getMaxHeight(); + + int getMinHeight(); + + MCDifficulty getDifficulty(); + + void setDifficulty(MCDifficulty difficulty); + + boolean getPVP(); + + void setPVP(boolean pvp); + + String[] getGameRules(); + + boolean isGameRule(String gameRule); + + Object getGameRuleValue(String gameRule); + + boolean setGameRuleValue(String gameRule, Object value); + + MCWorldBorder getWorldBorder(); + + MCBlock getBlockAt(int x, int y, int z); + + MCChunk getChunkAt(int x, int z); + + MCChunk getChunkAt(MCBlock b); - public MCDifficulty getDifficulty(); - public void setDifficulty(MCDifficulty difficulty); - public boolean getPVP(); - public void setPVP(boolean pvp); - public boolean getGameRuleValue(MCGameRule gameRule); - public void setGameRuleValue(MCGameRule gameRule, boolean value); + MCChunk getChunkAt(MCLocation l); - public MCBlock getBlockAt(int x, int y, int z); - public MCChunk getChunkAt(int x, int z); - public MCChunk getChunkAt(MCBlock b); - public MCChunk getChunkAt(MCLocation l); - public MCChunk[] getLoadedChunks(); + MCChunk[] getLoadedChunks(); - public boolean regenerateChunk(int x, int y); + boolean isChunkLoaded(int x, int z); - public MCEntity spawn(MCLocation l, Class mobType); + boolean regenerateChunk(int x, int y); - public MCEntity spawn(MCLocation l, MCEntityType entType); + MCEntity spawn(MCLocation l, Class mobType); - public boolean generateTree(MCLocation l, MCTreeType treeType); + MCEntity spawn(MCLocation l, MCEntityType entType); - public void playEffect(MCLocation l, MCEffect mCEffect, int e, int data); + MCEntity spawn(MCLocation l, MCEntityType entType, final CClosure closure); - public void playSound(MCLocation l, MCSound sound, float volume, float pitch); + MCEntity spawn(MCLocation l, MCEntityType.MCVanillaEntityType entityType); - public void playSound(MCLocation l, String sound, float volume, float pitch); + boolean generateTree(MCLocation l, MCTreeType treeType); - public MCItem dropItemNaturally(MCLocation l, MCItemStack is); + void playEffect(MCLocation l, MCEffect mCEffect, int data, int radius); - public MCItem dropItem(MCLocation l, MCItemStack is); + void playEffect(MCLocation l, MCEffect mCEffect, Object data, int radius); - public MCLightningStrike strikeLightning(MCLocation GetLocation); + void spawnParticle(MCLocation l, MCParticle pa, int count, double offsetX, double offsetY, double offsetZ, double velocity, boolean force, Object data); - public MCLightningStrike strikeLightningEffect(MCLocation GetLocation); + void playSound(MCLocation l, MCSound sound, MCSoundCategory category, float volume, float pitch, Long seed); - public void setStorm(boolean b); - public void setThundering(boolean b); - public void setWeatherDuration(int time); - public void setThunderDuration(int time); - public boolean isStorming(); - public boolean isThundering(); + void playSound(MCEntity ent, MCSound sound, MCSoundCategory category, float volume, float pitch, Long seed); - public MCLocation getSpawnLocation(); - public void setSpawnLocation(int x, int y, int z); + void playSound(MCEntity ent, String sound, MCSoundCategory category, float volume, float pitch, Long seed); - public void refreshChunk(int x, int z); - public void loadChunk(int x, int z); - public void unloadChunk(int x, int z); + void playSound(MCLocation l, String sound, MCSoundCategory category, float volume, float pitch, Long seed); - public void setTime(long time); + MCItem dropItemNaturally(MCLocation l, MCItemStack is); - public long getTime(); + MCItem dropItem(MCLocation l, MCItemStack is); - public CArray spawnMob(MCMobs name, String subClass, int qty, MCLocation location, Target t); + MCLightningStrike strikeLightning(MCLocation location); - public MCFallingBlock spawnFallingBlock(MCLocation loc, int type, byte data); + MCLightningStrike strikeLightningEffect(MCLocation location); - public MCBiomeType getBiome(int x, int z); + void setStorm(boolean b); - public void setBiome(int x, int z, MCBiomeType type); + void setThundering(boolean b); - public MCBlock getHighestBlockAt(int x, int z); + void setWeatherDuration(int time); - public void explosion(double x, double y, double z, float size, boolean safe); + void setThunderDuration(int time); + + void setClearWeatherDuration(int time); + + boolean isStorming(); + + boolean isThundering(); + + MCLocation getSpawnLocation(); + + void setSpawnLocation(int x, int y, int z); + + void setSpawnLocation(MCLocation location); + + void refreshChunk(int x, int z); + + void loadChunk(int x, int z); + + void unloadChunk(int x, int z); + + boolean isChunkForceLoaded(int x, int z); + + void setChunkForceLoaded(int x, int z, boolean forced); + + MCChunk[] getForceLoadedChunks(); + + void setTime(long time); + + long getTime(); + + void setFullTime(long time); + + long getFullTime(); + + MCFallingBlock spawnFallingBlock(MCLocation loc, MCBlockData data); + + MCFirework launchFirework(MCLocation l, int strength, List effects); + + MCBiomeType getBiome(MCLocation location); + + void setBiome(int x, int z, MCBiomeType type); + + void setBiome(MCLocation location, MCBiomeType type); + + MCBlock getHighestBlockAt(int x, int z); + + boolean explosion(MCLocation location, float size, boolean safe, boolean fire, MCEntity source); /** * This method performs some check on the world to ensure it exists. + * * @return */ - public boolean exists(); + boolean exists(); + + boolean isAutoSave(); + + void setAutoSave(boolean autoSave); + + void save(); - public void save(); + void setKeepSpawnInMemory(boolean keepLoaded); } diff --git a/src/main/java/com/laytonsmith/abstraction/MCWorldBorder.java b/src/main/java/com/laytonsmith/abstraction/MCWorldBorder.java new file mode 100644 index 0000000000..369f5cb140 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/MCWorldBorder.java @@ -0,0 +1,32 @@ +package com.laytonsmith.abstraction; + +public interface MCWorldBorder extends AbstractionObject { + + void reset(); + + double getSize(); + + void setSize(double size); + + void setSize(double size, int seconds); + + MCLocation getCenter(); + + void setCenter(MCLocation location); + + double getDamageBuffer(); + + void setDamageBuffer(double blocks); + + double getDamageAmount(); + + void setDamageAmount(double damage); + + int getWarningTime(); + + void setWarningTime(int seconds); + + int getWarningDistance(); + + void setWarningDistance(int distance); +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCWorldCreator.java b/src/main/java/com/laytonsmith/abstraction/MCWorldCreator.java index bde589cc99..6e4b14ecf4 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCWorldCreator.java +++ b/src/main/java/com/laytonsmith/abstraction/MCWorldCreator.java @@ -1,18 +1,19 @@ - package com.laytonsmith.abstraction; import com.laytonsmith.abstraction.enums.MCWorldEnvironment; import com.laytonsmith.abstraction.enums.MCWorldType; -/** - * - * - */ public interface MCWorldCreator { + MCWorld createWorld(); + MCWorldCreator type(MCWorldType type); + MCWorldCreator environment(MCWorldEnvironment environment); + MCWorldCreator seed(long seed); + MCWorldCreator generator(String generator); + MCWorldCreator copy(MCWorld toCopy); } diff --git a/src/main/java/com/laytonsmith/abstraction/StaticLayer.java b/src/main/java/com/laytonsmith/abstraction/StaticLayer.java index 86aeff1feb..f81b6668f2 100644 --- a/src/main/java/com/laytonsmith/abstraction/StaticLayer.java +++ b/src/main/java/com/laytonsmith/abstraction/StaticLayer.java @@ -1,151 +1,153 @@ package com.laytonsmith.abstraction; import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery; +import com.laytonsmith.PureUtilities.Common.StreamUtils; import com.laytonsmith.PureUtilities.DaemonManager; import com.laytonsmith.abstraction.blocks.MCMaterial; +import com.laytonsmith.abstraction.entities.MCTransformation; +import com.laytonsmith.abstraction.enums.MCPotionType; import com.laytonsmith.abstraction.enums.MCRecipeType; import com.laytonsmith.annotations.convert; import com.laytonsmith.commandhelper.CommandHelperPlugin; -import java.util.Set; +import java.util.Set; +import org.joml.Quaternionf; +import org.joml.Vector3f; /** * Unfortunately some methods just can't be overridden. - * + * */ public final class StaticLayer { - - private StaticLayer(){} - //Do not rename this field, it is used reflectively in testing - private static Convertor convertor = null; - static{ - InitConvertor(); - } - - private static synchronized void InitConvertor(){ - Set classes = ClassDiscovery.getDefaultInstance().loadClassesWithAnnotation(convert.class); - for(Class c : classes){ - if(!Convertor.class.isAssignableFrom(c)){ - System.err.println("The Convertor " + c.getSimpleName() + " doesn't implement Convertor!"); - } - convert convert = (convert)c.getAnnotation(convert.class); - if(convert.type() == Implementation.GetServerType()){ - //This is what we're looking for, instatiate it. - try{ - if(convertor != null){ - //Uh... There are more than one implementations for this server type - System.out.println("More than one Convertor for this server type was detected!"); - } - convertor = (Convertor) c.newInstance(); - //At this point we are all set - } catch(Exception e){ - System.err.println("Tried to instantiate the Convertor, but couldn't!"); - } - } - } - if(convertor == null){ - System.err.println("Could not find a suitable convertor! You will experience serious issues with this plugin."); - } - } - - public static MCLocation GetLocation(MCWorld w, double x, double y, double z, float yaw, float pitch) { - return convertor.GetLocation(w, x, y, z, yaw, pitch); - } - - public static Class GetServerEventMixin() { - return convertor.GetServerEventMixin(); - } - - public static MCLocation GetLocation(MCWorld w, double x, double y, double z){ - return GetLocation(w, x, y, z, 0, 0); - } - - public static MCItemStack GetItemStack(int type, int qty) { - return convertor.GetItemStack(type, qty); - } - - public static MCItemStack GetItemStack(int type, int data, int qty){ - return convertor.GetItemStack(type, data, qty); - } - - public static MCServer GetServer(){ - return convertor.GetServer(); - } - - public static MCEnchantment GetEnchantmentByName(String name){ - return convertor.GetEnchantmentByName(name); - } - + + private StaticLayer() { + } + //Do not rename this field, it is used reflectively in testing + private static Convertor convertor = null; + + static { + InitConvertor(); + } + + private static synchronized void InitConvertor() { + Set> classes = ClassDiscovery.getDefaultInstance().loadClassesWithAnnotation(convert.class); + for(Class c : classes) { + if(!Convertor.class.isAssignableFrom(c)) { + StreamUtils.GetSystemErr().println("The Convertor " + c.getSimpleName() + " doesn't implement Convertor!"); + } + convert convert = c.getAnnotation(convert.class); + if(convert.type() == Implementation.GetServerType()) { + //This is what we're looking for, instatiate it. + try { + if(convertor != null) { + //Uh... There are more than one implementations for this server type + StreamUtils.GetSystemErr().println("More than one Convertor for this server type was detected!"); + } + convertor = (Convertor) c.newInstance(); + //At this point we are all set + } catch (Exception e) { + StreamUtils.GetSystemErr().println("Tried to instantiate the Convertor, but couldn't!"); + } + } + } + if(convertor == null) { + StreamUtils.GetSystemErr().println("Could not find a suitable convertor! You will experience serious issues with this plugin."); + } + } + + public static MCLocation GetLocation(MCWorld w, double x, double y, double z, float yaw, float pitch) { + return convertor.GetLocation(w, x, y, z, yaw, pitch); + } + + public static MCLocation GetLocation(MCWorld w, double x, double y, double z) { + return GetLocation(w, x, y, z, 0, 0); + } + + public static Class GetServerEventMixin() { + return convertor.GetServerEventMixin(); + } + + public static MCMaterial GetMaterialFromLegacy(int type, int data) { + return convertor.GetMaterialFromLegacy(type, data); + } + + public static MCMaterial GetMaterialFromLegacy(String name, int data) { + return convertor.GetMaterialFromLegacy(name, data); + } + + public static MCItemStack GetItemStack(String type, int qty) { + return convertor.GetItemStack(type, qty); + } + + public static MCItemStack GetItemStack(MCMaterial type, int qty) { + return convertor.GetItemStack(type, qty); + } + + public static MCPotionData GetPotionData(MCPotionType type, boolean extended, boolean upgraded) { + return convertor.GetPotionData(type, extended, upgraded); + } + + public static MCServer GetServer() { + return convertor.GetServer(); + } + + public static MCMaterial[] GetMaterialValues() { + return convertor.GetMaterialValues(); + } + public static MCMaterial GetMaterial(String name) { return convertor.GetMaterial(name); } - - public static MCEnchantment[] GetEnchantmentValues(){ - return convertor.GetEnchantmentValues(); - } - public static void Startup(CommandHelperPlugin chp) { - convertor.Startup(chp); - } + public static void Startup(CommandHelperPlugin chp) { + convertor.Startup(chp); + } public static MCMetadataValue GetMetadataValue(Object value, MCPlugin plugin) { return convertor.GetMetadataValue(value, plugin); } - /** - * Returns the data value of the specified material name, or -1 if none was found. - * @param materialName - * @return - */ - public static int LookupItemId(String materialName){ - return convertor.LookupItemId(materialName); - } - - /** - * Returns the name of the material, given the material's ID. - * @param id - * @return - */ - public static String LookupMaterialName(int id){ - return convertor.LookupMaterialName(id); - } - - /** - * Adds a runnable to the main thread, if required by this platform, - * if a multithreaded user code would be dangerous. - * @param ms - * @param r - * @return - */ - public static int SetFutureRunnable(DaemonManager dm, long ms, Runnable r){ - return convertor.SetFutureRunnable(dm, ms, r); - } - - public static int SetFutureRepeater(DaemonManager dm, long ms, long initialDelay, Runnable r){ - return convertor.SetFutureRepeater(dm, ms, initialDelay, r); - } - - public static void ClearAllRunnables() { - convertor.ClearAllRunnables(); - } - - public static void ClearFutureRunnable(int id){ - convertor.ClearFutureRunnable(id); - } - - /** - * Given an entity, returns the more specific entity type, by creating a new more - * specific type based on the actual type of the underlying object contained by the - * more generic type. - * @param e - * @return - */ - public static MCEntity GetCorrectEntity(MCEntity e) { - return convertor.GetCorrectEntity(e); - } - - public static MCRecipe GetNewRecipe(MCRecipeType type, MCItemStack result) { - return convertor.GetNewRecipe(type, result); + /** + * Adds a runnable to the main thread, if required by this platform, if a multithreaded user code would be + * dangerous. + * + * @param ms + * @param r + * @return + */ + public static int SetFutureRunnable(DaemonManager dm, long ms, Runnable r) { + return convertor.SetFutureRunnable(dm, ms, r); + } + + public static int SetFutureRepeater(DaemonManager dm, long ms, long initialDelay, Runnable r) { + return convertor.SetFutureRepeater(dm, ms, initialDelay, r); + } + + public static void ClearAllRunnables() { + convertor.ClearAllRunnables(); + } + + public static void ClearFutureRunnable(int id) { + convertor.ClearFutureRunnable(id); + } + + /** + * Given an entity, returns the more specific entity type, by creating a new more specific type based on the actual + * type of the underlying object contained by the more generic type. + * + * @param e + * @return + */ + public static MCEntity GetCorrectEntity(MCEntity e) { + return convertor.GetCorrectEntity(e); + } + + public static MCRecipe GetNewRecipe(String key, MCRecipeType type, MCItemStack result) { + return convertor.GetNewRecipe(key, type, result); + } + + public static MCTransformation GetTransformation(Quaternionf leftRotation, Quaternionf rightRotation, Vector3f scale, Vector3f translation) { + return convertor.GetTransformation(leftRotation, rightRotation, scale, translation); } public static String GetPluginName() { @@ -155,8 +157,12 @@ public static String GetPluginName() { public static MCPlugin GetPlugin() { return convertor.GetPlugin(); } - - public static synchronized Convertor GetConvertor(){ + + public static boolean IsMainThread() { + return convertor.IsMainThread(); + } + + public static synchronized Convertor GetConvertor() { return convertor; } } diff --git a/src/main/java/com/laytonsmith/abstraction/Velocity.java b/src/main/java/com/laytonsmith/abstraction/Velocity.java deleted file mode 100644 index 7024da2708..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/Velocity.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.laytonsmith.abstraction; - -/** - * - * - */ -public class Velocity { - public double magnitude; - public double x; - public double y; - public double z; - - public Velocity(double x, double y, double z) { - this(Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2) + Math.pow(z, 2)), x, y, z); - } - - public Velocity(double magnitude, double x, double y, double z) { - this.magnitude = magnitude; - this.x = x; - this.y = y; - this.z = z; - } - - public Velocity add(Velocity vec) { - this.x += vec.x; - this.y += vec.y; - this.z += vec.z; - return this; - } - - public Velocity multiply(Velocity vec) { - this.x *= vec.x; - this.y *= vec.y; - this.z *= vec.z; - return this; - } - - public Velocity multiply(double m) { - this.x *= m; - this.y *= m; - this.z *= m; - return this; - } - - public Velocity normalize() { - double length = length(); - this.x /= length; - this.y /= length; - this.z /= length; - return this; - } - - public Velocity subtract(Velocity vec) { - this.x -= vec.x; - this.y -= vec.y; - this.z -= vec.z; - return this; - } - - public double length() { - return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2) + Math.pow(this.z, 2)); - } -} diff --git a/src/main/java/com/laytonsmith/abstraction/blocks/MCBanner.java b/src/main/java/com/laytonsmith/abstraction/blocks/MCBanner.java new file mode 100644 index 0000000000..0efdf7254d --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/blocks/MCBanner.java @@ -0,0 +1,15 @@ +package com.laytonsmith.abstraction.blocks; + +import com.laytonsmith.abstraction.MCPattern; +import com.laytonsmith.abstraction.enums.MCDyeColor; + +import java.util.List; + +public interface MCBanner extends MCBlockState { + MCDyeColor getBaseColor(); + void setBaseColor(MCDyeColor color); + int numberOfPatterns(); + List getPatterns(); + void addPattern(MCPattern pattern); + void clearPatterns(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/blocks/MCBeacon.java b/src/main/java/com/laytonsmith/abstraction/blocks/MCBeacon.java new file mode 100644 index 0000000000..1a8e860ea8 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/blocks/MCBeacon.java @@ -0,0 +1,14 @@ +package com.laytonsmith.abstraction.blocks; + +import java.util.Collection; + +import com.laytonsmith.abstraction.MCLivingEntity; + +public interface MCBeacon extends MCBlockState { + Collection getEntitiesInRange(); +// MCPotionEffect getPrimaryEffect(); +// MCPotionEffect getSecondaryEffect(); + int getTier(); +// void setPrimaryEffect(MCPotionEffect effect); +// void setSecondaryEffect(MCPotionEffect effect); +} diff --git a/src/main/java/com/laytonsmith/abstraction/blocks/MCBeehive.java b/src/main/java/com/laytonsmith/abstraction/blocks/MCBeehive.java new file mode 100644 index 0000000000..d62f4eda31 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/blocks/MCBeehive.java @@ -0,0 +1,10 @@ +package com.laytonsmith.abstraction.blocks; + +import com.laytonsmith.abstraction.MCLocation; + +public interface MCBeehive extends MCBlockState { + MCLocation getFlowerLocation(); + void setFlowerLocation(MCLocation loc); + void addBees(int count); + int getEntityCount(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/blocks/MCBlock.java b/src/main/java/com/laytonsmith/abstraction/blocks/MCBlock.java index fc5d3e1d28..3f049a1a40 100644 --- a/src/main/java/com/laytonsmith/abstraction/blocks/MCBlock.java +++ b/src/main/java/com/laytonsmith/abstraction/blocks/MCBlock.java @@ -1,74 +1,87 @@ - - package com.laytonsmith.abstraction.blocks; import com.laytonsmith.abstraction.MCItemStack; import com.laytonsmith.abstraction.MCLocation; import com.laytonsmith.abstraction.MCMetadatable; import com.laytonsmith.abstraction.MCWorld; + import java.util.Collection; -/** - * - * - */ public interface MCBlock extends MCMetadatable { - public int getTypeId(); + MCMaterial getType(); + + void setType(MCMaterial mat); + + void setType(MCMaterial mat, boolean physics); + + MCBlockData getBlockData(); + + void setBlockData(MCBlockData data, boolean physics); + + MCBlockState getState(); + + MCWorld getWorld(); + + int getX(); + + int getY(); + + int getZ(); + + MCLocation getLocation(); + + MCSign getSign(); + + boolean isSign(); + + MCCommandBlock getCommandBlock(); + + boolean isCommandBlock(); + + MCDispenser getDispenser(); + + boolean isDispenser(); + + boolean isSolid(); + + boolean isFlammable(); - public byte getData(); + boolean isTransparent(); - public void setTypeId(int idata); + boolean isOccluding(); - public void setData(byte imeta); + boolean isBurnable(); - public void setTypeAndData(int type, byte data, boolean physics); + boolean isPassable(); - public MCBlockState getState(); + boolean isBanner(); - public MCMaterial getType(); + Collection getDrops(); - public MCWorld getWorld(); + Collection getDrops(MCItemStack tool); - public int getX(); + double getTemperature(); - public int getY(); + int getLightLevel(); - public int getZ(); + int getLightFromSky(); - public MCSign getSign(); - - public MCLocation getLocation(); + int getLightFromBlocks(); - public boolean isSign(); - - public MCCommandBlock getCommandBlock(); - - public boolean isCommandBlock(); + int getBlockPower(); - public boolean isNull(); - - public boolean isSolid(); - - public boolean isFlammable(); - - public boolean isTransparent(); - - public boolean isOccluding(); - - public boolean isBurnable(); + boolean isBlockPowered(); - public Collection getDrops(); + boolean isBlockIndirectlyPowered(); - public Collection getDrops(MCItemStack tool); + MCBlock getRelative(MCBlockFace face); - public int getLightLevel(); + MCBlockFace getFace(MCBlock get); - public int getBlockPower(); - - public boolean isBlockPowered(); + boolean isEmpty(); - public MCBlock getRelative(MCBlockFace face); + boolean applyBoneMeal(); - public MCBlockFace getFace(MCBlock get); + boolean breakNaturally(MCItemStack item); } diff --git a/src/main/java/com/laytonsmith/abstraction/blocks/MCBlockData.java b/src/main/java/com/laytonsmith/abstraction/blocks/MCBlockData.java new file mode 100644 index 0000000000..dcc59772dd --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/blocks/MCBlockData.java @@ -0,0 +1,8 @@ +package com.laytonsmith.abstraction.blocks; + +import com.laytonsmith.abstraction.AbstractionObject; + +public interface MCBlockData extends AbstractionObject { + MCMaterial getMaterial(); + String getAsString(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/blocks/MCBlockFace.java b/src/main/java/com/laytonsmith/abstraction/blocks/MCBlockFace.java index b09a95784f..6eb6610b46 100644 --- a/src/main/java/com/laytonsmith/abstraction/blocks/MCBlockFace.java +++ b/src/main/java/com/laytonsmith/abstraction/blocks/MCBlockFace.java @@ -2,135 +2,131 @@ import com.laytonsmith.annotations.MEnum; -/** - * - * - */ -@MEnum("BlockFace") +@MEnum("com.commandhelper.BlockFace") public enum MCBlockFace { - NORTH(0, 0, -1), - EAST(1, 0, 0), - SOUTH(0, 0, 1), - WEST(-1, 0, 0), - UP(0, 1, 0), - DOWN(0, -1, 0), - NORTH_EAST(NORTH, EAST), - NORTH_WEST(NORTH, WEST), - SOUTH_EAST(SOUTH, EAST), - SOUTH_WEST(SOUTH, WEST), - WEST_NORTH_WEST(WEST, NORTH_WEST), - NORTH_NORTH_WEST(NORTH, NORTH_WEST), - NORTH_NORTH_EAST(NORTH, NORTH_EAST), - EAST_NORTH_EAST(EAST, NORTH_EAST), - EAST_SOUTH_EAST(EAST, SOUTH_EAST), - SOUTH_SOUTH_EAST(SOUTH, SOUTH_EAST), - SOUTH_SOUTH_WEST(SOUTH, SOUTH_WEST), - WEST_SOUTH_WEST(WEST, SOUTH_WEST), - SELF(0, 0, 0); - - private final int modX; - private final int modY; - private final int modZ; - - private MCBlockFace(final int modX, final int modY, final int modZ) { - this.modX = modX; - this.modY = modY; - this.modZ = modZ; - } - - private MCBlockFace(final MCBlockFace face1, final MCBlockFace face2) { - this.modX = face1.getModX() + face2.getModX(); - this.modY = face1.getModY() + face2.getModY(); - this.modZ = face1.getModZ() + face2.getModZ(); - } - - /** - * Get the amount of X-coordinates to modify to get the represented block - * - * @return Amount of X-coordinates to modify - */ - public int getModX() { - return modX; - } - - /** - * Get the amount of Y-coordinates to modify to get the represented block - * - * @return Amount of Y-coordinates to modify - */ - public int getModY() { - return modY; - } - - /** - * Get the amount of Z-coordinates to modify to get the represented block - * - * @return Amount of Z-coordinates to modify - */ - public int getModZ() { - return modZ; - } - - public MCBlockFace getOppositeFace() { - switch (this) { - case NORTH: - return MCBlockFace.SOUTH; - - case SOUTH: - return MCBlockFace.NORTH; - - case EAST: - return MCBlockFace.WEST; - - case WEST: - return MCBlockFace.EAST; - - case UP: - return MCBlockFace.DOWN; - - case DOWN: - return MCBlockFace.UP; - - case NORTH_EAST: - return MCBlockFace.SOUTH_WEST; - - case NORTH_WEST: - return MCBlockFace.SOUTH_EAST; - - case SOUTH_EAST: - return MCBlockFace.NORTH_WEST; - - case SOUTH_WEST: - return MCBlockFace.NORTH_EAST; - - case WEST_NORTH_WEST: - return MCBlockFace.EAST_SOUTH_EAST; - - case NORTH_NORTH_WEST: - return MCBlockFace.SOUTH_SOUTH_EAST; - - case NORTH_NORTH_EAST: - return MCBlockFace.SOUTH_SOUTH_WEST; - - case EAST_NORTH_EAST: - return MCBlockFace.WEST_SOUTH_WEST; - - case EAST_SOUTH_EAST: - return MCBlockFace.WEST_NORTH_WEST; - - case SOUTH_SOUTH_EAST: - return MCBlockFace.NORTH_NORTH_WEST; - - case SOUTH_SOUTH_WEST: - return MCBlockFace.NORTH_NORTH_EAST; - - case WEST_SOUTH_WEST: - return MCBlockFace.EAST_NORTH_EAST; + NORTH(0, 0, -1), + EAST(1, 0, 0), + SOUTH(0, 0, 1), + WEST(-1, 0, 0), + UP(0, 1, 0), + DOWN(0, -1, 0), + NORTH_EAST(NORTH, EAST), + NORTH_WEST(NORTH, WEST), + SOUTH_EAST(SOUTH, EAST), + SOUTH_WEST(SOUTH, WEST), + WEST_NORTH_WEST(WEST, NORTH_WEST), + NORTH_NORTH_WEST(NORTH, NORTH_WEST), + NORTH_NORTH_EAST(NORTH, NORTH_EAST), + EAST_NORTH_EAST(EAST, NORTH_EAST), + EAST_SOUTH_EAST(EAST, SOUTH_EAST), + SOUTH_SOUTH_EAST(SOUTH, SOUTH_EAST), + SOUTH_SOUTH_WEST(SOUTH, SOUTH_WEST), + WEST_SOUTH_WEST(WEST, SOUTH_WEST), + SELF(0, 0, 0); + + private final int modX; + private final int modY; + private final int modZ; + + MCBlockFace(final int modX, final int modY, final int modZ) { + this.modX = modX; + this.modY = modY; + this.modZ = modZ; + } + + MCBlockFace(final MCBlockFace face1, final MCBlockFace face2) { + this.modX = face1.getModX() + face2.getModX(); + this.modY = face1.getModY() + face2.getModY(); + this.modZ = face1.getModZ() + face2.getModZ(); + } + + /** + * Get the amount of X-coordinates to modify to get the represented block + * + * @return Amount of X-coordinates to modify + */ + public int getModX() { + return modX; + } + + /** + * Get the amount of Y-coordinates to modify to get the represented block + * + * @return Amount of Y-coordinates to modify + */ + public int getModY() { + return modY; + } + + /** + * Get the amount of Z-coordinates to modify to get the represented block + * + * @return Amount of Z-coordinates to modify + */ + public int getModZ() { + return modZ; + } + + public MCBlockFace getOppositeFace() { + switch(this) { + case NORTH: + return MCBlockFace.SOUTH; + + case SOUTH: + return MCBlockFace.NORTH; + + case EAST: + return MCBlockFace.WEST; + + case WEST: + return MCBlockFace.EAST; + + case UP: + return MCBlockFace.DOWN; + + case DOWN: + return MCBlockFace.UP; + + case NORTH_EAST: + return MCBlockFace.SOUTH_WEST; + + case NORTH_WEST: + return MCBlockFace.SOUTH_EAST; + + case SOUTH_EAST: + return MCBlockFace.NORTH_WEST; + + case SOUTH_WEST: + return MCBlockFace.NORTH_EAST; + + case WEST_NORTH_WEST: + return MCBlockFace.EAST_SOUTH_EAST; + + case NORTH_NORTH_WEST: + return MCBlockFace.SOUTH_SOUTH_EAST; + + case NORTH_NORTH_EAST: + return MCBlockFace.SOUTH_SOUTH_WEST; + + case EAST_NORTH_EAST: + return MCBlockFace.WEST_SOUTH_WEST; + + case EAST_SOUTH_EAST: + return MCBlockFace.WEST_NORTH_WEST; + + case SOUTH_SOUTH_EAST: + return MCBlockFace.NORTH_NORTH_WEST; + + case SOUTH_SOUTH_WEST: + return MCBlockFace.NORTH_NORTH_EAST; + + case WEST_SOUTH_WEST: + return MCBlockFace.EAST_NORTH_EAST; - case SELF: - return MCBlockFace.SELF; - } + case SELF: + return MCBlockFace.SELF; + } - return MCBlockFace.SELF; - } + return MCBlockFace.SELF; + } } diff --git a/src/main/java/com/laytonsmith/abstraction/blocks/MCBlockProjectileSource.java b/src/main/java/com/laytonsmith/abstraction/blocks/MCBlockProjectileSource.java index 7a538db4d2..dfd415d9ab 100644 --- a/src/main/java/com/laytonsmith/abstraction/blocks/MCBlockProjectileSource.java +++ b/src/main/java/com/laytonsmith/abstraction/blocks/MCBlockProjectileSource.java @@ -2,11 +2,7 @@ import com.laytonsmith.abstraction.MCProjectileSource; -/** - * - * @author jb_aero - */ public interface MCBlockProjectileSource extends MCProjectileSource { - - public MCBlock getBlock(); + + MCBlock getBlock(); } diff --git a/src/main/java/com/laytonsmith/abstraction/blocks/MCBlockState.java b/src/main/java/com/laytonsmith/abstraction/blocks/MCBlockState.java index 67436b6e87..5a77464ba4 100644 --- a/src/main/java/com/laytonsmith/abstraction/blocks/MCBlockState.java +++ b/src/main/java/com/laytonsmith/abstraction/blocks/MCBlockState.java @@ -1,20 +1,24 @@ package com.laytonsmith.abstraction.blocks; import com.laytonsmith.abstraction.MCLocation; -import com.laytonsmith.abstraction.MCMaterialData; import com.laytonsmith.abstraction.MCMetadatable; -/** - * - * - */ public interface MCBlockState extends MCMetadatable { - public MCMaterialData getData(); + MCMaterial getType(); - public int getTypeId(); + MCBlock getBlock(); - public MCBlock getBlock(); + MCLocation getLocation(); - public MCLocation getLocation(); -} \ No newline at end of file + void update(); + + boolean isLockable(); + + boolean isLocked(); + + String getLock(); + + void setLock(String key); + +} diff --git a/src/main/java/com/laytonsmith/abstraction/blocks/MCBrewingStand.java b/src/main/java/com/laytonsmith/abstraction/blocks/MCBrewingStand.java new file mode 100644 index 0000000000..d96b01b99a --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/blocks/MCBrewingStand.java @@ -0,0 +1,12 @@ +package com.laytonsmith.abstraction.blocks; + +import com.laytonsmith.abstraction.MCBrewerInventory; + +public interface MCBrewingStand extends MCContainer { + int getBrewingTime(); + int getFuelLevel(); + @Override + MCBrewerInventory getInventory(); + void setBrewingTime(int brewTime); + void setFuelLevel(int level); +} diff --git a/src/main/java/com/laytonsmith/abstraction/blocks/MCChest.java b/src/main/java/com/laytonsmith/abstraction/blocks/MCChest.java new file mode 100644 index 0000000000..ce8ae89c77 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/blocks/MCChest.java @@ -0,0 +1,4 @@ +package com.laytonsmith.abstraction.blocks; + +public interface MCChest extends MCContainer { +} diff --git a/src/main/java/com/laytonsmith/abstraction/blocks/MCCommandBlock.java b/src/main/java/com/laytonsmith/abstraction/blocks/MCCommandBlock.java index 9577628e3e..4af02370ca 100644 --- a/src/main/java/com/laytonsmith/abstraction/blocks/MCCommandBlock.java +++ b/src/main/java/com/laytonsmith/abstraction/blocks/MCCommandBlock.java @@ -1,13 +1,12 @@ package com.laytonsmith.abstraction.blocks; - public interface MCCommandBlock extends MCBlockState { - - public String getCommand(); - - public String getName(); - - public void setCommand(String command); - - public void setName(String name); + + String getCommand(); + + String getName(); + + void setCommand(String command); + + void setName(String name); } diff --git a/src/main/java/com/laytonsmith/abstraction/blocks/MCContainer.java b/src/main/java/com/laytonsmith/abstraction/blocks/MCContainer.java new file mode 100644 index 0000000000..908570142c --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/blocks/MCContainer.java @@ -0,0 +1,6 @@ +package com.laytonsmith.abstraction.blocks; + +import com.laytonsmith.abstraction.MCInventoryHolder; + +public interface MCContainer extends MCBlockState, MCInventoryHolder { +} diff --git a/src/main/java/com/laytonsmith/abstraction/blocks/MCDecoratedPot.java b/src/main/java/com/laytonsmith/abstraction/blocks/MCDecoratedPot.java new file mode 100644 index 0000000000..d6eb99692d --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/blocks/MCDecoratedPot.java @@ -0,0 +1,23 @@ +package com.laytonsmith.abstraction.blocks; + +import com.laytonsmith.abstraction.MCItemStack; + +import java.util.Map; + +public interface MCDecoratedPot extends MCBlockState { + + Map getSherds(); + + void setSherd(Side side, MCMaterial sherd); + + MCItemStack getItemStack(); + + void setItemStack(MCItemStack item); + + enum Side { + BACK, + FRONT, + LEFT, + RIGHT + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/blocks/MCDispenser.java b/src/main/java/com/laytonsmith/abstraction/blocks/MCDispenser.java new file mode 100644 index 0000000000..1b6a75f9f3 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/blocks/MCDispenser.java @@ -0,0 +1,8 @@ +package com.laytonsmith.abstraction.blocks; + +import com.laytonsmith.abstraction.MCInventoryHolder; + +public interface MCDispenser extends MCBlockState, MCInventoryHolder { + boolean dispense(); + MCBlockProjectileSource getBlockProjectileSource(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/blocks/MCDropper.java b/src/main/java/com/laytonsmith/abstraction/blocks/MCDropper.java new file mode 100644 index 0000000000..d7ca265b8b --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/blocks/MCDropper.java @@ -0,0 +1,5 @@ +package com.laytonsmith.abstraction.blocks; + +public interface MCDropper extends MCContainer { + void drop(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/blocks/MCEndGateway.java b/src/main/java/com/laytonsmith/abstraction/blocks/MCEndGateway.java new file mode 100644 index 0000000000..5a7eb0849f --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/blocks/MCEndGateway.java @@ -0,0 +1,12 @@ +package com.laytonsmith.abstraction.blocks; + +import com.laytonsmith.abstraction.MCLocation; + +public interface MCEndGateway extends MCBlockState { + MCLocation getExitLocation(); + void setExitLocation(MCLocation location); + boolean isExactTeleport(); + void setExactTeleport(boolean isExact); + long getAge(); + void setAge(long age); +} diff --git a/src/main/java/com/laytonsmith/abstraction/blocks/MCFallingBlock.java b/src/main/java/com/laytonsmith/abstraction/blocks/MCFallingBlock.java deleted file mode 100644 index fc44c21127..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/blocks/MCFallingBlock.java +++ /dev/null @@ -1,16 +0,0 @@ - -package com.laytonsmith.abstraction.blocks; - -import com.laytonsmith.abstraction.MCEntity; - -/** - * - * @author import - */ -public interface MCFallingBlock extends MCEntity { - public byte getBlockData(); - public int getBlockId(); - public boolean getDropItem(); - public MCMaterial getMaterial(); - public void setDropItem(boolean drop); -} diff --git a/src/main/java/com/laytonsmith/abstraction/blocks/MCFurnace.java b/src/main/java/com/laytonsmith/abstraction/blocks/MCFurnace.java new file mode 100644 index 0000000000..c1f7c1ad95 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/blocks/MCFurnace.java @@ -0,0 +1,12 @@ +package com.laytonsmith.abstraction.blocks; + +import com.laytonsmith.abstraction.MCFurnaceInventory; + +public interface MCFurnace extends MCContainer { + short getBurnTime(); + void setBurnTime(short burnTime); + short getCookTime(); + void setCookTime(short cookTime); + @Override + MCFurnaceInventory getInventory(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/blocks/MCLectern.java b/src/main/java/com/laytonsmith/abstraction/blocks/MCLectern.java new file mode 100644 index 0000000000..493bdda730 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/blocks/MCLectern.java @@ -0,0 +1,8 @@ +package com.laytonsmith.abstraction.blocks; + +import com.laytonsmith.abstraction.MCInventoryHolder; + +public interface MCLectern extends MCBlockState, MCInventoryHolder { + int getPage(); + void setPage(int page); +} diff --git a/src/main/java/com/laytonsmith/abstraction/blocks/MCMaterial.java b/src/main/java/com/laytonsmith/abstraction/blocks/MCMaterial.java index a690b9fbf7..afb7ba157e 100644 --- a/src/main/java/com/laytonsmith/abstraction/blocks/MCMaterial.java +++ b/src/main/java/com/laytonsmith/abstraction/blocks/MCMaterial.java @@ -1,29 +1,1894 @@ +package com.laytonsmith.abstraction.blocks; +import com.laytonsmith.PureUtilities.ClassLoading.DynamicEnum; +import com.laytonsmith.abstraction.AbstractionObject; +import com.laytonsmith.abstraction.blocks.MCMaterial.MCVanillaMaterial; +import com.laytonsmith.abstraction.enums.MCVersion; +import com.laytonsmith.annotations.MDynamicEnum; -package com.laytonsmith.abstraction.blocks; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@MDynamicEnum("com.commandhelper.Material") +public abstract class MCMaterial extends DynamicEnum implements AbstractionObject { + + protected static final Map BY_STRING = new HashMap<>(); + + public MCMaterial(MCVanillaMaterial mcVanillaMaterial, Concrete concrete) { + super(mcVanillaMaterial, concrete); + } + + /** + * Replicates Enum.valueOf(). + * + * @param materialName To test against our current values. + * @return Matching material object + * @throws IllegalArgumentException if material is not found by that name + */ + public static MCMaterial valueOf(String materialName) throws IllegalArgumentException { + MCMaterial ret = get(materialName); + if(ret == null) { + throw new IllegalArgumentException("Unknown material type: " + materialName); + } + return ret; + } + + /** + * @param materialName To test against our current values. + * @return Matching material object or null + */ + public static MCMaterial get(String materialName) { + return BY_STRING.get(materialName); + } + + public static Set types() { + if(BY_STRING.isEmpty()) { // docs mode + Set dummy = new HashSet<>(); + for(final MCVanillaMaterial t : MCVanillaMaterial.values()) { + if(t.existsIn(MCVersion.CURRENT)) { + dummy.add(t.name()); + } + } + return dummy; + } + return BY_STRING.keySet(); + } + + /** + * Reflectively accessed by NativeTypeList! + * + * @return Our own MCMaterial list + */ + public static List values() { + if(BY_STRING.isEmpty()) { + ArrayList dummy = new ArrayList<>(); + for(final MCVanillaMaterial t : MCVanillaMaterial.values()) { + if(!t.existsIn(MCVersion.CURRENT)) { + continue; + } + dummy.add(new MCMaterial<>(t, null) { + @Override + public String name() { + return t.name(); + } + }); + } + return dummy; + } + return new ArrayList<>(BY_STRING.values()); + } + + public MCBlockData createBlockData() { + throw new UnsupportedOperationException(); + } + + public short getMaxDurability() { + throw new UnsupportedOperationException(); + } + + public int getType() { + throw new UnsupportedOperationException(); + } + + public String getName() { + return name(); + } + + public int getMaxStackSize() { + throw new UnsupportedOperationException(); + } + + public boolean hasGravity() { + throw new UnsupportedOperationException(); + } + + public boolean isBlock() { + throw new UnsupportedOperationException(); + } + + public boolean isItem() { + throw new UnsupportedOperationException(); + } + + public boolean isBurnable() { + throw new UnsupportedOperationException(); + } + + public boolean isEdible() { + throw new UnsupportedOperationException(); + } + + public boolean isFlammable() { + throw new UnsupportedOperationException(); + } + + public boolean isOccluding() { + throw new UnsupportedOperationException(); + } + + public boolean isRecord() { + throw new UnsupportedOperationException(); + } + + public boolean isSolid() { + throw new UnsupportedOperationException(); + } + + public boolean isTransparent() { + throw new UnsupportedOperationException(); + } + + public boolean isInteractable() { + throw new UnsupportedOperationException(); + } + + public boolean isAir() { + return getAbstracted() == MCVanillaMaterial.AIR || getAbstracted() == MCVanillaMaterial.VOID_AIR; + } + + public boolean isLegacy() { + return false; + } + + public float getHardness() { + throw new UnsupportedOperationException(); + } + + public float getBlastResistance() { + throw new UnsupportedOperationException(); + } + + @Override + public Object getHandle() { + return getConcrete(); + } + + public enum MCVanillaMaterial { + AIR, + STONE, + GRANITE, + POLISHED_GRANITE, + DIORITE, + POLISHED_DIORITE, + ANDESITE, + POLISHED_ANDESITE, + GRASS_BLOCK, + DIRT, + COARSE_DIRT, + PODZOL, + CRIMSON_NYLIUM, + WARPED_NYLIUM, + COBBLESTONE, + OAK_PLANKS, + SPRUCE_PLANKS, + BIRCH_PLANKS, + JUNGLE_PLANKS, + ACACIA_PLANKS, + DARK_OAK_PLANKS, + CRIMSON_PLANKS, + WARPED_PLANKS, + OAK_SAPLING, + SPRUCE_SAPLING, + BIRCH_SAPLING, + JUNGLE_SAPLING, + ACACIA_SAPLING, + DARK_OAK_SAPLING, + BEDROCK, + SAND, + RED_SAND, + GRAVEL, + COAL_ORE, + IRON_ORE, + GOLD_ORE, + REDSTONE_ORE, + EMERALD_ORE, + LAPIS_ORE, + DIAMOND_ORE, + NETHER_GOLD_ORE, + NETHER_QUARTZ_ORE, + ANCIENT_DEBRIS, + COAL_BLOCK, + IRON_BLOCK, + GOLD_BLOCK, + DIAMOND_BLOCK, + NETHERITE_BLOCK, + OAK_LOG, + SPRUCE_LOG, + BIRCH_LOG, + JUNGLE_LOG, + ACACIA_LOG, + DARK_OAK_LOG, + CRIMSON_STEM, + WARPED_STEM, + STRIPPED_OAK_LOG, + STRIPPED_SPRUCE_LOG, + STRIPPED_BIRCH_LOG, + STRIPPED_JUNGLE_LOG, + STRIPPED_ACACIA_LOG, + STRIPPED_DARK_OAK_LOG, + STRIPPED_CRIMSON_STEM, + STRIPPED_WARPED_STEM, + STRIPPED_OAK_WOOD, + STRIPPED_SPRUCE_WOOD, + STRIPPED_BIRCH_WOOD, + STRIPPED_JUNGLE_WOOD, + STRIPPED_ACACIA_WOOD, + STRIPPED_DARK_OAK_WOOD, + STRIPPED_CRIMSON_HYPHAE, + STRIPPED_WARPED_HYPHAE, + OAK_WOOD, + SPRUCE_WOOD, + BIRCH_WOOD, + JUNGLE_WOOD, + ACACIA_WOOD, + DARK_OAK_WOOD, + CRIMSON_HYPHAE, + WARPED_HYPHAE, + OAK_LEAVES, + SPRUCE_LEAVES, + BIRCH_LEAVES, + JUNGLE_LEAVES, + ACACIA_LEAVES, + DARK_OAK_LEAVES, + SPONGE, + WET_SPONGE, + GLASS, + LAPIS_BLOCK, + SANDSTONE, + CHISELED_SANDSTONE, + CUT_SANDSTONE, + COBWEB, + GRASS(MCVersion.MC1_0, MCVersion.MC1_20_2), + SHORT_GRASS(MCVersion.MC1_20_4), // changed from GRASS + FERN, + DEAD_BUSH, + SEAGRASS, + SEA_PICKLE, + WHITE_WOOL, + ORANGE_WOOL, + MAGENTA_WOOL, + LIGHT_BLUE_WOOL, + YELLOW_WOOL, + LIME_WOOL, + PINK_WOOL, + GRAY_WOOL, + LIGHT_GRAY_WOOL, + CYAN_WOOL, + PURPLE_WOOL, + BLUE_WOOL, + BROWN_WOOL, + GREEN_WOOL, + RED_WOOL, + BLACK_WOOL, + DANDELION, + POPPY, + BLUE_ORCHID, + ALLIUM, + AZURE_BLUET, + RED_TULIP, + ORANGE_TULIP, + WHITE_TULIP, + PINK_TULIP, + OXEYE_DAISY, + CORNFLOWER, + LILY_OF_THE_VALLEY, + WITHER_ROSE, + BROWN_MUSHROOM, + RED_MUSHROOM, + CRIMSON_FUNGUS, + WARPED_FUNGUS, + CRIMSON_ROOTS, + WARPED_ROOTS, + NETHER_SPROUTS, + WEEPING_VINES, + TWISTING_VINES, + SUGAR_CANE, + KELP, + BAMBOO, + OAK_SLAB, + SPRUCE_SLAB, + BIRCH_SLAB, + JUNGLE_SLAB, + ACACIA_SLAB, + DARK_OAK_SLAB, + CRIMSON_SLAB, + WARPED_SLAB, + STONE_SLAB, + SMOOTH_STONE_SLAB, + SANDSTONE_SLAB, + CUT_SANDSTONE_SLAB, + PETRIFIED_OAK_SLAB, + COBBLESTONE_SLAB, + BRICK_SLAB, + STONE_BRICK_SLAB, + NETHER_BRICK_SLAB, + QUARTZ_SLAB, + RED_SANDSTONE_SLAB, + CUT_RED_SANDSTONE_SLAB, + PURPUR_SLAB, + PRISMARINE_SLAB, + PRISMARINE_BRICK_SLAB, + DARK_PRISMARINE_SLAB, + SMOOTH_QUARTZ, + SMOOTH_RED_SANDSTONE, + SMOOTH_SANDSTONE, + SMOOTH_STONE, + BRICKS, + BOOKSHELF, + MOSSY_COBBLESTONE, + OBSIDIAN, + TORCH, + END_ROD, + CHORUS_PLANT, + CHORUS_FLOWER, + PURPUR_BLOCK, + PURPUR_PILLAR, + PURPUR_STAIRS, + SPAWNER, + OAK_STAIRS, + CHEST, + CRAFTING_TABLE, + FARMLAND, + FURNACE, + LADDER, + COBBLESTONE_STAIRS, + SNOW, + ICE, + SNOW_BLOCK, + CACTUS, + CLAY, + JUKEBOX, + OAK_FENCE, + SPRUCE_FENCE, + BIRCH_FENCE, + JUNGLE_FENCE, + ACACIA_FENCE, + DARK_OAK_FENCE, + CRIMSON_FENCE, + WARPED_FENCE, + PUMPKIN, + CARVED_PUMPKIN, + JACK_O_LANTERN, + NETHERRACK, + SOUL_SAND, + SOUL_SOIL, + BASALT, + POLISHED_BASALT, + SOUL_TORCH, + GLOWSTONE, + INFESTED_STONE, + INFESTED_COBBLESTONE, + INFESTED_STONE_BRICKS, + INFESTED_MOSSY_STONE_BRICKS, + INFESTED_CRACKED_STONE_BRICKS, + INFESTED_CHISELED_STONE_BRICKS, + STONE_BRICKS, + MOSSY_STONE_BRICKS, + CRACKED_STONE_BRICKS, + CHISELED_STONE_BRICKS, + BROWN_MUSHROOM_BLOCK, + RED_MUSHROOM_BLOCK, + MUSHROOM_STEM, + IRON_BARS, + CHAIN(MCVersion.MC1_16, MCVersion.MC1_21_8), + IRON_CHAIN(MCVersion.MC1_21_9), // changed from CHAIN + GLASS_PANE, + MELON, + VINE, + BRICK_STAIRS, + STONE_BRICK_STAIRS, + MYCELIUM, + LILY_PAD, + NETHER_BRICKS, + CRACKED_NETHER_BRICKS, + CHISELED_NETHER_BRICKS, + NETHER_BRICK_FENCE, + NETHER_BRICK_STAIRS, + ENCHANTING_TABLE, + END_PORTAL_FRAME, + END_STONE, + END_STONE_BRICKS, + DRAGON_EGG, + SANDSTONE_STAIRS, + ENDER_CHEST, + EMERALD_BLOCK, + SPRUCE_STAIRS, + BIRCH_STAIRS, + JUNGLE_STAIRS, + CRIMSON_STAIRS, + WARPED_STAIRS, + COMMAND_BLOCK, + BEACON, + COBBLESTONE_WALL, + MOSSY_COBBLESTONE_WALL, + BRICK_WALL, + PRISMARINE_WALL, + RED_SANDSTONE_WALL, + MOSSY_STONE_BRICK_WALL, + GRANITE_WALL, + STONE_BRICK_WALL, + NETHER_BRICK_WALL, + ANDESITE_WALL, + RED_NETHER_BRICK_WALL, + SANDSTONE_WALL, + END_STONE_BRICK_WALL, + DIORITE_WALL, + BLACKSTONE_WALL, + POLISHED_BLACKSTONE_WALL, + POLISHED_BLACKSTONE_BRICK_WALL, + ANVIL, + CHIPPED_ANVIL, + DAMAGED_ANVIL, + CHISELED_QUARTZ_BLOCK, + QUARTZ_BLOCK, + QUARTZ_BRICKS, + QUARTZ_PILLAR, + QUARTZ_STAIRS, + WHITE_TERRACOTTA, + ORANGE_TERRACOTTA, + MAGENTA_TERRACOTTA, + LIGHT_BLUE_TERRACOTTA, + YELLOW_TERRACOTTA, + LIME_TERRACOTTA, + PINK_TERRACOTTA, + GRAY_TERRACOTTA, + LIGHT_GRAY_TERRACOTTA, + CYAN_TERRACOTTA, + PURPLE_TERRACOTTA, + BLUE_TERRACOTTA, + BROWN_TERRACOTTA, + GREEN_TERRACOTTA, + RED_TERRACOTTA, + BLACK_TERRACOTTA, + BARRIER, + HAY_BLOCK, + WHITE_CARPET, + ORANGE_CARPET, + MAGENTA_CARPET, + LIGHT_BLUE_CARPET, + YELLOW_CARPET, + LIME_CARPET, + PINK_CARPET, + GRAY_CARPET, + LIGHT_GRAY_CARPET, + CYAN_CARPET, + PURPLE_CARPET, + BLUE_CARPET, + BROWN_CARPET, + GREEN_CARPET, + RED_CARPET, + BLACK_CARPET, + TERRACOTTA, + PACKED_ICE, + ACACIA_STAIRS, + DARK_OAK_STAIRS, + SUNFLOWER, + LILAC, + ROSE_BUSH, + PEONY, + TALL_GRASS, + LARGE_FERN, + WHITE_STAINED_GLASS, + ORANGE_STAINED_GLASS, + MAGENTA_STAINED_GLASS, + LIGHT_BLUE_STAINED_GLASS, + YELLOW_STAINED_GLASS, + LIME_STAINED_GLASS, + PINK_STAINED_GLASS, + GRAY_STAINED_GLASS, + LIGHT_GRAY_STAINED_GLASS, + CYAN_STAINED_GLASS, + PURPLE_STAINED_GLASS, + BLUE_STAINED_GLASS, + BROWN_STAINED_GLASS, + GREEN_STAINED_GLASS, + RED_STAINED_GLASS, + BLACK_STAINED_GLASS, + WHITE_STAINED_GLASS_PANE, + ORANGE_STAINED_GLASS_PANE, + MAGENTA_STAINED_GLASS_PANE, + LIGHT_BLUE_STAINED_GLASS_PANE, + YELLOW_STAINED_GLASS_PANE, + LIME_STAINED_GLASS_PANE, + PINK_STAINED_GLASS_PANE, + GRAY_STAINED_GLASS_PANE, + LIGHT_GRAY_STAINED_GLASS_PANE, + CYAN_STAINED_GLASS_PANE, + PURPLE_STAINED_GLASS_PANE, + BLUE_STAINED_GLASS_PANE, + BROWN_STAINED_GLASS_PANE, + GREEN_STAINED_GLASS_PANE, + RED_STAINED_GLASS_PANE, + BLACK_STAINED_GLASS_PANE, + PRISMARINE, + PRISMARINE_BRICKS, + DARK_PRISMARINE, + PRISMARINE_STAIRS, + PRISMARINE_BRICK_STAIRS, + DARK_PRISMARINE_STAIRS, + SEA_LANTERN, + RED_SANDSTONE, + CHISELED_RED_SANDSTONE, + CUT_RED_SANDSTONE, + RED_SANDSTONE_STAIRS, + REPEATING_COMMAND_BLOCK, + CHAIN_COMMAND_BLOCK, + MAGMA_BLOCK, + NETHER_WART_BLOCK, + WARPED_WART_BLOCK, + RED_NETHER_BRICKS, + BONE_BLOCK, + STRUCTURE_VOID, + SHULKER_BOX, + WHITE_SHULKER_BOX, + ORANGE_SHULKER_BOX, + MAGENTA_SHULKER_BOX, + LIGHT_BLUE_SHULKER_BOX, + YELLOW_SHULKER_BOX, + LIME_SHULKER_BOX, + PINK_SHULKER_BOX, + GRAY_SHULKER_BOX, + LIGHT_GRAY_SHULKER_BOX, + CYAN_SHULKER_BOX, + PURPLE_SHULKER_BOX, + BLUE_SHULKER_BOX, + BROWN_SHULKER_BOX, + GREEN_SHULKER_BOX, + RED_SHULKER_BOX, + BLACK_SHULKER_BOX, + WHITE_GLAZED_TERRACOTTA, + ORANGE_GLAZED_TERRACOTTA, + MAGENTA_GLAZED_TERRACOTTA, + LIGHT_BLUE_GLAZED_TERRACOTTA, + YELLOW_GLAZED_TERRACOTTA, + LIME_GLAZED_TERRACOTTA, + PINK_GLAZED_TERRACOTTA, + GRAY_GLAZED_TERRACOTTA, + LIGHT_GRAY_GLAZED_TERRACOTTA, + CYAN_GLAZED_TERRACOTTA, + PURPLE_GLAZED_TERRACOTTA, + BLUE_GLAZED_TERRACOTTA, + BROWN_GLAZED_TERRACOTTA, + GREEN_GLAZED_TERRACOTTA, + RED_GLAZED_TERRACOTTA, + BLACK_GLAZED_TERRACOTTA, + WHITE_CONCRETE, + ORANGE_CONCRETE, + MAGENTA_CONCRETE, + LIGHT_BLUE_CONCRETE, + YELLOW_CONCRETE, + LIME_CONCRETE, + PINK_CONCRETE, + GRAY_CONCRETE, + LIGHT_GRAY_CONCRETE, + CYAN_CONCRETE, + PURPLE_CONCRETE, + BLUE_CONCRETE, + BROWN_CONCRETE, + GREEN_CONCRETE, + RED_CONCRETE, + BLACK_CONCRETE, + WHITE_CONCRETE_POWDER, + ORANGE_CONCRETE_POWDER, + MAGENTA_CONCRETE_POWDER, + LIGHT_BLUE_CONCRETE_POWDER, + YELLOW_CONCRETE_POWDER, + LIME_CONCRETE_POWDER, + PINK_CONCRETE_POWDER, + GRAY_CONCRETE_POWDER, + LIGHT_GRAY_CONCRETE_POWDER, + CYAN_CONCRETE_POWDER, + PURPLE_CONCRETE_POWDER, + BLUE_CONCRETE_POWDER, + BROWN_CONCRETE_POWDER, + GREEN_CONCRETE_POWDER, + RED_CONCRETE_POWDER, + BLACK_CONCRETE_POWDER, + TURTLE_EGG, + DEAD_TUBE_CORAL_BLOCK, + DEAD_BRAIN_CORAL_BLOCK, + DEAD_BUBBLE_CORAL_BLOCK, + DEAD_FIRE_CORAL_BLOCK, + DEAD_HORN_CORAL_BLOCK, + TUBE_CORAL_BLOCK, + BRAIN_CORAL_BLOCK, + BUBBLE_CORAL_BLOCK, + FIRE_CORAL_BLOCK, + HORN_CORAL_BLOCK, + TUBE_CORAL, + BRAIN_CORAL, + BUBBLE_CORAL, + FIRE_CORAL, + HORN_CORAL, + DEAD_BRAIN_CORAL, + DEAD_BUBBLE_CORAL, + DEAD_FIRE_CORAL, + DEAD_HORN_CORAL, + DEAD_TUBE_CORAL, + TUBE_CORAL_FAN, + BRAIN_CORAL_FAN, + BUBBLE_CORAL_FAN, + FIRE_CORAL_FAN, + HORN_CORAL_FAN, + DEAD_TUBE_CORAL_FAN, + DEAD_BRAIN_CORAL_FAN, + DEAD_BUBBLE_CORAL_FAN, + DEAD_FIRE_CORAL_FAN, + DEAD_HORN_CORAL_FAN, + BLUE_ICE, + CONDUIT, + POLISHED_GRANITE_STAIRS, + SMOOTH_RED_SANDSTONE_STAIRS, + MOSSY_STONE_BRICK_STAIRS, + POLISHED_DIORITE_STAIRS, + MOSSY_COBBLESTONE_STAIRS, + END_STONE_BRICK_STAIRS, + STONE_STAIRS, + SMOOTH_SANDSTONE_STAIRS, + SMOOTH_QUARTZ_STAIRS, + GRANITE_STAIRS, + ANDESITE_STAIRS, + RED_NETHER_BRICK_STAIRS, + POLISHED_ANDESITE_STAIRS, + DIORITE_STAIRS, + POLISHED_GRANITE_SLAB, + SMOOTH_RED_SANDSTONE_SLAB, + MOSSY_STONE_BRICK_SLAB, + POLISHED_DIORITE_SLAB, + MOSSY_COBBLESTONE_SLAB, + END_STONE_BRICK_SLAB, + SMOOTH_SANDSTONE_SLAB, + SMOOTH_QUARTZ_SLAB, + GRANITE_SLAB, + ANDESITE_SLAB, + RED_NETHER_BRICK_SLAB, + POLISHED_ANDESITE_SLAB, + DIORITE_SLAB, + SCAFFOLDING, + REDSTONE, + REDSTONE_TORCH, + REDSTONE_BLOCK, + REPEATER, + COMPARATOR, + PISTON, + STICKY_PISTON, + SLIME_BLOCK, + HONEY_BLOCK, + OBSERVER, + HOPPER, + DISPENSER, + DROPPER, + LECTERN, + TARGET, + LEVER, + DAYLIGHT_DETECTOR, + TRIPWIRE_HOOK, + TRAPPED_CHEST, + TNT, + REDSTONE_LAMP, + NOTE_BLOCK, + STONE_BUTTON, + POLISHED_BLACKSTONE_BUTTON, + OAK_BUTTON, + SPRUCE_BUTTON, + BIRCH_BUTTON, + JUNGLE_BUTTON, + ACACIA_BUTTON, + DARK_OAK_BUTTON, + CRIMSON_BUTTON, + WARPED_BUTTON, + STONE_PRESSURE_PLATE, + POLISHED_BLACKSTONE_PRESSURE_PLATE, + LIGHT_WEIGHTED_PRESSURE_PLATE, + HEAVY_WEIGHTED_PRESSURE_PLATE, + OAK_PRESSURE_PLATE, + SPRUCE_PRESSURE_PLATE, + BIRCH_PRESSURE_PLATE, + JUNGLE_PRESSURE_PLATE, + ACACIA_PRESSURE_PLATE, + DARK_OAK_PRESSURE_PLATE, + CRIMSON_PRESSURE_PLATE, + WARPED_PRESSURE_PLATE, + IRON_DOOR, + OAK_DOOR, + SPRUCE_DOOR, + BIRCH_DOOR, + JUNGLE_DOOR, + ACACIA_DOOR, + DARK_OAK_DOOR, + CRIMSON_DOOR, + WARPED_DOOR, + IRON_TRAPDOOR, + OAK_TRAPDOOR, + SPRUCE_TRAPDOOR, + BIRCH_TRAPDOOR, + JUNGLE_TRAPDOOR, + ACACIA_TRAPDOOR, + DARK_OAK_TRAPDOOR, + CRIMSON_TRAPDOOR, + WARPED_TRAPDOOR, + OAK_FENCE_GATE, + SPRUCE_FENCE_GATE, + BIRCH_FENCE_GATE, + JUNGLE_FENCE_GATE, + ACACIA_FENCE_GATE, + DARK_OAK_FENCE_GATE, + CRIMSON_FENCE_GATE, + WARPED_FENCE_GATE, + POWERED_RAIL, + DETECTOR_RAIL, + RAIL, + ACTIVATOR_RAIL, + SADDLE, + MINECART, + CHEST_MINECART, + FURNACE_MINECART, + TNT_MINECART, + HOPPER_MINECART, + CARROT_ON_A_STICK, + WARPED_FUNGUS_ON_A_STICK, + ELYTRA, + OAK_BOAT, + SPRUCE_BOAT, + BIRCH_BOAT, + JUNGLE_BOAT, + ACACIA_BOAT, + DARK_OAK_BOAT, + STRUCTURE_BLOCK, + JIGSAW, + TURTLE_HELMET, + SCUTE(MCVersion.MC1_13, MCVersion.MC1_20_4), + TURTLE_SCUTE(MCVersion.MC1_20_6), // changed from SCUTE + FLINT_AND_STEEL, + APPLE, + BOW, + ARROW, + COAL, + CHARCOAL, + DIAMOND, + EMERALD, + LAPIS_LAZULI, + QUARTZ, + IRON_INGOT, + GOLD_INGOT, + NETHERITE_INGOT, + NETHERITE_SCRAP, + WOODEN_SWORD, + WOODEN_SHOVEL, + WOODEN_PICKAXE, + WOODEN_AXE, + WOODEN_HOE, + STONE_SWORD, + STONE_SHOVEL, + STONE_PICKAXE, + STONE_AXE, + STONE_HOE, + GOLDEN_SWORD, + GOLDEN_SHOVEL, + GOLDEN_PICKAXE, + GOLDEN_AXE, + GOLDEN_HOE, + IRON_SWORD, + IRON_SHOVEL, + IRON_PICKAXE, + IRON_AXE, + IRON_HOE, + DIAMOND_SWORD, + DIAMOND_SHOVEL, + DIAMOND_PICKAXE, + DIAMOND_AXE, + DIAMOND_HOE, + NETHERITE_SWORD, + NETHERITE_SHOVEL, + NETHERITE_PICKAXE, + NETHERITE_AXE, + NETHERITE_HOE, + STICK, + BOWL, + MUSHROOM_STEW, + STRING, + FEATHER, + GUNPOWDER, + WHEAT_SEEDS, + WHEAT, + BREAD, + LEATHER_HELMET, + LEATHER_CHESTPLATE, + LEATHER_LEGGINGS, + LEATHER_BOOTS, + CHAINMAIL_HELMET, + CHAINMAIL_CHESTPLATE, + CHAINMAIL_LEGGINGS, + CHAINMAIL_BOOTS, + IRON_HELMET, + IRON_CHESTPLATE, + IRON_LEGGINGS, + IRON_BOOTS, + DIAMOND_HELMET, + DIAMOND_CHESTPLATE, + DIAMOND_LEGGINGS, + DIAMOND_BOOTS, + GOLDEN_HELMET, + GOLDEN_CHESTPLATE, + GOLDEN_LEGGINGS, + GOLDEN_BOOTS, + NETHERITE_HELMET, + NETHERITE_CHESTPLATE, + NETHERITE_LEGGINGS, + NETHERITE_BOOTS, + FLINT, + PORKCHOP, + COOKED_PORKCHOP, + PAINTING, + GOLDEN_APPLE, + ENCHANTED_GOLDEN_APPLE, + OAK_SIGN, + SPRUCE_SIGN, + BIRCH_SIGN, + JUNGLE_SIGN, + ACACIA_SIGN, + DARK_OAK_SIGN, + CRIMSON_SIGN, + WARPED_SIGN, + BUCKET, + WATER_BUCKET, + LAVA_BUCKET, + SNOWBALL, + LEATHER, + MILK_BUCKET, + PUFFERFISH_BUCKET, + SALMON_BUCKET, + COD_BUCKET, + TROPICAL_FISH_BUCKET, + BRICK, + CLAY_BALL, + DRIED_KELP_BLOCK, + PAPER, + BOOK, + SLIME_BALL, + EGG, + COMPASS, + FISHING_ROD, + CLOCK, + GLOWSTONE_DUST, + COD, + SALMON, + TROPICAL_FISH, + PUFFERFISH, + COOKED_COD, + COOKED_SALMON, + INK_SAC, + COCOA_BEANS, + WHITE_DYE, + ORANGE_DYE, + MAGENTA_DYE, + LIGHT_BLUE_DYE, + YELLOW_DYE, + LIME_DYE, + PINK_DYE, + GRAY_DYE, + LIGHT_GRAY_DYE, + CYAN_DYE, + PURPLE_DYE, + BLUE_DYE, + BROWN_DYE, + GREEN_DYE, + RED_DYE, + BLACK_DYE, + BONE_MEAL, + BONE, + SUGAR, + CAKE, + WHITE_BED, + ORANGE_BED, + MAGENTA_BED, + LIGHT_BLUE_BED, + YELLOW_BED, + LIME_BED, + PINK_BED, + GRAY_BED, + LIGHT_GRAY_BED, + CYAN_BED, + PURPLE_BED, + BLUE_BED, + BROWN_BED, + GREEN_BED, + RED_BED, + BLACK_BED, + COOKIE, + FILLED_MAP, + SHEARS, + MELON_SLICE, + DRIED_KELP, + PUMPKIN_SEEDS, + MELON_SEEDS, + BEEF, + COOKED_BEEF, + CHICKEN, + COOKED_CHICKEN, + ROTTEN_FLESH, + ENDER_PEARL, + BLAZE_ROD, + GHAST_TEAR, + GOLD_NUGGET, + NETHER_WART, + POTION, + GLASS_BOTTLE, + SPIDER_EYE, + FERMENTED_SPIDER_EYE, + BLAZE_POWDER, + MAGMA_CREAM, + BREWING_STAND, + CAULDRON, + ENDER_EYE, + GLISTERING_MELON_SLICE, + BAT_SPAWN_EGG, + BEE_SPAWN_EGG, + BLAZE_SPAWN_EGG, + CAT_SPAWN_EGG, + CAVE_SPIDER_SPAWN_EGG, + CHICKEN_SPAWN_EGG, + COD_SPAWN_EGG, + COW_SPAWN_EGG, + CREEPER_SPAWN_EGG, + DOLPHIN_SPAWN_EGG, + DONKEY_SPAWN_EGG, + DROWNED_SPAWN_EGG, + ELDER_GUARDIAN_SPAWN_EGG, + ENDERMAN_SPAWN_EGG, + ENDERMITE_SPAWN_EGG, + EVOKER_SPAWN_EGG, + FOX_SPAWN_EGG, + GHAST_SPAWN_EGG, + GUARDIAN_SPAWN_EGG, + HOGLIN_SPAWN_EGG, + HORSE_SPAWN_EGG, + HUSK_SPAWN_EGG, + LLAMA_SPAWN_EGG, + MAGMA_CUBE_SPAWN_EGG, + MOOSHROOM_SPAWN_EGG, + MULE_SPAWN_EGG, + OCELOT_SPAWN_EGG, + PANDA_SPAWN_EGG, + PARROT_SPAWN_EGG, + PHANTOM_SPAWN_EGG, + PIG_SPAWN_EGG, + PIGLIN_SPAWN_EGG, + PIGLIN_BRUTE_SPAWN_EGG, + PILLAGER_SPAWN_EGG, + POLAR_BEAR_SPAWN_EGG, + PUFFERFISH_SPAWN_EGG, + RABBIT_SPAWN_EGG, + RAVAGER_SPAWN_EGG, + SALMON_SPAWN_EGG, + SHEEP_SPAWN_EGG, + SHULKER_SPAWN_EGG, + SILVERFISH_SPAWN_EGG, + SKELETON_SPAWN_EGG, + SKELETON_HORSE_SPAWN_EGG, + SLIME_SPAWN_EGG, + SPIDER_SPAWN_EGG, + SQUID_SPAWN_EGG, + STRAY_SPAWN_EGG, + STRIDER_SPAWN_EGG, + TRADER_LLAMA_SPAWN_EGG, + TROPICAL_FISH_SPAWN_EGG, + TURTLE_SPAWN_EGG, + VEX_SPAWN_EGG, + VILLAGER_SPAWN_EGG, + VINDICATOR_SPAWN_EGG, + WANDERING_TRADER_SPAWN_EGG, + WITCH_SPAWN_EGG, + WITHER_SKELETON_SPAWN_EGG, + WOLF_SPAWN_EGG, + ZOGLIN_SPAWN_EGG, + ZOMBIE_SPAWN_EGG, + ZOMBIE_HORSE_SPAWN_EGG, + ZOMBIE_VILLAGER_SPAWN_EGG, + ZOMBIFIED_PIGLIN_SPAWN_EGG, + EXPERIENCE_BOTTLE, + FIRE_CHARGE, + WRITABLE_BOOK, + WRITTEN_BOOK, + ITEM_FRAME, + FLOWER_POT, + CARROT, + POTATO, + BAKED_POTATO, + POISONOUS_POTATO, + MAP, + GOLDEN_CARROT, + SKELETON_SKULL, + WITHER_SKELETON_SKULL, + PLAYER_HEAD, + ZOMBIE_HEAD, + CREEPER_HEAD, + DRAGON_HEAD, + NETHER_STAR, + PUMPKIN_PIE, + FIREWORK_ROCKET, + FIREWORK_STAR, + ENCHANTED_BOOK, + NETHER_BRICK, + PRISMARINE_SHARD, + PRISMARINE_CRYSTALS, + RABBIT, + COOKED_RABBIT, + RABBIT_STEW, + RABBIT_FOOT, + RABBIT_HIDE, + ARMOR_STAND, + IRON_HORSE_ARMOR, + GOLDEN_HORSE_ARMOR, + DIAMOND_HORSE_ARMOR, + LEATHER_HORSE_ARMOR, + LEAD, + NAME_TAG, + COMMAND_BLOCK_MINECART, + MUTTON, + COOKED_MUTTON, + WHITE_BANNER, + ORANGE_BANNER, + MAGENTA_BANNER, + LIGHT_BLUE_BANNER, + YELLOW_BANNER, + LIME_BANNER, + PINK_BANNER, + GRAY_BANNER, + LIGHT_GRAY_BANNER, + CYAN_BANNER, + PURPLE_BANNER, + BLUE_BANNER, + BROWN_BANNER, + GREEN_BANNER, + RED_BANNER, + BLACK_BANNER, + END_CRYSTAL, + CHORUS_FRUIT, + POPPED_CHORUS_FRUIT, + BEETROOT, + BEETROOT_SEEDS, + BEETROOT_SOUP, + DRAGON_BREATH, + SPLASH_POTION, + SPECTRAL_ARROW, + TIPPED_ARROW, + LINGERING_POTION, + SHIELD, + TOTEM_OF_UNDYING, + SHULKER_SHELL, + IRON_NUGGET, + KNOWLEDGE_BOOK, + DEBUG_STICK, + MUSIC_DISC_13, + MUSIC_DISC_CAT, + MUSIC_DISC_BLOCKS, + MUSIC_DISC_CHIRP, + MUSIC_DISC_FAR, + MUSIC_DISC_MALL, + MUSIC_DISC_MELLOHI, + MUSIC_DISC_STAL, + MUSIC_DISC_STRAD, + MUSIC_DISC_WARD, + MUSIC_DISC_11, + MUSIC_DISC_WAIT, + MUSIC_DISC_PIGSTEP, + TRIDENT, + PHANTOM_MEMBRANE, + NAUTILUS_SHELL, + HEART_OF_THE_SEA, + CROSSBOW, + SUSPICIOUS_STEW, + LOOM, + FLOWER_BANNER_PATTERN, + CREEPER_BANNER_PATTERN, + SKULL_BANNER_PATTERN, + MOJANG_BANNER_PATTERN, + GLOBE_BANNER_PATTERN, + PIGLIN_BANNER_PATTERN, + COMPOSTER, + BARREL, + SMOKER, + BLAST_FURNACE, + CARTOGRAPHY_TABLE, + FLETCHING_TABLE, + GRINDSTONE, + SMITHING_TABLE, + STONECUTTER, + BELL, + LANTERN, + SOUL_LANTERN, + SWEET_BERRIES, + CAMPFIRE, + SOUL_CAMPFIRE, + SHROOMLIGHT, + HONEYCOMB, + BEE_NEST, + BEEHIVE, + HONEY_BOTTLE, + HONEYCOMB_BLOCK, + LODESTONE, + CRYING_OBSIDIAN, + BLACKSTONE, + BLACKSTONE_SLAB, + BLACKSTONE_STAIRS, + GILDED_BLACKSTONE, + POLISHED_BLACKSTONE, + POLISHED_BLACKSTONE_SLAB, + POLISHED_BLACKSTONE_STAIRS, + CHISELED_POLISHED_BLACKSTONE, + POLISHED_BLACKSTONE_BRICKS, + POLISHED_BLACKSTONE_BRICK_SLAB, + POLISHED_BLACKSTONE_BRICK_STAIRS, + CRACKED_POLISHED_BLACKSTONE_BRICKS, + RESPAWN_ANCHOR, + WATER, + LAVA, + TALL_SEAGRASS, + PISTON_HEAD, + MOVING_PISTON, + WALL_TORCH, + FIRE, + SOUL_FIRE, + REDSTONE_WIRE, + OAK_WALL_SIGN, + SPRUCE_WALL_SIGN, + BIRCH_WALL_SIGN, + ACACIA_WALL_SIGN, + JUNGLE_WALL_SIGN, + DARK_OAK_WALL_SIGN, + REDSTONE_WALL_TORCH, + SOUL_WALL_TORCH, + NETHER_PORTAL, + ATTACHED_PUMPKIN_STEM, + ATTACHED_MELON_STEM, + PUMPKIN_STEM, + MELON_STEM, + END_PORTAL, + COCOA, + TRIPWIRE, + POTTED_OAK_SAPLING, + POTTED_SPRUCE_SAPLING, + POTTED_BIRCH_SAPLING, + POTTED_JUNGLE_SAPLING, + POTTED_ACACIA_SAPLING, + POTTED_DARK_OAK_SAPLING, + POTTED_FERN, + POTTED_DANDELION, + POTTED_POPPY, + POTTED_BLUE_ORCHID, + POTTED_ALLIUM, + POTTED_AZURE_BLUET, + POTTED_RED_TULIP, + POTTED_ORANGE_TULIP, + POTTED_WHITE_TULIP, + POTTED_PINK_TULIP, + POTTED_OXEYE_DAISY, + POTTED_CORNFLOWER, + POTTED_LILY_OF_THE_VALLEY, + POTTED_WITHER_ROSE, + POTTED_RED_MUSHROOM, + POTTED_BROWN_MUSHROOM, + POTTED_DEAD_BUSH, + POTTED_CACTUS, + CARROTS, + POTATOES, + SKELETON_WALL_SKULL, + WITHER_SKELETON_WALL_SKULL, + ZOMBIE_WALL_HEAD, + PLAYER_WALL_HEAD, + CREEPER_WALL_HEAD, + DRAGON_WALL_HEAD, + WHITE_WALL_BANNER, + ORANGE_WALL_BANNER, + MAGENTA_WALL_BANNER, + LIGHT_BLUE_WALL_BANNER, + YELLOW_WALL_BANNER, + LIME_WALL_BANNER, + PINK_WALL_BANNER, + GRAY_WALL_BANNER, + LIGHT_GRAY_WALL_BANNER, + CYAN_WALL_BANNER, + PURPLE_WALL_BANNER, + BLUE_WALL_BANNER, + BROWN_WALL_BANNER, + GREEN_WALL_BANNER, + RED_WALL_BANNER, + BLACK_WALL_BANNER, + BEETROOTS, + END_GATEWAY, + FROSTED_ICE, + KELP_PLANT, + DEAD_TUBE_CORAL_WALL_FAN, + DEAD_BRAIN_CORAL_WALL_FAN, + DEAD_BUBBLE_CORAL_WALL_FAN, + DEAD_FIRE_CORAL_WALL_FAN, + DEAD_HORN_CORAL_WALL_FAN, + TUBE_CORAL_WALL_FAN, + BRAIN_CORAL_WALL_FAN, + BUBBLE_CORAL_WALL_FAN, + FIRE_CORAL_WALL_FAN, + HORN_CORAL_WALL_FAN, + BAMBOO_SAPLING, + POTTED_BAMBOO, + VOID_AIR, + CAVE_AIR, + BUBBLE_COLUMN, + SWEET_BERRY_BUSH, + WEEPING_VINES_PLANT, + TWISTING_VINES_PLANT, + CRIMSON_WALL_SIGN, + WARPED_WALL_SIGN, + POTTED_CRIMSON_FUNGUS, + POTTED_WARPED_FUNGUS, + POTTED_CRIMSON_ROOTS, + POTTED_WARPED_ROOTS, + + // 1.17 additions + AMETHYST_BLOCK(MCVersion.MC1_17), + AMETHYST_CLUSTER(MCVersion.MC1_17), + AMETHYST_SHARD(MCVersion.MC1_17), + BUDDING_AMETHYST(MCVersion.MC1_17), + SMALL_AMETHYST_BUD(MCVersion.MC1_17), + MEDIUM_AMETHYST_BUD(MCVersion.MC1_17), + LARGE_AMETHYST_BUD(MCVersion.MC1_17), + AXOLOTL_BUCKET(MCVersion.MC1_17), + AXOLOTL_SPAWN_EGG(MCVersion.MC1_17), + AZALEA(MCVersion.MC1_17), + AZALEA_LEAVES(MCVersion.MC1_17), + FLOWERING_AZALEA(MCVersion.MC1_17), + FLOWERING_AZALEA_LEAVES(MCVersion.MC1_17), + POTTED_AZALEA_BUSH(MCVersion.MC1_17), + POTTED_FLOWERING_AZALEA_BUSH(MCVersion.MC1_17), + BUNDLE(MCVersion.MC1_17), + CANDLE(MCVersion.MC1_17), + WHITE_CANDLE(MCVersion.MC1_17), + ORANGE_CANDLE(MCVersion.MC1_17), + MAGENTA_CANDLE(MCVersion.MC1_17), + LIGHT_BLUE_CANDLE(MCVersion.MC1_17), + YELLOW_CANDLE(MCVersion.MC1_17), + LIME_CANDLE(MCVersion.MC1_17), + PINK_CANDLE(MCVersion.MC1_17), + GRAY_CANDLE(MCVersion.MC1_17), + LIGHT_GRAY_CANDLE(MCVersion.MC1_17), + CYAN_CANDLE(MCVersion.MC1_17), + PURPLE_CANDLE(MCVersion.MC1_17), + BLUE_CANDLE(MCVersion.MC1_17), + BROWN_CANDLE(MCVersion.MC1_17), + GREEN_CANDLE(MCVersion.MC1_17), + RED_CANDLE(MCVersion.MC1_17), + BLACK_CANDLE(MCVersion.MC1_17), + CANDLE_CAKE(MCVersion.MC1_17), + WHITE_CANDLE_CAKE(MCVersion.MC1_17), + ORANGE_CANDLE_CAKE(MCVersion.MC1_17), + MAGENTA_CANDLE_CAKE(MCVersion.MC1_17), + LIGHT_BLUE_CANDLE_CAKE(MCVersion.MC1_17), + YELLOW_CANDLE_CAKE(MCVersion.MC1_17), + LIME_CANDLE_CAKE(MCVersion.MC1_17), + PINK_CANDLE_CAKE(MCVersion.MC1_17), + GRAY_CANDLE_CAKE(MCVersion.MC1_17), + LIGHT_GRAY_CANDLE_CAKE(MCVersion.MC1_17), + CYAN_CANDLE_CAKE(MCVersion.MC1_17), + PURPLE_CANDLE_CAKE(MCVersion.MC1_17), + BLUE_CANDLE_CAKE(MCVersion.MC1_17), + BROWN_CANDLE_CAKE(MCVersion.MC1_17), + GREEN_CANDLE_CAKE(MCVersion.MC1_17), + RED_CANDLE_CAKE(MCVersion.MC1_17), + BLACK_CANDLE_CAKE(MCVersion.MC1_17), + CALCITE(MCVersion.MC1_17), + CAVE_VINES(MCVersion.MC1_17), + CAVE_VINES_PLANT(MCVersion.MC1_17), + COPPER_BLOCK(MCVersion.MC1_17), + COPPER_INGOT(MCVersion.MC1_17), + COPPER_ORE(MCVersion.MC1_17), + EXPOSED_COPPER(MCVersion.MC1_17), + WEATHERED_COPPER(MCVersion.MC1_17), + OXIDIZED_COPPER(MCVersion.MC1_17), + CUT_COPPER(MCVersion.MC1_17), + EXPOSED_CUT_COPPER(MCVersion.MC1_17), + WEATHERED_CUT_COPPER(MCVersion.MC1_17), + OXIDIZED_CUT_COPPER(MCVersion.MC1_17), + CUT_COPPER_STAIRS(MCVersion.MC1_17), + EXPOSED_CUT_COPPER_STAIRS(MCVersion.MC1_17), + WEATHERED_CUT_COPPER_STAIRS(MCVersion.MC1_17), + OXIDIZED_CUT_COPPER_STAIRS(MCVersion.MC1_17), + CUT_COPPER_SLAB(MCVersion.MC1_17), + EXPOSED_CUT_COPPER_SLAB(MCVersion.MC1_17), + WEATHERED_CUT_COPPER_SLAB(MCVersion.MC1_17), + OXIDIZED_CUT_COPPER_SLAB(MCVersion.MC1_17), + RAW_COPPER(MCVersion.MC1_17), + RAW_COPPER_BLOCK(MCVersion.MC1_17), + WAXED_COPPER_BLOCK(MCVersion.MC1_17), + WAXED_EXPOSED_COPPER(MCVersion.MC1_17), + WAXED_WEATHERED_COPPER(MCVersion.MC1_17), + WAXED_OXIDIZED_COPPER(MCVersion.MC1_17), + WAXED_CUT_COPPER(MCVersion.MC1_17), + WAXED_EXPOSED_CUT_COPPER(MCVersion.MC1_17), + WAXED_WEATHERED_CUT_COPPER(MCVersion.MC1_17), + WAXED_OXIDIZED_CUT_COPPER(MCVersion.MC1_17), + WAXED_CUT_COPPER_STAIRS(MCVersion.MC1_17), + WAXED_EXPOSED_CUT_COPPER_STAIRS(MCVersion.MC1_17), + WAXED_WEATHERED_CUT_COPPER_STAIRS(MCVersion.MC1_17), + WAXED_OXIDIZED_CUT_COPPER_STAIRS(MCVersion.MC1_17), + WAXED_CUT_COPPER_SLAB(MCVersion.MC1_17), + WAXED_EXPOSED_CUT_COPPER_SLAB(MCVersion.MC1_17), + WAXED_WEATHERED_CUT_COPPER_SLAB(MCVersion.MC1_17), + WAXED_OXIDIZED_CUT_COPPER_SLAB(MCVersion.MC1_17), + CHISELED_DEEPSLATE(MCVersion.MC1_17), + COBBLED_DEEPSLATE(MCVersion.MC1_17), + COBBLED_DEEPSLATE_SLAB(MCVersion.MC1_17), + COBBLED_DEEPSLATE_STAIRS(MCVersion.MC1_17), + COBBLED_DEEPSLATE_WALL(MCVersion.MC1_17), + CRACKED_DEEPSLATE_BRICKS(MCVersion.MC1_17), + CRACKED_DEEPSLATE_TILES(MCVersion.MC1_17), + DEEPSLATE(MCVersion.MC1_17), + DEEPSLATE_BRICKS(MCVersion.MC1_17), + DEEPSLATE_BRICK_SLAB(MCVersion.MC1_17), + DEEPSLATE_BRICK_STAIRS(MCVersion.MC1_17), + DEEPSLATE_BRICK_WALL(MCVersion.MC1_17), + DEEPSLATE_COAL_ORE(MCVersion.MC1_17), + DEEPSLATE_COPPER_ORE(MCVersion.MC1_17), + DEEPSLATE_DIAMOND_ORE(MCVersion.MC1_17), + DEEPSLATE_EMERALD_ORE(MCVersion.MC1_17), + DEEPSLATE_GOLD_ORE(MCVersion.MC1_17), + DEEPSLATE_IRON_ORE(MCVersion.MC1_17), + DEEPSLATE_LAPIS_ORE(MCVersion.MC1_17), + DEEPSLATE_REDSTONE_ORE(MCVersion.MC1_17), + DEEPSLATE_TILES(MCVersion.MC1_17), + DEEPSLATE_TILE_SLAB(MCVersion.MC1_17), + DEEPSLATE_TILE_STAIRS(MCVersion.MC1_17), + DEEPSLATE_TILE_WALL(MCVersion.MC1_17), + INFESTED_DEEPSLATE(MCVersion.MC1_17), + POLISHED_DEEPSLATE(MCVersion.MC1_17), + POLISHED_DEEPSLATE_SLAB(MCVersion.MC1_17), + POLISHED_DEEPSLATE_STAIRS(MCVersion.MC1_17), + POLISHED_DEEPSLATE_WALL(MCVersion.MC1_17), + BIG_DRIPLEAF(MCVersion.MC1_17), + SMALL_DRIPLEAF(MCVersion.MC1_17), + BIG_DRIPLEAF_STEM(MCVersion.MC1_17), + DRIPSTONE_BLOCK(MCVersion.MC1_17), + POINTED_DRIPSTONE(MCVersion.MC1_17), + GLOW_BERRIES(MCVersion.MC1_17), + GLOW_INK_SAC(MCVersion.MC1_17), + GLOW_ITEM_FRAME(MCVersion.MC1_17), + GLOW_LICHEN(MCVersion.MC1_17), + GOAT_SPAWN_EGG(MCVersion.MC1_17), + GLOW_SQUID_SPAWN_EGG(MCVersion.MC1_17), + HANGING_ROOTS(MCVersion.MC1_17), + LIGHT(MCVersion.MC1_17), + LIGHTNING_ROD(MCVersion.MC1_17), + MOSS_CARPET(MCVersion.MC1_17), + MOSS_BLOCK(MCVersion.MC1_17), + POWDER_SNOW(MCVersion.MC1_17), + POWDER_SNOW_BUCKET(MCVersion.MC1_17), + POWDER_SNOW_CAULDRON(MCVersion.MC1_17), + RAW_GOLD(MCVersion.MC1_17), + RAW_GOLD_BLOCK(MCVersion.MC1_17), + RAW_IRON(MCVersion.MC1_17), + RAW_IRON_BLOCK(MCVersion.MC1_17), + ROOTED_DIRT(MCVersion.MC1_17), + SCULK_SENSOR(MCVersion.MC1_17), + SMOOTH_BASALT(MCVersion.MC1_17), + SPORE_BLOSSOM(MCVersion.MC1_17), + SPYGLASS(MCVersion.MC1_17), + TINTED_GLASS(MCVersion.MC1_17), + TUFF(MCVersion.MC1_17), + GRASS_PATH(MCVersion.MC1_9, MCVersion.MC1_16_X), + DIRT_PATH(MCVersion.MC1_17), // changed from GRASS_PATH + WATER_CAULDRON(MCVersion.MC1_17), + LAVA_CAULDRON(MCVersion.MC1_17), + + // 1.18 additions + MUSIC_DISC_OTHERSIDE(MCVersion.MC1_18), + + // 1.19 additions + ACACIA_CHEST_BOAT(MCVersion.MC1_19), + BIRCH_CHEST_BOAT(MCVersion.MC1_19), + DARK_OAK_CHEST_BOAT(MCVersion.MC1_19), + JUNGLE_CHEST_BOAT(MCVersion.MC1_19), + OAK_CHEST_BOAT(MCVersion.MC1_19), + SPRUCE_CHEST_BOAT(MCVersion.MC1_19), + REINFORCED_DEEPSLATE(MCVersion.MC1_19), + DISC_FRAGMENT_5(MCVersion.MC1_19), + ECHO_SHARD(MCVersion.MC1_19), + OCHRE_FROGLIGHT(MCVersion.MC1_19), + PEARLESCENT_FROGLIGHT(MCVersion.MC1_19), + VERDANT_FROGLIGHT(MCVersion.MC1_19), + FROGSPAWN(MCVersion.MC1_19), + GOAT_HORN(MCVersion.MC1_19), + MANGROVE_BOAT(MCVersion.MC1_19), + MANGROVE_BUTTON(MCVersion.MC1_19), + MANGROVE_CHEST_BOAT(MCVersion.MC1_19), + MANGROVE_DOOR(MCVersion.MC1_19), + MANGROVE_FENCE(MCVersion.MC1_19), + MANGROVE_FENCE_GATE(MCVersion.MC1_19), + MANGROVE_LEAVES(MCVersion.MC1_19), + MANGROVE_LOG(MCVersion.MC1_19), + MANGROVE_PLANKS(MCVersion.MC1_19), + MANGROVE_PRESSURE_PLATE(MCVersion.MC1_19), + MANGROVE_PROPAGULE(MCVersion.MC1_19), + MANGROVE_ROOTS(MCVersion.MC1_19), + MANGROVE_SLAB(MCVersion.MC1_19), + MANGROVE_SIGN(MCVersion.MC1_19), + MANGROVE_STAIRS(MCVersion.MC1_19), + MANGROVE_TRAPDOOR(MCVersion.MC1_19), + MANGROVE_WALL_SIGN(MCVersion.MC1_19), + MANGROVE_WOOD(MCVersion.MC1_19), + MUDDY_MANGROVE_ROOTS(MCVersion.MC1_19), + POTTED_MANGROVE_PROPAGULE(MCVersion.MC1_19), + STRIPPED_MANGROVE_LOG(MCVersion.MC1_19), + STRIPPED_MANGROVE_WOOD(MCVersion.MC1_19), + MUD(MCVersion.MC1_19), + MUD_BRICK_SLAB(MCVersion.MC1_19), + MUD_BRICK_STAIRS(MCVersion.MC1_19), + MUD_BRICK_WALL(MCVersion.MC1_19), + MUD_BRICKS(MCVersion.MC1_19), + PACKED_MUD(MCVersion.MC1_19), + MUSIC_DISC_5(MCVersion.MC1_19), + RECOVERY_COMPASS(MCVersion.MC1_19), + SCULK(MCVersion.MC1_19), + SCULK_VEIN(MCVersion.MC1_19), + SCULK_CATALYST(MCVersion.MC1_19), + SCULK_SHRIEKER(MCVersion.MC1_19), + ALLAY_SPAWN_EGG(MCVersion.MC1_19), + FROG_SPAWN_EGG(MCVersion.MC1_19), + TADPOLE_SPAWN_EGG(MCVersion.MC1_19), + WARDEN_SPAWN_EGG(MCVersion.MC1_19), + TADPOLE_BUCKET(MCVersion.MC1_19), + + // 1.19.3 additions + ENDER_DRAGON_SPAWN_EGG(MCVersion.MC1_19_3), + IRON_GOLEM_SPAWN_EGG(MCVersion.MC1_19_3), + SNOW_GOLEM_SPAWN_EGG(MCVersion.MC1_19_3), + WITHER_SPAWN_EGG(MCVersion.MC1_19_3), + + // 1.19.3 experimental additions for 1.20 (enabled with feature flag on this version) + BAMBOO_BLOCK(MCVersion.MC1_19_3), + BAMBOO_BUTTON(MCVersion.MC1_19_3), + BAMBOO_DOOR(MCVersion.MC1_19_3), + BAMBOO_FENCE(MCVersion.MC1_19_3), + BAMBOO_FENCE_GATE(MCVersion.MC1_19_3), + BAMBOO_MOSAIC(MCVersion.MC1_19_3), + BAMBOO_PLANKS(MCVersion.MC1_19_3), + BAMBOO_PRESSURE_PLATE(MCVersion.MC1_19_3), + BAMBOO_RAFT(MCVersion.MC1_19_3), + BAMBOO_CHEST_RAFT(MCVersion.MC1_19_3), + BAMBOO_SIGN(MCVersion.MC1_19_3), + BAMBOO_SLAB(MCVersion.MC1_19_3), + BAMBOO_MOSAIC_SLAB(MCVersion.MC1_19_3), + BAMBOO_STAIRS(MCVersion.MC1_19_3), + BAMBOO_MOSAIC_STAIRS(MCVersion.MC1_19_3), + BAMBOO_TRAPDOOR(MCVersion.MC1_19_3), + STRIPPED_BAMBOO_BLOCK(MCVersion.MC1_19_3), + CHISELED_BOOKSHELF(MCVersion.MC1_19_3), + PIGLIN_HEAD(MCVersion.MC1_19_3), + PIGLIN_WALL_HEAD(MCVersion.MC1_19_3), + ACACIA_HANGING_SIGN(MCVersion.MC1_19_3), + BAMBOO_HANGING_SIGN(MCVersion.MC1_19_3), + BIRCH_HANGING_SIGN(MCVersion.MC1_19_3), + CRIMSON_HANGING_SIGN(MCVersion.MC1_19_3), + DARK_OAK_HANGING_SIGN(MCVersion.MC1_19_3), + JUNGLE_HANGING_SIGN(MCVersion.MC1_19_3), + MANGROVE_HANGING_SIGN(MCVersion.MC1_19_3), + OAK_HANGING_SIGN(MCVersion.MC1_19_3), + SPRUCE_HANGING_SIGN(MCVersion.MC1_19_3), + WARPED_HANGING_SIGN(MCVersion.MC1_19_3), + BAMBOO_WALL_SIGN(MCVersion.MC1_19_3), + ACACIA_WALL_HANGING_SIGN(MCVersion.MC1_19_3), + BAMBOO_WALL_HANGING_SIGN(MCVersion.MC1_19_3), + BIRCH_WALL_HANGING_SIGN(MCVersion.MC1_19_3), + CRIMSON_WALL_HANGING_SIGN(MCVersion.MC1_19_3), + DARK_OAK_WALL_HANGING_SIGN(MCVersion.MC1_19_3), + JUNGLE_WALL_HANGING_SIGN(MCVersion.MC1_19_3), + MANGROVE_WALL_HANGING_SIGN(MCVersion.MC1_19_3), + OAK_WALL_HANGING_SIGN(MCVersion.MC1_19_3), + SPRUCE_WALL_HANGING_SIGN(MCVersion.MC1_19_3), + WARPED_WALL_HANGING_SIGN(MCVersion.MC1_19_3), + CAMEL_SPAWN_EGG(MCVersion.MC1_19_3), + + // 1.19.4 experimental additions for 1.20 (enabled with feature flag on this version) + BRUSH(MCVersion.MC1_19_4), + CHERRY_BOAT(MCVersion.MC1_19_4), + CHERRY_BUTTON(MCVersion.MC1_19_4), + CHERRY_CHEST_BOAT(MCVersion.MC1_19_4), + CHERRY_DOOR(MCVersion.MC1_19_4), + CHERRY_FENCE(MCVersion.MC1_19_4), + CHERRY_FENCE_GATE(MCVersion.MC1_19_4), + CHERRY_HANGING_SIGN(MCVersion.MC1_19_4), + CHERRY_LEAVES(MCVersion.MC1_19_4), + CHERRY_LOG(MCVersion.MC1_19_4), + CHERRY_PLANKS(MCVersion.MC1_19_4), + CHERRY_PRESSURE_PLATE(MCVersion.MC1_19_4), + CHERRY_SAPLING(MCVersion.MC1_19_4), + CHERRY_SIGN(MCVersion.MC1_19_4), + CHERRY_SLAB(MCVersion.MC1_19_4), + CHERRY_STAIRS(MCVersion.MC1_19_4), + CHERRY_TRAPDOOR(MCVersion.MC1_19_4), + CHERRY_WALL_HANGING_SIGN(MCVersion.MC1_19_4), + CHERRY_WALL_SIGN(MCVersion.MC1_19_4), + CHERRY_WOOD(MCVersion.MC1_19_4), + STRIPPED_CHERRY_LOG(MCVersion.MC1_19_4), + STRIPPED_CHERRY_WOOD(MCVersion.MC1_19_4), + PINK_PETALS(MCVersion.MC1_19_4), + DECORATED_POT(MCVersion.MC1_19_4), + SNIFFER_SPAWN_EGG(MCVersion.MC1_19_4), + SUSPICIOUS_SAND(MCVersion.MC1_19_4), + TORCHFLOWER(MCVersion.MC1_19_4), + TORCHFLOWER_CROP(MCVersion.MC1_19_4), + TORCHFLOWER_SEEDS(MCVersion.MC1_19_4), + POTTED_CHERRY_SAPLING(MCVersion.MC1_19_4), + POTTED_TORCHFLOWER(MCVersion.MC1_19_4), + COAST_ARMOR_TRIM_SMITHING_TEMPLATE(MCVersion.MC1_19_4), + DUNE_ARMOR_TRIM_SMITHING_TEMPLATE(MCVersion.MC1_19_4), + EYE_ARMOR_TRIM_SMITHING_TEMPLATE(MCVersion.MC1_19_4), + NETHERITE_UPGRADE_SMITHING_TEMPLATE(MCVersion.MC1_19_4), + RIB_ARMOR_TRIM_SMITHING_TEMPLATE(MCVersion.MC1_19_4), + SENTRY_ARMOR_TRIM_SMITHING_TEMPLATE(MCVersion.MC1_19_4), + SNOUT_ARMOR_TRIM_SMITHING_TEMPLATE(MCVersion.MC1_19_4), + SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE(MCVersion.MC1_19_4), + TIDE_ARMOR_TRIM_SMITHING_TEMPLATE(MCVersion.MC1_19_4), + VEX_ARMOR_TRIM_SMITHING_TEMPLATE(MCVersion.MC1_19_4), + WARD_ARMOR_TRIM_SMITHING_TEMPLATE(MCVersion.MC1_19_4), + WILD_ARMOR_TRIM_SMITHING_TEMPLATE(MCVersion.MC1_19_4), + + // Other 1.20 additions + SUSPICIOUS_GRAVEL(MCVersion.MC1_20), + PITCHER_PLANT(MCVersion.MC1_20), + SNIFFER_EGG(MCVersion.MC1_20), + CALIBRATED_SCULK_SENSOR(MCVersion.MC1_20), + PITCHER_CROP(MCVersion.MC1_20), + PITCHER_POD(MCVersion.MC1_20), + MUSIC_DISC_RELIC(MCVersion.MC1_20), + WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE(MCVersion.MC1_20), + SHAPER_ARMOR_TRIM_SMITHING_TEMPLATE(MCVersion.MC1_20), + SILENCE_ARMOR_TRIM_SMITHING_TEMPLATE(MCVersion.MC1_20), + RAISER_ARMOR_TRIM_SMITHING_TEMPLATE(MCVersion.MC1_20), + HOST_ARMOR_TRIM_SMITHING_TEMPLATE(MCVersion.MC1_20), + ANGLER_POTTERY_SHERD(MCVersion.MC1_20), + ARCHER_POTTERY_SHERD(MCVersion.MC1_20), + ARMS_UP_POTTERY_SHERD(MCVersion.MC1_20), + BLADE_POTTERY_SHERD(MCVersion.MC1_20), + BREWER_POTTERY_SHERD(MCVersion.MC1_20), + BURN_POTTERY_SHERD(MCVersion.MC1_20), + DANGER_POTTERY_SHERD(MCVersion.MC1_20), + EXPLORER_POTTERY_SHERD(MCVersion.MC1_20), + FRIEND_POTTERY_SHERD(MCVersion.MC1_20), + HEART_POTTERY_SHERD(MCVersion.MC1_20), + HEARTBREAK_POTTERY_SHERD(MCVersion.MC1_20), + HOWL_POTTERY_SHERD(MCVersion.MC1_20), + MINER_POTTERY_SHERD(MCVersion.MC1_20), + MOURNER_POTTERY_SHERD(MCVersion.MC1_20), + PLENTY_POTTERY_SHERD(MCVersion.MC1_20), + PRIZE_POTTERY_SHERD(MCVersion.MC1_20), + SHEAF_POTTERY_SHERD(MCVersion.MC1_20), + SHELTER_POTTERY_SHERD(MCVersion.MC1_20), + SKULL_POTTERY_SHERD(MCVersion.MC1_20), + SNORT_POTTERY_SHERD(MCVersion.MC1_20), + + // 1.20.3 experimental additions for 1.21 + CRAFTER(MCVersion.MC1_20_4), + CHISELED_COPPER(MCVersion.MC1_20_4), + EXPOSED_CHISELED_COPPER(MCVersion.MC1_20_4), + WEATHERED_CHISELED_COPPER(MCVersion.MC1_20_4), + OXIDIZED_CHISELED_COPPER(MCVersion.MC1_20_4), + WAXED_CHISELED_COPPER(MCVersion.MC1_20_4), + WAXED_EXPOSED_CHISELED_COPPER(MCVersion.MC1_20_4), + WAXED_WEATHERED_CHISELED_COPPER(MCVersion.MC1_20_4), + WAXED_OXIDIZED_CHISELED_COPPER(MCVersion.MC1_20_4), + COPPER_BULB(MCVersion.MC1_20_4), + EXPOSED_COPPER_BULB(MCVersion.MC1_20_4), + WEATHERED_COPPER_BULB(MCVersion.MC1_20_4), + OXIDIZED_COPPER_BULB(MCVersion.MC1_20_4), + WAXED_COPPER_BULB(MCVersion.MC1_20_4), + WAXED_EXPOSED_COPPER_BULB(MCVersion.MC1_20_4), + WAXED_WEATHERED_COPPER_BULB(MCVersion.MC1_20_4), + WAXED_OXIDIZED_COPPER_BULB(MCVersion.MC1_20_4), + COPPER_DOOR(MCVersion.MC1_20_4), + EXPOSED_COPPER_DOOR(MCVersion.MC1_20_4), + WEATHERED_COPPER_DOOR(MCVersion.MC1_20_4), + OXIDIZED_COPPER_DOOR(MCVersion.MC1_20_4), + WAXED_COPPER_DOOR(MCVersion.MC1_20_4), + WAXED_EXPOSED_COPPER_DOOR(MCVersion.MC1_20_4), + WAXED_WEATHERED_COPPER_DOOR(MCVersion.MC1_20_4), + WAXED_OXIDIZED_COPPER_DOOR(MCVersion.MC1_20_4), + COPPER_GRATE(MCVersion.MC1_20_4), + EXPOSED_COPPER_GRATE(MCVersion.MC1_20_4), + WEATHERED_COPPER_GRATE(MCVersion.MC1_20_4), + OXIDIZED_COPPER_GRATE(MCVersion.MC1_20_4), + WAXED_COPPER_GRATE(MCVersion.MC1_20_4), + WAXED_EXPOSED_COPPER_GRATE(MCVersion.MC1_20_4), + WAXED_WEATHERED_COPPER_GRATE(MCVersion.MC1_20_4), + WAXED_OXIDIZED_COPPER_GRATE(MCVersion.MC1_20_4), + COPPER_TRAPDOOR(MCVersion.MC1_20_4), + EXPOSED_COPPER_TRAPDOOR(MCVersion.MC1_20_4), + WEATHERED_COPPER_TRAPDOOR(MCVersion.MC1_20_4), + OXIDIZED_COPPER_TRAPDOOR(MCVersion.MC1_20_4), + WAXED_COPPER_TRAPDOOR(MCVersion.MC1_20_4), + WAXED_EXPOSED_COPPER_TRAPDOOR(MCVersion.MC1_20_4), + WAXED_WEATHERED_COPPER_TRAPDOOR(MCVersion.MC1_20_4), + WAXED_OXIDIZED_COPPER_TRAPDOOR(MCVersion.MC1_20_4), + BREEZE_SPAWN_EGG(MCVersion.MC1_20_4), + TRIAL_SPAWNER(MCVersion.MC1_20_4), + TRIAL_KEY(MCVersion.MC1_20_4), + TUFF_BRICKS(MCVersion.MC1_20_4), + TUFF_BRICK_SLAB(MCVersion.MC1_20_4), + TUFF_BRICK_STAIRS(MCVersion.MC1_20_4), + TUFF_BRICK_WALL(MCVersion.MC1_20_4), + TUFF_SLAB(MCVersion.MC1_20_4), + TUFF_STAIRS(MCVersion.MC1_20_4), + TUFF_WALL(MCVersion.MC1_20_4), + CHISELED_TUFF(MCVersion.MC1_20_4), + POLISHED_TUFF(MCVersion.MC1_20_4), + CHISELED_TUFF_BRICKS(MCVersion.MC1_20_4), + POLISHED_TUFF_SLAB(MCVersion.MC1_20_4), + POLISHED_TUFF_STAIRS(MCVersion.MC1_20_4), + POLISHED_TUFF_WALL(MCVersion.MC1_20_4), + + // 1.20.5 additions + ARMADILLO_SCUTE(MCVersion.MC1_20_6), + ARMADILLO_SPAWN_EGG(MCVersion.MC1_20_6), + WOLF_ARMOR(MCVersion.MC1_20_6), + + // 1.20.5 experimental additions for 1.21 + BREEZE_ROD(MCVersion.MC1_20_6), + BOGGED_SPAWN_EGG(MCVersion.MC1_20_6), + HEAVY_CORE(MCVersion.MC1_20_6), + MACE(MCVersion.MC1_20_6), + OMINOUS_BOTTLE(MCVersion.MC1_20_6), + OMINOUS_TRIAL_KEY(MCVersion.MC1_20_6), + FLOW_BANNER_PATTERN(MCVersion.MC1_20_6), + GUSTER_BANNER_PATTERN(MCVersion.MC1_20_6), + FLOW_ARMOR_TRIM_SMITHING_TEMPLATE(MCVersion.MC1_20_6), + BOLT_ARMOR_TRIM_SMITHING_TEMPLATE(MCVersion.MC1_20_6), + FLOW_POTTERY_SHERD(MCVersion.MC1_20_6), + GUSTER_POTTERY_SHERD(MCVersion.MC1_20_6), + SCRAPE_POTTERY_SHERD(MCVersion.MC1_20_6), + VAULT(MCVersion.MC1_20_6), + WIND_CHARGE(MCVersion.MC1_20_6), + + // 1.21 additions + MUSIC_DISC_CREATOR(MCVersion.MC1_21), + MUSIC_DISC_CREATOR_MUSIC_BOX(MCVersion.MC1_21), + MUSIC_DISC_PRECIPICE(MCVersion.MC1_21), + + // 1.21.2 additions + BLACK_BUNDLE(MCVersion.MC1_21_3), + BLUE_BUNDLE(MCVersion.MC1_21_3), + BROWN_BUNDLE(MCVersion.MC1_21_3), + CYAN_BUNDLE(MCVersion.MC1_21_3), + GRAY_BUNDLE(MCVersion.MC1_21_3), + GREEN_BUNDLE(MCVersion.MC1_21_3), + LIGHT_BLUE_BUNDLE(MCVersion.MC1_21_3), + LIGHT_GRAY_BUNDLE(MCVersion.MC1_21_3), + LIME_BUNDLE(MCVersion.MC1_21_3), + MAGENTA_BUNDLE(MCVersion.MC1_21_3), + ORANGE_BUNDLE(MCVersion.MC1_21_3), + PINK_BUNDLE(MCVersion.MC1_21_3), + PURPLE_BUNDLE(MCVersion.MC1_21_3), + RED_BUNDLE(MCVersion.MC1_21_3), + WHITE_BUNDLE(MCVersion.MC1_21_3), + YELLOW_BUNDLE(MCVersion.MC1_21_3), + BORDURE_INDENTED_BANNER_PATTERN(MCVersion.MC1_21_3), + FIELD_MASONED_BANNER_PATTERN(MCVersion.MC1_21_3), + + // 1.21.2 experimental additions + CREAKING_HEART(MCVersion.MC1_21_3), + CREAKING_SPAWN_EGG(MCVersion.MC1_21_3), + PALE_HANGING_MOSS(MCVersion.MC1_21_3), + PALE_MOSS_BLOCK(MCVersion.MC1_21_3), + PALE_MOSS_CARPET(MCVersion.MC1_21_3), + PALE_OAK_BOAT(MCVersion.MC1_21_3), + PALE_OAK_BUTTON(MCVersion.MC1_21_3), + PALE_OAK_CHEST_BOAT(MCVersion.MC1_21_3), + PALE_OAK_DOOR(MCVersion.MC1_21_3), + PALE_OAK_FENCE(MCVersion.MC1_21_3), + PALE_OAK_FENCE_GATE(MCVersion.MC1_21_3), + PALE_OAK_HANGING_SIGN(MCVersion.MC1_21_3), + PALE_OAK_LEAVES(MCVersion.MC1_21_3), + PALE_OAK_LOG(MCVersion.MC1_21_3), + PALE_OAK_PLANKS(MCVersion.MC1_21_3), + PALE_OAK_PRESSURE_PLATE(MCVersion.MC1_21_3), + PALE_OAK_SAPLING(MCVersion.MC1_21_3), + PALE_OAK_SIGN(MCVersion.MC1_21_3), + PALE_OAK_SLAB(MCVersion.MC1_21_3), + PALE_OAK_STAIRS(MCVersion.MC1_21_3), + PALE_OAK_TRAPDOOR(MCVersion.MC1_21_3), + PALE_OAK_WALL_HANGING_SIGN(MCVersion.MC1_21_3), + PALE_OAK_WALL_SIGN(MCVersion.MC1_21_3), + PALE_OAK_WOOD(MCVersion.MC1_21_3), + POTTED_PALE_OAK_SAPLING(MCVersion.MC1_21_3), + STRIPPED_PALE_OAK_LOG(MCVersion.MC1_21_3), + STRIPPED_PALE_OAK_WOOD(MCVersion.MC1_21_3), + + // 1.21.4 additions + CLOSED_EYEBLOSSOM(MCVersion.MC1_21_4), + OPEN_EYEBLOSSOM(MCVersion.MC1_21_4), + POTTED_CLOSED_EYEBLOSSOM(MCVersion.MC1_21_4), + POTTED_OPEN_EYEBLOSSOM(MCVersion.MC1_21_4), + RESIN_BLOCK(MCVersion.MC1_21_4), + RESIN_BRICK(MCVersion.MC1_21_4), + RESIN_BRICKS(MCVersion.MC1_21_4), + RESIN_BRICK_SLAB(MCVersion.MC1_21_4), + RESIN_BRICK_STAIRS(MCVersion.MC1_21_4), + RESIN_BRICK_WALL(MCVersion.MC1_21_4), + RESIN_CLUMP(MCVersion.MC1_21_4), + CHISELED_RESIN_BRICKS(MCVersion.MC1_21_4), + + // 1.21.5 additions + BLUE_EGG(MCVersion.MC1_21_5), + BROWN_EGG(MCVersion.MC1_21_5), + BUSH(MCVersion.MC1_21_5), + CACTUS_FLOWER(MCVersion.MC1_21_5), + FIREFLY_BUSH(MCVersion.MC1_21_5), + LEAF_LITTER(MCVersion.MC1_21_5), + SHORT_DRY_GRASS(MCVersion.MC1_21_5), + TALL_DRY_GRASS(MCVersion.MC1_21_5), + TEST_BLOCK(MCVersion.MC1_21_5), + TEST_INSTANCE_BLOCK(MCVersion.MC1_21_5), + WILDFLOWERS(MCVersion.MC1_21_5), + + // 1.21.6 additions + BLACK_HARNESS(MCVersion.MC1_21_6), + BLUE_HARNESS(MCVersion.MC1_21_6), + BROWN_HARNESS(MCVersion.MC1_21_6), + CYAN_HARNESS(MCVersion.MC1_21_6), + GRAY_HARNESS(MCVersion.MC1_21_6), + GREEN_HARNESS(MCVersion.MC1_21_6), + LIGHT_BLUE_HARNESS(MCVersion.MC1_21_6), + LIGHT_GRAY_HARNESS(MCVersion.MC1_21_6), + LIME_HARNESS(MCVersion.MC1_21_6), + MAGENTA_HARNESS(MCVersion.MC1_21_6), + ORANGE_HARNESS(MCVersion.MC1_21_6), + PINK_HARNESS(MCVersion.MC1_21_6), + PURPLE_HARNESS(MCVersion.MC1_21_6), + RED_HARNESS(MCVersion.MC1_21_6), + WHITE_HARNESS(MCVersion.MC1_21_6), + YELLOW_HARNESS(MCVersion.MC1_21_6), + DRIED_GHAST(MCVersion.MC1_21_6), + HAPPY_GHAST_SPAWN_EGG(MCVersion.MC1_21_6), + MUSIC_DISC_TEARS(MCVersion.MC1_21_6), + + // 1.21.7 additions + MUSIC_DISC_LAVA_CHICKEN(MCVersion.MC1_21_7), + + // 1.21.9 additions + COPPER_AXE(MCVersion.MC1_21_9), + COPPER_BARS(MCVersion.MC1_21_9), + COPPER_BOOTS(MCVersion.MC1_21_9), + COPPER_CHAIN(MCVersion.MC1_21_9), + COPPER_CHEST(MCVersion.MC1_21_9), + COPPER_CHESTPLATE(MCVersion.MC1_21_9), + COPPER_GOLEM_SPAWN_EGG(MCVersion.MC1_21_9), + COPPER_GOLEM_STATUE(MCVersion.MC1_21_9), + COPPER_HELMET(MCVersion.MC1_21_9), + COPPER_HOE(MCVersion.MC1_21_9), + COPPER_HORSE_ARMOR(MCVersion.MC1_21_9), + COPPER_LANTERN(MCVersion.MC1_21_9), + COPPER_LEGGINGS(MCVersion.MC1_21_9), + COPPER_NUGGET(MCVersion.MC1_21_9), + COPPER_PICKAXE(MCVersion.MC1_21_9), + COPPER_SHOVEL(MCVersion.MC1_21_9), + COPPER_SWORD(MCVersion.MC1_21_9), + COPPER_TORCH(MCVersion.MC1_21_9), + COPPER_WALL_TORCH(MCVersion.MC1_21_9), + EXPOSED_COPPER_BARS(MCVersion.MC1_21_9), + EXPOSED_COPPER_CHAIN(MCVersion.MC1_21_9), + EXPOSED_COPPER_CHEST(MCVersion.MC1_21_9), + EXPOSED_COPPER_GOLEM_STATUE(MCVersion.MC1_21_9), + EXPOSED_COPPER_LANTERN(MCVersion.MC1_21_9), + EXPOSED_LIGHTNING_ROD(MCVersion.MC1_21_9), + OXIDIZED_COPPER_BARS(MCVersion.MC1_21_9), + OXIDIZED_COPPER_CHAIN(MCVersion.MC1_21_9), + OXIDIZED_COPPER_CHEST(MCVersion.MC1_21_9), + OXIDIZED_COPPER_GOLEM_STATUE(MCVersion.MC1_21_9), + OXIDIZED_COPPER_LANTERN(MCVersion.MC1_21_9), + OXIDIZED_LIGHTNING_ROD(MCVersion.MC1_21_9), + WAXED_COPPER_BARS(MCVersion.MC1_21_9), + WAXED_COPPER_CHAIN(MCVersion.MC1_21_9), + WAXED_COPPER_CHEST(MCVersion.MC1_21_9), + WAXED_COPPER_GOLEM_STATUE(MCVersion.MC1_21_9), + WAXED_COPPER_LANTERN(MCVersion.MC1_21_9), + WAXED_EXPOSED_COPPER_BARS(MCVersion.MC1_21_9), + WAXED_EXPOSED_COPPER_CHAIN(MCVersion.MC1_21_9), + WAXED_EXPOSED_COPPER_CHEST(MCVersion.MC1_21_9), + WAXED_EXPOSED_COPPER_GOLEM_STATUE(MCVersion.MC1_21_9), + WAXED_EXPOSED_COPPER_LANTERN(MCVersion.MC1_21_9), + WAXED_EXPOSED_LIGHTNING_ROD(MCVersion.MC1_21_9), + WAXED_LIGHTNING_ROD(MCVersion.MC1_21_9), + WAXED_OXIDIZED_COPPER_BARS(MCVersion.MC1_21_9), + WAXED_OXIDIZED_COPPER_CHAIN(MCVersion.MC1_21_9), + WAXED_OXIDIZED_COPPER_CHEST(MCVersion.MC1_21_9), + WAXED_OXIDIZED_COPPER_GOLEM_STATUE(MCVersion.MC1_21_9), + WAXED_OXIDIZED_COPPER_LANTERN(MCVersion.MC1_21_9), + WAXED_OXIDIZED_LIGHTNING_ROD(MCVersion.MC1_21_9), + WAXED_WEATHERED_COPPER_BARS(MCVersion.MC1_21_9), + WAXED_WEATHERED_COPPER_CHAIN(MCVersion.MC1_21_9), + WAXED_WEATHERED_COPPER_CHEST(MCVersion.MC1_21_9), + WAXED_WEATHERED_COPPER_GOLEM_STATUE(MCVersion.MC1_21_9), + WAXED_WEATHERED_COPPER_LANTERN(MCVersion.MC1_21_9), + WAXED_WEATHERED_LIGHTNING_ROD(MCVersion.MC1_21_9), + WEATHERED_COPPER_BARS(MCVersion.MC1_21_9), + WEATHERED_COPPER_CHAIN(MCVersion.MC1_21_9), + WEATHERED_COPPER_CHEST(MCVersion.MC1_21_9), + WEATHERED_COPPER_GOLEM_STATUE(MCVersion.MC1_21_9), + WEATHERED_COPPER_LANTERN(MCVersion.MC1_21_9), + WEATHERED_LIGHTNING_ROD(MCVersion.MC1_21_9), + ACACIA_SHELF(MCVersion.MC1_21_9), + BAMBOO_SHELF(MCVersion.MC1_21_9), + BIRCH_SHELF(MCVersion.MC1_21_9), + CHERRY_SHELF(MCVersion.MC1_21_9), + CRIMSON_SHELF(MCVersion.MC1_21_9), + DARK_OAK_SHELF(MCVersion.MC1_21_9), + JUNGLE_SHELF(MCVersion.MC1_21_9), + MANGROVE_SHELF(MCVersion.MC1_21_9), + OAK_SHELF(MCVersion.MC1_21_9), + PALE_OAK_SHELF(MCVersion.MC1_21_9), + SPRUCE_SHELF(MCVersion.MC1_21_9), + WARPED_SHELF(MCVersion.MC1_21_9), + + // 1.21.11 additions + CAMEL_HUSK_SPAWN_EGG(MCVersion.MC1_21_11), + COPPER_NAUTILUS_ARMOR(MCVersion.MC1_21_11), + COPPER_SPEAR(MCVersion.MC1_21_11), + DIAMOND_NAUTILUS_ARMOR(MCVersion.MC1_21_11), + DIAMOND_SPEAR(MCVersion.MC1_21_11), + GOLDEN_NAUTILUS_ARMOR(MCVersion.MC1_21_11), + GOLDEN_SPEAR(MCVersion.MC1_21_11), + IRON_NAUTILUS_ARMOR(MCVersion.MC1_21_11), + IRON_SPEAR(MCVersion.MC1_21_11), + NAUTILUS_SPAWN_EGG(MCVersion.MC1_21_11), + NETHERITE_HORSE_ARMOR(MCVersion.MC1_21_11), + NETHERITE_NAUTILUS_ARMOR(MCVersion.MC1_21_11), + NETHERITE_SPEAR(MCVersion.MC1_21_11), + PARCHED_SPAWN_EGG(MCVersion.MC1_21_11), + STONE_SPEAR(MCVersion.MC1_21_11), + WOODEN_SPEAR(MCVersion.MC1_21_11), + ZOMBIE_NAUTILUS_SPAWN_EGG(MCVersion.MC1_21_11); + + private final MCVersion since; + private final MCVersion until; + + MCVanillaMaterial() { + this.since = MCVersion.MC1_0; + this.until = MCVersion.FUTURE; + } + + MCVanillaMaterial(MCVersion since) { + this.since = since; + this.until = MCVersion.FUTURE; + } + + MCVanillaMaterial(MCVersion since, MCVersion until) { + this.since = since; + this.until = until; + } -import com.laytonsmith.abstraction.MCMaterialData; - -/** - * - * - */ -public interface MCMaterial { - short getMaxDurability(); - - public int getType(); - public MCMaterialData getData(); - public String getName(); - - public int getMaxStackSize(); - - public boolean hasGravity(); - public boolean isBlock(); - public boolean isBurnable(); - public boolean isEdible(); - public boolean isFlammable(); - public boolean isOccluding(); - public boolean isRecord(); - public boolean isSolid(); - public boolean isTransparent(); + public boolean existsIn(MCVersion version) { + return version.gte(since) && version.lte(until); + } + } } diff --git a/src/main/java/com/laytonsmith/abstraction/blocks/MCSign.java b/src/main/java/com/laytonsmith/abstraction/blocks/MCSign.java index f553734ca9..63ceea104f 100644 --- a/src/main/java/com/laytonsmith/abstraction/blocks/MCSign.java +++ b/src/main/java/com/laytonsmith/abstraction/blocks/MCSign.java @@ -1,15 +1,21 @@ +package com.laytonsmith.abstraction.blocks; +public interface MCSign extends MCBlockState, MCSignText { -package com.laytonsmith.abstraction.blocks; + boolean isWaxed(); -/** - * - * - */ -public interface MCSign extends MCBlockState{ + void setWaxed(boolean waxed); - public void setLine(int i, String line1); + /** + * Gets the back text for this sign block. + * Using the methods directly on the sign object will apply to the front text for backwards compatibility. + * + * @return Back sign text object (null if unavailable) + */ + MCSignText getBackText(); - public String getLine(int i); - + enum Side { + FRONT, + BACK + } } diff --git a/src/main/java/com/laytonsmith/abstraction/blocks/MCSignText.java b/src/main/java/com/laytonsmith/abstraction/blocks/MCSignText.java new file mode 100644 index 0000000000..bdc4ca2a3b --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/blocks/MCSignText.java @@ -0,0 +1,21 @@ +package com.laytonsmith.abstraction.blocks; + +import com.laytonsmith.abstraction.AbstractionObject; +import com.laytonsmith.abstraction.enums.MCDyeColor; + +public interface MCSignText extends AbstractionObject { + + String[] getLines(); + + void setLine(int i, String line1); + + String getLine(int i); + + boolean isGlowingText(); + + void setGlowingText(boolean glowing); + + MCDyeColor getDyeColor(); + + void setDyeColor(MCDyeColor color); +} diff --git a/src/main/java/com/laytonsmith/abstraction/blocks/MCSkull.java b/src/main/java/com/laytonsmith/abstraction/blocks/MCSkull.java new file mode 100644 index 0000000000..66b091463a --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/blocks/MCSkull.java @@ -0,0 +1,29 @@ +package com.laytonsmith.abstraction.blocks; + +import com.laytonsmith.abstraction.MCOfflinePlayer; +import com.laytonsmith.abstraction.MCPlayerProfile; + +public interface MCSkull extends MCBlockState { + + /** + * Gets the player which owns the skull. + * @return The player or {@code null} if the skull does not have an owner. + */ + MCOfflinePlayer getOwningPlayer(); + + /** + * Gets whether the skull has an owner. + * @return {@code true} if the skull has an owner, {@code false} otherwise. + */ + boolean hasOwner(); + + /** + * Sets the player which owns the skull. + * @param player - The new skull owner or {@code null} to clear the current owner. + */ + void setOwningPlayer(MCOfflinePlayer player); + + MCPlayerProfile getPlayerProfile(); + + void setPlayerProfile(MCPlayerProfile profile); +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitConvertor.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitConvertor.java index 3f4763c674..ce16bd2380 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitConvertor.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitConvertor.java @@ -1,58 +1,72 @@ package com.laytonsmith.abstraction.bukkit; +import com.laytonsmith.PureUtilities.Common.ReflectionUtils; import com.laytonsmith.PureUtilities.DaemonManager; import com.laytonsmith.abstraction.AbstractConvertor; import com.laytonsmith.abstraction.ConvertorHelper; import com.laytonsmith.abstraction.Implementation; +import com.laytonsmith.abstraction.MCAttributeModifier; import com.laytonsmith.abstraction.MCColor; import com.laytonsmith.abstraction.MCCommand; import com.laytonsmith.abstraction.MCCommandSender; -import com.laytonsmith.abstraction.MCEnchantment; import com.laytonsmith.abstraction.MCEntity; import com.laytonsmith.abstraction.MCFireworkBuilder; import com.laytonsmith.abstraction.MCInventory; +import com.laytonsmith.abstraction.MCInventoryHolder; import com.laytonsmith.abstraction.MCItemMeta; import com.laytonsmith.abstraction.MCItemStack; import com.laytonsmith.abstraction.MCLocation; import com.laytonsmith.abstraction.MCMetadataValue; +import com.laytonsmith.abstraction.MCNamespacedKey; import com.laytonsmith.abstraction.MCNote; +import com.laytonsmith.abstraction.MCPattern; import com.laytonsmith.abstraction.MCPlugin; import com.laytonsmith.abstraction.MCPluginMeta; +import com.laytonsmith.abstraction.MCPotionData; import com.laytonsmith.abstraction.MCRecipe; import com.laytonsmith.abstraction.MCServer; import com.laytonsmith.abstraction.MCWorld; import com.laytonsmith.abstraction.MCWorldCreator; +import com.laytonsmith.abstraction.blocks.MCBlockState; import com.laytonsmith.abstraction.blocks.MCMaterial; -import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCFallingBlock; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCBanner; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCBeacon; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCBeehive; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCBlockState; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCBrewingStand; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCChest; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCCommandBlock; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCContainer; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCDecoratedPot; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCDispenser; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCDropper; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCEndGateway; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCFurnace; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCLectern; import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCMaterial; -import com.laytonsmith.abstraction.bukkit.entities.BukkitMCArrow; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCSign; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCSkull; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCAgeable; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCAnimal; import com.laytonsmith.abstraction.bukkit.entities.BukkitMCBoat; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCBreedable; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCChestBoat; import com.laytonsmith.abstraction.bukkit.entities.BukkitMCCommandMinecart; import com.laytonsmith.abstraction.bukkit.entities.BukkitMCComplexEntityPart; import com.laytonsmith.abstraction.bukkit.entities.BukkitMCComplexLivingEntity; -import com.laytonsmith.abstraction.bukkit.entities.BukkitMCCreeper; -import com.laytonsmith.abstraction.bukkit.entities.BukkitMCEnderDragon; -import com.laytonsmith.abstraction.bukkit.entities.BukkitMCEnderDragonPart; -import com.laytonsmith.abstraction.bukkit.entities.BukkitMCEnderSignal; -import com.laytonsmith.abstraction.bukkit.entities.BukkitMCEnderman; -import com.laytonsmith.abstraction.bukkit.entities.BukkitMCEntityProjectileSource; -import com.laytonsmith.abstraction.bukkit.entities.BukkitMCFirework; -import com.laytonsmith.abstraction.bukkit.entities.BukkitMCFishHook; -import com.laytonsmith.abstraction.bukkit.entities.BukkitMCHorse; -import com.laytonsmith.abstraction.bukkit.entities.BukkitMCIronGolem; -import com.laytonsmith.abstraction.bukkit.entities.BukkitMCItemFrame; -import com.laytonsmith.abstraction.bukkit.entities.BukkitMCMagmaCube; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCEntity; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCFireball; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCHanging; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCHumanEntity; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCLivingEntity; import com.laytonsmith.abstraction.bukkit.entities.BukkitMCMinecart; -import com.laytonsmith.abstraction.bukkit.entities.BukkitMCOcelot; -import com.laytonsmith.abstraction.bukkit.entities.BukkitMCPigZombie; -import com.laytonsmith.abstraction.bukkit.entities.BukkitMCSheep; -import com.laytonsmith.abstraction.bukkit.entities.BukkitMCSkeleton; -import com.laytonsmith.abstraction.bukkit.entities.BukkitMCSlime; -import com.laytonsmith.abstraction.bukkit.entities.BukkitMCThrownPotion; -import com.laytonsmith.abstraction.bukkit.entities.BukkitMCVillager; -import com.laytonsmith.abstraction.bukkit.entities.BukkitMCWitherSkull; -import com.laytonsmith.abstraction.bukkit.entities.BukkitMCWolf; -import com.laytonsmith.abstraction.bukkit.entities.BukkitMCZombie; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCPlayer; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCProjectile; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCSizedFireball; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCTameable; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCItemProjectile; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCTransformation; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCVehicle; import com.laytonsmith.abstraction.bukkit.events.BukkitAbstractEventMixin; import com.laytonsmith.abstraction.bukkit.events.drivers.BukkitBlockListener; import com.laytonsmith.abstraction.bukkit.events.drivers.BukkitEntityListener; @@ -62,478 +76,500 @@ import com.laytonsmith.abstraction.bukkit.events.drivers.BukkitVehicleListener; import com.laytonsmith.abstraction.bukkit.events.drivers.BukkitWeatherListener; import com.laytonsmith.abstraction.bukkit.events.drivers.BukkitWorldListener; -import com.laytonsmith.abstraction.enums.MCEntityType; +import com.laytonsmith.abstraction.entities.MCTransformation; +import com.laytonsmith.abstraction.enums.MCAttribute; +import com.laytonsmith.abstraction.enums.MCDyeColor; +import com.laytonsmith.abstraction.enums.MCEquipmentSlot; +import com.laytonsmith.abstraction.enums.MCEquipmentSlotGroup; +import com.laytonsmith.abstraction.enums.MCPatternShape; +import com.laytonsmith.abstraction.enums.MCPotionType; import com.laytonsmith.abstraction.enums.MCRecipeType; import com.laytonsmith.abstraction.enums.MCTone; +import com.laytonsmith.abstraction.enums.MCVersion; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCDyeColor; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCEntityType; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCEquipmentSlot; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCLegacyMaterial; import com.laytonsmith.annotations.convert; import com.laytonsmith.commandhelper.CommandHelperPlugin; -import com.laytonsmith.core.CHLog; import com.laytonsmith.core.LogLevel; +import com.laytonsmith.core.MSLog; import com.laytonsmith.core.Static; import com.laytonsmith.core.constructs.Target; -import com.laytonsmith.core.functions.Exceptions; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.logging.Level; -import org.apache.log4j.Logger; +import com.laytonsmith.core.environments.CommandHelperEnvironment; +import com.laytonsmith.core.environments.Environment; +import com.laytonsmith.core.exceptions.CRE.CREFormatException; +import com.laytonsmith.core.exceptions.CancelCommandException; import org.bukkit.Bukkit; import org.bukkit.Color; import org.bukkit.Location; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.World; -import org.bukkit.block.Block; -import org.bukkit.block.DoubleChest; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeModifier; +import org.bukkit.block.Banner; +import org.bukkit.block.Beacon; +import org.bukkit.block.Beehive; +import org.bukkit.block.BlockState; +import org.bukkit.block.BrewingStand; +import org.bukkit.block.Chest; +import org.bukkit.block.CommandBlock; +import org.bukkit.block.Container; +import org.bukkit.block.CreatureSpawner; +import org.bukkit.block.DecoratedPot; +import org.bukkit.block.Dispenser; +import org.bukkit.block.Dropper; +import org.bukkit.block.EndGateway; +import org.bukkit.block.Furnace; +import org.bukkit.block.Lectern; +import org.bukkit.block.Sign; +import org.bukkit.block.Skull; +import org.bukkit.block.banner.Pattern; +import org.bukkit.block.banner.PatternType; import org.bukkit.command.BlockCommandSender; import org.bukkit.command.CommandSender; import org.bukkit.command.ConsoleCommandSender; -import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.Ageable; -import org.bukkit.entity.Arrow; +import org.bukkit.entity.Animals; import org.bukkit.entity.Boat; +import org.bukkit.entity.Breedable; +import org.bukkit.entity.ChestBoat; import org.bukkit.entity.ComplexEntityPart; import org.bukkit.entity.ComplexLivingEntity; -import org.bukkit.entity.Creeper; -import org.bukkit.entity.EnderCrystal; -import org.bukkit.entity.EnderDragon; -import org.bukkit.entity.EnderDragonPart; -import org.bukkit.entity.EnderSignal; -import org.bukkit.entity.Enderman; import org.bukkit.entity.Entity; -import org.bukkit.entity.ExperienceOrb; -import org.bukkit.entity.FallingBlock; import org.bukkit.entity.Fireball; -import org.bukkit.entity.Firework; -import org.bukkit.entity.Fish; import org.bukkit.entity.Hanging; -import org.bukkit.entity.Horse; import org.bukkit.entity.HumanEntity; -import org.bukkit.entity.IronGolem; -import org.bukkit.entity.Item; -import org.bukkit.entity.ItemFrame; -import org.bukkit.entity.LightningStrike; import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.MagmaCube; import org.bukkit.entity.Minecart; -import org.bukkit.entity.Ocelot; -import org.bukkit.entity.Painting; -import org.bukkit.entity.Pig; -import org.bukkit.entity.PigZombie; import org.bukkit.entity.Player; import org.bukkit.entity.Projectile; -import org.bukkit.entity.Sheep; -import org.bukkit.entity.Skeleton; -import org.bukkit.entity.Slime; -import org.bukkit.entity.TNTPrimed; -import org.bukkit.entity.ThrownPotion; +import org.bukkit.entity.SizedFireball; +import org.bukkit.entity.Tameable; +import org.bukkit.entity.ThrowableProjectile; import org.bukkit.entity.Vehicle; -import org.bukkit.entity.Villager; -import org.bukkit.entity.WitherSkull; -import org.bukkit.entity.Wolf; -import org.bukkit.entity.Zombie; import org.bukkit.entity.minecart.CommandMinecart; -import org.bukkit.event.Listener; +import org.bukkit.inventory.BlastingRecipe; +import org.bukkit.inventory.CampfireRecipe; +import org.bukkit.inventory.ComplexRecipe; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.EquipmentSlotGroup; import org.bukkit.inventory.FurnaceRecipe; import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MerchantRecipe; import org.bukkit.inventory.Recipe; import org.bukkit.inventory.ShapedRecipe; import org.bukkit.inventory.ShapelessRecipe; +import org.bukkit.inventory.SmithingRecipe; +import org.bukkit.inventory.SmokingRecipe; +import org.bukkit.inventory.StonecuttingRecipe; +import org.bukkit.inventory.meta.ArmorMeta; +import org.bukkit.inventory.meta.AxolotlBucketMeta; +import org.bukkit.inventory.meta.BannerMeta; +import org.bukkit.inventory.meta.BlockStateMeta; import org.bukkit.inventory.meta.BookMeta; +import org.bukkit.inventory.meta.BundleMeta; +import org.bukkit.inventory.meta.ColorableArmorMeta; +import org.bukkit.inventory.meta.CompassMeta; +import org.bukkit.inventory.meta.CrossbowMeta; import org.bukkit.inventory.meta.EnchantmentStorageMeta; import org.bukkit.inventory.meta.FireworkEffectMeta; import org.bukkit.inventory.meta.FireworkMeta; import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.KnowledgeBookMeta; import org.bukkit.inventory.meta.LeatherArmorMeta; +import org.bukkit.inventory.meta.MapMeta; +import org.bukkit.inventory.meta.MusicInstrumentMeta; +import org.bukkit.inventory.meta.OminousBottleMeta; import org.bukkit.inventory.meta.PotionMeta; import org.bukkit.inventory.meta.SkullMeta; -import org.bukkit.material.MaterialData; +import org.bukkit.inventory.meta.SuspiciousStewMeta; +import org.bukkit.inventory.meta.TropicalFishBucketMeta; import org.bukkit.metadata.FixedMetadataValue; -import org.bukkit.projectiles.ProjectileSource; +import org.bukkit.potion.PotionType; import org.yaml.snakeyaml.Yaml; -/** - * - * - */ -@convert(type=Implementation.Type.BUKKIT) +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import org.bukkit.command.RemoteConsoleCommandSender; +import org.bukkit.util.Transformation; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +@convert(type = Implementation.Type.BUKKIT) public class BukkitConvertor extends AbstractConvertor { private static BukkitMCPluginMeta pluginMeta = null; @Override - public MCLocation GetLocation(MCWorld w, double x, double y, double z, float yaw, float pitch) { - World w2 = null; - if(w != null){ - w2 = ((BukkitMCWorld)w).__World(); - } - return new BukkitMCLocation(new Location(w2, x, y, z, yaw, pitch)); - } + public MCLocation GetLocation(MCWorld w, double x, double y, double z, float yaw, float pitch) { + World w2 = null; + if(w != null) { + w2 = ((BukkitMCWorld) w).__World(); + } + return new BukkitMCLocation(new Location(w2, x, y, z, yaw, pitch)); + } @Override - public Class GetServerEventMixin() { - return BukkitAbstractEventMixin.class; - } + public Class GetServerEventMixin() { + return BukkitAbstractEventMixin.class; + } @Override - public MCEnchantment[] GetEnchantmentValues() { - MCEnchantment[] ea = new MCEnchantment[Enchantment.values().length]; - Enchantment[] oea = Enchantment.values(); - for (int i = 0; i < ea.length; i++) { - ea[i] = new BukkitMCEnchantment(oea[i]); - } - return ea; - - } + public MCServer GetServer() { + return BukkitMCServer.Get(); + } @Override - public MCEnchantment GetEnchantmentByName(String name) { - try{ - //If they are looking it up by number, we can support that - int i = Integer.valueOf(name); - return new BukkitMCEnchantment(Enchantment.getById(i)); - } catch(NumberFormatException e){ - try{ - return new BukkitMCEnchantment(Enchantment.getByName(name)); - } catch(NullPointerException ee){ - return null; - } - } - } + public MCMaterial[] GetMaterialValues() { + Material[] mats = Material.values(); + MCMaterial[] ret = new MCMaterial[mats.length]; + for(int i = 0; i < mats.length; i++) { + ret[i] = BukkitMCMaterial.valueOfConcrete(mats[i]); + } + return ret; + } @Override - public MCServer GetServer() { - return BukkitMCServer.Get(); - } + public MCMaterial GetMaterialFromLegacy(String mat, int data) { + Material m = BukkitMCLegacyMaterial.getMaterial(mat, data); + return m == null ? null : BukkitMCMaterial.valueOfConcrete(m); + } @Override - public MCMaterial getMaterial(int id) { - return new BukkitMCMaterial(Material.getMaterial(id)); + public MCMaterial GetMaterialFromLegacy(int id, int data) { + Material m = BukkitMCLegacyMaterial.getMaterial(id, data); + return m == null ? null : BukkitMCMaterial.valueOfConcrete(m); } @Override - public MCItemStack GetItemStack(int type, int qty) { - return new BukkitMCItemStack(new ItemStack(type, qty)); - } - @Override - public MCItemStack GetItemStack(int type, int data, int qty) { - return new BukkitMCItemStack(new ItemStack(type, qty, (short)0, (byte)data)); - } + public MCMaterial GetMaterial(String name) { + MCMaterial ret = MCMaterial.get(name); + if(ret != null) { + return ret; + } + // Try fuzzy match + Material match = Material.matchMaterial(name); + if(match != null) { + return BukkitMCMaterial.valueOfConcrete(match); + } + // Try legacy + match = BukkitMCLegacyMaterial.getMaterial(name); + if(match != null) { + return BukkitMCMaterial.valueOfConcrete(match); + } + return null; + } @Override - public MCMetadataValue GetMetadataValue(Object value, MCPlugin plugin) { - return new BukkitMCMetadataValue(new FixedMetadataValue(((BukkitMCPlugin) plugin).getHandle(), value)); + public MCItemStack GetItemStack(MCMaterial type, int qty) { + return new BukkitMCItemStack(new ItemStack(((BukkitMCMaterial) type).getHandle(), qty)); } - public static final BukkitBlockListener BlockListener = new BukkitBlockListener(); - public static final BukkitEntityListener EntityListener = new BukkitEntityListener(); - public static final BukkitInventoryListener InventoryListener = new BukkitInventoryListener(); - public static final BukkitPlayerListener PlayerListener = new BukkitPlayerListener(); - public static final BukkitServerListener ServerListener = new BukkitServerListener(); - public static final BukkitVehicleListener VehicleListener = new BukkitVehicleListener(); - public static final BukkitWeatherListener WeatherListener = new BukkitWeatherListener(); - public static final BukkitWorldListener WorldListener = new BukkitWorldListener(); - - @Override - public void Startup(CommandHelperPlugin chp) { - chp.registerEvent((Listener)BlockListener); - chp.registerEvent((Listener)EntityListener); - chp.registerEvent((Listener)InventoryListener); - chp.registerEvent((Listener)PlayerListener); - chp.registerEvent((Listener)ServerListener); - chp.registerEvent((Listener)VehicleListener); - chp.registerEvent((Listener)WeatherListener); - chp.registerEvent((Listener)WorldListener); - } - - @Override - public int LookupItemId(String materialName) { - if(Material.matchMaterial(materialName) != null){ - return new MaterialData(Material.matchMaterial(materialName)).getItemTypeId(); - } else { - return -1; - } - } - - @Override - public String LookupMaterialName(int id) { - return Material.getMaterial(id).toString(); - } - - /** - * We don't want to allow scripts to clear other plugin's tasks - * on accident, so only ids registered through our interface - * can also be cancelled. - */ - private static final Set validIDs = new TreeSet(); - - @Override - public synchronized int SetFutureRunnable(DaemonManager dm, long ms, Runnable r) { - int id = Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(CommandHelperPlugin.self, r, Static.msToTicks(ms)); - validIDs.add(id); - return id; - } - - @Override - public synchronized int SetFutureRepeater(DaemonManager dm, long ms, long initialDelay, Runnable r){ - int id = Bukkit.getServer().getScheduler().scheduleSyncRepeatingTask(CommandHelperPlugin.self, r, Static.msToTicks(initialDelay), Static.msToTicks(ms)); - validIDs.add(id); - return id; - } - - @Override - public synchronized void ClearAllRunnables() { - //Doing cancelTasks apparently does not work, so let's just manually cancel each task, which does appear to work. - //Anyways, it's better that way anyhow, because we actually remove IDs from validIDs that way. - //((BukkitMCServer)Static.getServer()).__Server().getScheduler().cancelTasks(CommandHelperPlugin.self); - Set ids = new TreeSet(validIDs); - for(int id : ids){ - try{ - //If this doesn't work, it shouldn't kill everything. - ClearFutureRunnable(id); - } catch(Exception e){ - Logger.getLogger(BukkitConvertor.class.getName()).log(null, Level.SEVERE, e); - } - } - } - @Override - public void ClearFutureRunnable(int id) { - if(validIDs.contains(id)){ - Bukkit.getServer().getScheduler().cancelTask(id); - validIDs.remove(id); - } - } - - public static MCEntity BukkitGetCorrectEntity(Entity be){ - if (be == null) { - return null; - } - - //TODO: Change this to a reflection mechanism, this is getting tiresome to do. - - // Steve! - if(be instanceof Player){ - // Must come before HumanEntity - return new BukkitMCPlayer((Player)be); - } - - // Mobs - Passive / Tamable - if(be instanceof Villager){ - return new BukkitMCVillager((Villager)be); - } - - if(be instanceof Wolf){ - return new BukkitMCWolf(be); - } - - if(be instanceof Ocelot){ - return new BukkitMCOcelot(be); - } - - if (be instanceof Sheep) { - return new BukkitMCSheep((Sheep) be); + public MCItemStack GetItemStack(String type, int qty) { + Material mat = Material.getMaterial(type); + if(mat == null) { + mat = BukkitMCLegacyMaterial.getMaterial(type); } - - if (be instanceof Horse) { - // Must come before Vehicle - return new BukkitMCHorse((Horse) be); + if(mat == null) { + mat = Material.matchMaterial(type); } - - if (be instanceof Pig) { - // Must come before Vehicle - return new BukkitMCPig((Pig) be); + if(mat == null || !mat.isItem()) { + return null; } + return new BukkitMCItemStack(new ItemStack(mat, qty)); + } - // Mobs - Enemies - if (be instanceof EnderDragon) { - // Must come before ComplexLivingEntity - return new BukkitMCEnderDragon((EnderDragon) be); + @Override + public MCPotionData GetPotionData(MCPotionType type, boolean extended, boolean upgraded) { + try { + Class clz = Class.forName("org.bukkit.potion.PotionData"); + return new BukkitMCPotionData(ReflectionUtils.newInstance(clz, + new Class[]{PotionType.class, boolean.class, boolean.class}, + new Object[]{type.getConcrete(), extended, upgraded})); + } catch (ClassNotFoundException ex) { + // probably after 1.20.5 + // use PotionType instead + return null; } + } - if (be instanceof Enderman) { - return new BukkitMCEnderman((Enderman) be); + @Override + public MCAttributeModifier GetAttributeModifier(MCAttribute attr, UUID id, String name, double amt, MCAttributeModifier.Operation op, MCEquipmentSlot slot) { + if(id == null) { + id = UUID.randomUUID(); } + AttributeModifier mod = new AttributeModifier(id, name, amt, + BukkitMCAttributeModifier.Operation.getConvertor().getConcreteEnum(op), + BukkitMCEquipmentSlot.getConvertor().getConcreteEnum(slot)); + return new BukkitMCAttributeModifier((Attribute) attr.getConcrete(), mod); + } - if (be instanceof Creeper) { - return new BukkitMCCreeper((Creeper) be); - } - - if (be instanceof IronGolem) { - return new BukkitMCIronGolem((IronGolem) be); - } - - if (be instanceof Skeleton) { - return new BukkitMCSkeleton((Skeleton) be); - } - - if (be instanceof MagmaCube) { - // Must come before Slime - return new BukkitMCMagmaCube((MagmaCube) be); - } + @Override + public MCAttributeModifier GetAttributeModifier(MCAttribute attr, UUID id, String name, double amt, MCAttributeModifier.Operation op, MCEquipmentSlotGroup slot) { + if(!((BukkitMCServer) Static.getServer()).isPaper()) { + // BODY is missing from Spigot, so this falls back to ARMOR just like EquipmentSlot.BODY + if(slot == MCEquipmentSlotGroup.BODY) { + slot = MCEquipmentSlotGroup.ARMOR; + } + } + if(id == null) { + id = UUID.randomUUID(); + } + AttributeModifier mod = new AttributeModifier(id, name, amt, + BukkitMCAttributeModifier.Operation.getConvertor().getConcreteEnum(op), + EquipmentSlotGroup.getByName(slot.name())); + return new BukkitMCAttributeModifier((Attribute) attr.getConcrete(), mod); + } - if (be instanceof Slime) { - return new BukkitMCSlime((Slime) be); - } + @Override + public MCAttributeModifier GetAttributeModifier(MCAttribute attr, MCNamespacedKey key, double amt, MCAttributeModifier.Operation op, MCEquipmentSlot slot) { + EquipmentSlot es = BukkitMCEquipmentSlot.getConvertor().getConcreteEnum(slot); + AttributeModifier mod = new AttributeModifier((NamespacedKey) key.getHandle(), amt, + BukkitMCAttributeModifier.Operation.getConvertor().getConcreteEnum(op), + es == null ? EquipmentSlotGroup.ANY : es.getGroup()); + return new BukkitMCAttributeModifier((Attribute) attr.getConcrete(), mod); + } - if (be instanceof PigZombie) { - // Must come before Zombie - return new BukkitMCPigZombie((PigZombie) be); + @Override + public MCAttributeModifier GetAttributeModifier(MCAttribute attr, MCNamespacedKey key, double amt, MCAttributeModifier.Operation op, MCEquipmentSlotGroup slot) { + if(!((BukkitMCServer) Static.getServer()).isPaper()) { + // BODY is missing from Spigot, so this falls back to ARMOR just like EquipmentSlot.BODY + if(slot == MCEquipmentSlotGroup.BODY) { + slot = MCEquipmentSlotGroup.ARMOR; + } } + AttributeModifier mod = new AttributeModifier((NamespacedKey) key.getHandle(), amt, + BukkitMCAttributeModifier.Operation.getConvertor().getConcreteEnum(op), + EquipmentSlotGroup.getByName(slot.name())); + return new BukkitMCAttributeModifier((Attribute) attr.getConcrete(), mod); + } - if (be instanceof Zombie) { - return new BukkitMCZombie((Zombie) be); - } + @Override + public MCMetadataValue GetMetadataValue(Object value, MCPlugin plugin) { + return new BukkitMCMetadataValue(new FixedMetadataValue(((BukkitMCPlugin) plugin).getHandle(), value)); + } - // Block entities - if(be instanceof FallingBlock){ - return new BukkitMCFallingBlock((FallingBlock) be); - } + public static final BukkitBlockListener BLOCK_LISTENER = new BukkitBlockListener(); + public static final BukkitEntityListener ENTITY_LISTENER = new BukkitEntityListener(); + public static final BukkitInventoryListener INVENTORY_LISTENER = new BukkitInventoryListener(); + public static final BukkitPlayerListener PLAYER_LISTENER = new BukkitPlayerListener(); + public static final BukkitServerListener SERVER_LISTENER = new BukkitServerListener(); + public static final BukkitVehicleListener VEHICLE_LISTENER = new BukkitVehicleListener(); + public static final BukkitWeatherListener WEATHER_LISTENER = new BukkitWeatherListener(); + public static final BukkitWorldListener WORLD_LISTENER = new BukkitWorldListener(); + + @Override + public void Startup(CommandHelperPlugin chp) { + chp.registerEvents(BLOCK_LISTENER); + chp.registerEvents(ENTITY_LISTENER); + chp.registerEventsDynamic(INVENTORY_LISTENER); + chp.registerEventsDynamic(PLAYER_LISTENER); + chp.registerEvents(SERVER_LISTENER); + chp.registerEvents(VEHICLE_LISTENER); + chp.registerEvents(WEATHER_LISTENER); + chp.registerEvents(WORLD_LISTENER); + } - if(be instanceof TNTPrimed){ - return new BukkitMCTNT((TNTPrimed)be); - } + @Override + protected void triggerRunnable(final Runnable r) { + try { + runOnMainThreadAndWait(new Callable() { - if(be instanceof EnderCrystal){ - return new BukkitMCEnderCrystal((EnderCrystal)be); + @Override + public Object call() throws Exception { + r.run(); + return null; + } + }); + } catch (CancellationException ex) { + // Ignore the Exception when the plugin is disabled (server shutting down). + if(CommandHelperPlugin.self.isEnabled()) { + java.util.logging.Logger.getLogger(BukkitConvertor.class.getName()).log(Level.SEVERE, null, ex); + } + } catch (InterruptedException | ExecutionException ex) { + java.util.logging.Logger.getLogger(BukkitConvertor.class.getName()).log(Level.SEVERE, null, ex); } + } - // Pickups - if(be instanceof Item){ - return new BukkitMCItem((Item)be); +// /** +// * We don't want to allow scripts to clear other plugin's tasks +// * on accident, so only ids registered through our interface +// * can also be cancelled. +// */ +// private static final Set validIDs = new TreeSet(); +// +// @Override +// public synchronized int SetFutureRunnable(DaemonManager dm, long ms, Runnable r) { +// int id = Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(CommandHelperPlugin.self, r, Static.msToTicks(ms)); +// validIDs.add(id); +// return id; +// } +// +// @Override +// public synchronized int SetFutureRepeater(DaemonManager dm, long ms, long initialDelay, Runnable r){ +// int id = Bukkit.getServer().getScheduler().scheduleSyncRepeatingTask(CommandHelperPlugin.self, r, Static.msToTicks(initialDelay), Static.msToTicks(ms)); +// validIDs.add(id); +// return id; +// } +// +// @Override +// public synchronized void ClearAllRunnables() { +// //Doing cancelTasks apparently does not work, so let's just manually cancel each task, which does appear to work. +// //Anyways, it's better that way anyhow, because we actually remove IDs from validIDs that way. +// //((BukkitMCServer)Static.getServer()).__Server().getScheduler().cancelTasks(CommandHelperPlugin.self); +// Set ids = new TreeSet(validIDs); +// for(int id : ids){ +// try { +// //If this doesn't work, it shouldn't kill everything. +// ClearFutureRunnable(id); +// } catch (Exception e){ +// Logger.getLogger(BukkitConvertor.class.getName()).log(null, Level.SEVERE, e); +// } +// } +// } +// +// @Override +// public void ClearFutureRunnable(int id) { +// if(validIDs.contains(id)){ +// Bukkit.getServer().getScheduler().cancelTask(id); +// validIDs.remove(id); +// } +// } + public static MCEntity BukkitGetCorrectEntity(Entity be) { + if(be == null) { + return null; } - if(be instanceof ExperienceOrb){ - return new BukkitMCExperienceOrb((ExperienceOrb)be); + BukkitMCEntityType type = BukkitMCEntityType.valueOfConcrete(be.getType()); + if(type.getWrapperClass() != null) { + return ReflectionUtils.newInstance(type.getWrapperClass(), new Class[]{Entity.class}, new Object[]{be}); } - // Projectiles - if (be instanceof Arrow) { - return new BukkitMCArrow((Arrow)be); + if(be instanceof Hanging) { + type.setWrapperClass(BukkitMCHanging.class); + return new BukkitMCHanging(be); } - if (be instanceof ThrownPotion) { - return new BukkitMCThrownPotion((ThrownPotion)be); + if(be instanceof Minecart) { + // Must come before Vehicle + type.setWrapperClass(BukkitMCMinecart.class); + return new BukkitMCMinecart(be); } - if (be instanceof Fish) { - return new BukkitMCFishHook((Fish) be); - } + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_21_3)) { + // boats were split into different classes by wood type + if(be instanceof ChestBoat) { + // Must come before Boat + type.setWrapperClass(BukkitMCChestBoat.class); + return new BukkitMCChestBoat(be); + } - if (be instanceof WitherSkull) { - //Must be before Fireball - return new BukkitMCWitherSkull((WitherSkull)be); + if(be instanceof Boat) { + // Must come before Vehicle + type.setWrapperClass(BukkitMCBoat.class); + return new BukkitMCBoat(be); + } } - if (be instanceof Fireball) { - return new BukkitMCFireball((Fireball) be); + if(be instanceof SizedFireball) { + // Must come before Fireball + type.setWrapperClass(BukkitMCSizedFireball.class); + return new BukkitMCSizedFireball(be); } - if (be instanceof Firework) { - // Not really a projectile, but it fits here. - return new BukkitMCFirework((Firework) be); + if(be instanceof Fireball) { + // Must come before Projectile + type.setWrapperClass(BukkitMCFireball.class); + return new BukkitMCFireball(be); } - // Static / Hanging - if (be instanceof EnderSignal) { - return new BukkitMCEnderSignal((EnderSignal) be); - } - - if (be instanceof ItemFrame) { - // Must come before Hanging - return new BukkitMCItemFrame((ItemFrame) be); - } - - if(be instanceof Painting){ - // Must come before Hanging - return new BukkitMCPainting((Painting)be); + if(be instanceof ThrowableProjectile) { + // Must come before Projectile + type.setWrapperClass(BukkitMCItemProjectile.class); + return new BukkitMCItemProjectile(be); } - if(be instanceof Hanging){ - return new BukkitMCHanging(be); - } - - // Vehicles - if(be instanceof CommandMinecart) { - return new BukkitMCCommandMinecart((CommandMinecart)be); + if(be instanceof Projectile) { + type.setWrapperClass(BukkitMCProjectile.class); + return new BukkitMCProjectile(be); } - if(be instanceof Minecart) { - // Must come before Vehicle - return new BukkitMCMinecart((Minecart)be); - } - - if(be instanceof Boat) { - // Must come before Vehicle - return new BukkitMCBoat((Boat)be); - } - - // Weather - if(be instanceof LightningStrike){ - return new BukkitMCLightningStrike((LightningStrike)be); + if(be instanceof Tameable) { + // Must come before Ageable + type.setWrapperClass(BukkitMCTameable.class); + return new BukkitMCTameable(be); } - // Misc - if (be instanceof EnderDragonPart) { - // Must come before ComplexLivingEntity - return new BukkitMCEnderDragonPart((EnderDragonPart) be); + if(be instanceof Animals) { + // Must come before Ageable + type.setWrapperClass(BukkitMCAnimal.class); + return new BukkitMCAnimal(be); } - // Abstractions - if(be instanceof Projectile){ - return new BukkitMCProjectile((Projectile)be); - } + if(be instanceof Breedable) { + // Must come before Ageable + type.setWrapperClass(BukkitMCBreedable.class); + return new BukkitMCBreedable(be); + } - if(be instanceof Ageable){ + if(be instanceof Ageable) { // Must come before LivingEntity - return new BukkitMCAgeable(be); - } + type.setWrapperClass(BukkitMCAgeable.class); + return new BukkitMCAgeable(be); + } - if(be instanceof HumanEntity){ + if(be instanceof HumanEntity) { // Must come before LivingEntity - return new BukkitMCHumanEntity((HumanEntity)be); - } + type.setWrapperClass(BukkitMCHumanEntity.class); + return new BukkitMCHumanEntity(be); + } if(be instanceof ComplexEntityPart) { - return new BukkitMCComplexEntityPart((ComplexEntityPart)be); + type.setWrapperClass(BukkitMCComplexEntityPart.class); + return new BukkitMCComplexEntityPart(be); } if(be instanceof ComplexLivingEntity) { // Must come before LivingEntity - return new BukkitMCComplexLivingEntity((ComplexLivingEntity)be); + type.setWrapperClass(BukkitMCComplexLivingEntity.class); + return new BukkitMCComplexLivingEntity(be); } - if(be instanceof LivingEntity){ - return new BukkitMCLivingEntity(((LivingEntity)be)); - } - - if (be instanceof ProjectileSource) { - return new BukkitMCEntityProjectileSource(be); + if(be instanceof LivingEntity) { + type.setWrapperClass(BukkitMCLivingEntity.class); + return new BukkitMCLivingEntity(be); } - if(be instanceof Vehicle){ - return new BukkitMCVehicle(be); - } + if(be instanceof Vehicle) { + type.setWrapperClass(BukkitMCVehicle.class); + return new BukkitMCVehicle(be); + } - throw new IllegalArgumentException("While trying to find the correct entity type for " + be.getClass().getName() - + ", was unable to find the appropriate implementation. If the named entity is not provided by mods," - + " please alert the developers of this stack trace. This is not necessarily an error," - + " we just don't have any special handling for this entity yet, and will treat it generically."); + // Handle generically if we can't find a more specific type + type.setWrapperClass(BukkitMCEntity.class); + return new BukkitMCEntity(be); } @Override public MCEntity GetCorrectEntity(MCEntity e) { - Entity be = ((BukkitMCEntity)e).getHandle(); + Entity be = ((BukkitMCEntity) e).getHandle(); try { return BukkitConvertor.BukkitGetCorrectEntity(be); } catch (IllegalArgumentException iae) { - CHLog.GetLogger().Log(CHLog.Tags.RUNTIME, LogLevel.INFO, iae.getMessage(), Target.UNKNOWN); + MSLog.GetLogger().Log(MSLog.Tags.RUNTIME, LogLevel.INFO, iae.getMessage(), Target.UNKNOWN); return e; } } @@ -546,89 +582,184 @@ public MCItemMeta GetCorrectMeta(MCItemMeta im) { @Override public List GetEntitiesAt(MCLocation location, double radius) { - if(location == null){ + if(location == null) { return Collections.EMPTY_LIST; } - if(radius <= 0){ + if(radius <= 0) { radius = 1; } - Entity tempEntity = ((BukkitMCEntity)location.getWorld().spawn(location, MCEntityType.ARROW)).getHandle(); - List near = tempEntity.getNearbyEntities(radius, radius, radius); - tempEntity.remove(); - List entities = new ArrayList(); - for(Entity e : near){ + Location l = (Location) location.getHandle(); + Collection near = l.getWorld().getNearbyEntities(l, radius, radius, radius); + List entities = new ArrayList<>(); + for(Entity e : near) { entities.add(BukkitGetCorrectEntity(e)); } return entities; } + public static MCBlockState BukkitGetCorrectBlockState(BlockState bs) { + if(bs instanceof Container) { + // This code block should only contain checks for blockstates that implement Container. + if(bs instanceof Chest) { + return new BukkitMCChest((Chest) bs); + } + if(bs instanceof BrewingStand) { + return new BukkitMCBrewingStand((BrewingStand) bs); + } + if(bs instanceof Dispenser) { + return new BukkitMCDispenser((Dispenser) bs); + } + if(bs instanceof Dropper) { + return new BukkitMCDropper((Dropper) bs); + } + if(bs instanceof Furnace) { + return new BukkitMCFurnace((Furnace) bs); + } + return new BukkitMCContainer((Container) bs); + } + if(bs instanceof Banner) { + return new BukkitMCBanner((Banner) bs); + } + if(bs instanceof CreatureSpawner) { + return new BukkitMCCreatureSpawner((CreatureSpawner) bs); + } + if(bs instanceof Beacon) { + return new BukkitMCBeacon((Beacon) bs); + } + if(bs instanceof Skull) { + return new BukkitMCSkull((Skull) bs); + } + if(bs instanceof Lectern) { + return new BukkitMCLectern((Lectern) bs); + } + if(bs instanceof Beehive) { + return new BukkitMCBeehive((Beehive) bs); + } + if(bs instanceof Sign) { + return new BukkitMCSign((Sign) bs); + } + if(bs instanceof CommandBlock) { + return new BukkitMCCommandBlock((CommandBlock) bs); + } + if(bs instanceof EndGateway) { + return new BukkitMCEndGateway((EndGateway) bs); + } + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_20_1) && bs instanceof DecoratedPot) { + return new BukkitMCDecoratedPot((DecoratedPot) bs); + } + return new BukkitMCBlockState(bs); + } + public static MCItemMeta BukkitGetCorrectMeta(ItemMeta im) { - if (im instanceof BookMeta) { + if(im instanceof BlockStateMeta) { + return new BukkitMCBlockStateMeta((BlockStateMeta) im); + } + if(im instanceof BannerMeta) { + return new BukkitMCBannerMeta((BannerMeta) im); + } + if(im instanceof BookMeta) { return new BukkitMCBookMeta((BookMeta) im); } - if (im instanceof EnchantmentStorageMeta) { + if(im instanceof EnchantmentStorageMeta) { return new BukkitMCEnchantmentStorageMeta((EnchantmentStorageMeta) im); } - if (im instanceof FireworkEffectMeta) { - + if(im instanceof FireworkEffectMeta) { + return new BukkitMCFireworkEffectMeta((FireworkEffectMeta) im); } - if (im instanceof FireworkMeta) { + if(im instanceof FireworkMeta) { return new BukkitMCFireworkMeta((FireworkMeta) im); } - if (im instanceof LeatherArmorMeta) { - return new BukkitMCLeatherArmorMeta((LeatherArmorMeta) im); - } - if (im instanceof PotionMeta) { + if(im instanceof PotionMeta) { return new BukkitMCPotionMeta((PotionMeta) im); } - if (im instanceof SkullMeta) { + if(im instanceof SkullMeta) { return new BukkitMCSkullMeta((SkullMeta) im); } - return new BukkitMCItemMeta(im); - } - - @Override - public MCInventory GetEntityInventory(int entityID) { - Entity entity = null; - outer: for(World w : Bukkit.getWorlds()){ - for(Entity e : w.getEntities()){ - if(e.getEntityId() == entityID){ - entity = e; - break outer; + if(im instanceof MapMeta) { + return new BukkitMCMapMeta((MapMeta) im); + } + if(im instanceof TropicalFishBucketMeta) { + return new BukkitMCTropicalFishBucketMeta((TropicalFishBucketMeta) im); + } + if(im instanceof CrossbowMeta) { + return new BukkitMCCrossbowMeta((CrossbowMeta) im); + } + if(im instanceof CompassMeta) { + return new BukkitMCCompassMeta((CompassMeta) im); + } + if(im instanceof SuspiciousStewMeta) { + return new BukkitMCSuspiciousStewMeta((SuspiciousStewMeta) im); + } + if(im instanceof KnowledgeBookMeta) { + return new BukkitMCKnowledgeBookMeta((KnowledgeBookMeta) im); + } + MCVersion version = Static.getServer().getMinecraftVersion(); + if(version.gte(MCVersion.MC1_17)) { + if(im instanceof BundleMeta) { + return new BukkitMCBundleMeta((BundleMeta) im); + } + if(version.gte(MCVersion.MC1_17_X)) { + if(im instanceof AxolotlBucketMeta) { + return new BukkitMCAxolotlBucketMeta((AxolotlBucketMeta) im); + } + if(version.gte(MCVersion.MC1_19_3)) { + if(im instanceof MusicInstrumentMeta) { + return new BukkitMCMusicInstrumentMeta((MusicInstrumentMeta) im); + } + if(version.gte(MCVersion.MC1_20)) { + if(im instanceof ColorableArmorMeta) { // Must be before ArmorMeta and LeatherArmorMeta + return new BukkitMCColorableArmorMeta((ColorableArmorMeta) im); + } + if(im instanceof ArmorMeta) { + return new BukkitMCArmorMeta((ArmorMeta) im); + } + if(version.gte(MCVersion.MC1_20_6)) { + if(im instanceof OminousBottleMeta) { + return new BukkitMCOminousBottleMeta((OminousBottleMeta) im); + } + } + } } } } - if(entity == null){ - return null; + if(im instanceof LeatherArmorMeta) { // Must be after ColorableArmorMeta + return new BukkitMCLeatherArmorMeta((LeatherArmorMeta) im); } - if(entity instanceof InventoryHolder){ - if(entity instanceof Player){ - return new BukkitMCPlayerInventory(((Player)entity).getInventory()); - } else { - return new BukkitMCInventory(((InventoryHolder)entity).getInventory()); + return new BukkitMCItemMeta(im); + } + + @Override + public MCInventory GetEntityInventory(MCEntity e) { + Entity entity = ((BukkitMCEntity) e).getHandle(); + if(entity instanceof InventoryHolder) { + if(entity instanceof Player p) { + return new BukkitMCPlayerInventory(p.getInventory()); } - } else { - return null; + return new BukkitMCInventory(((InventoryHolder) entity).getInventory()); } + return null; } @Override public MCInventory GetLocationInventory(MCLocation location) { - Block b = ((Location)(location.getHandle())).getBlock(); - if(b.getState() instanceof InventoryHolder){ - if(b.getState() instanceof DoubleChest){ - DoubleChest dc = (DoubleChest)(b.getState()); - return new BukkitMCDoubleChest(dc.getLeftSide().getInventory(), dc.getRightSide().getInventory()); - } else { - return new BukkitMCInventory(((InventoryHolder)b.getState()).getInventory()); - } - } else { - return null; + BlockState bs = ((Location) location.getHandle()).getBlock().getState(); + if(bs instanceof InventoryHolder) { + return new BukkitMCInventory(((InventoryHolder) bs).getInventory()); } + return null; + } + + @Override + public MCInventoryHolder CreateInventoryHolder(String id, String title) { + return new BukkitMCVirtualInventoryHolder(id, title); } @Override public void runOnMainThreadLater(DaemonManager dm, final Runnable r) { + if(!CommandHelperPlugin.self.isEnabled()) { + throw new CancelCommandException(Implementation.GetServerType().getBranding() + + " tried to schedule a task while the plugin was disabled (is the server shutting down?).", Target.UNKNOWN); + } Bukkit.getServer().getScheduler().callSyncMethod(CommandHelperPlugin.self, new Callable() { @Override @@ -641,7 +772,20 @@ public Object call() throws Exception { @Override public T runOnMainThreadAndWait(Callable callable) throws InterruptedException, ExecutionException { - return Bukkit.getServer().getScheduler().callSyncMethod(CommandHelperPlugin.self, callable).get(); + if(!CommandHelperPlugin.self.isEnabled()) { + throw new CancelCommandException(Implementation.GetServerType().getBranding() + + " tried to schedule a task while the plugin was disabled (is the server shutting down?).", Target.UNKNOWN); + } + + if(Bukkit.isPrimaryThread()) { + try { + return callable.call(); + } catch(Exception e) { + throw new ExecutionException(e); + } + } else { + return Bukkit.getServer().getScheduler().callSyncMethod(CommandHelperPlugin.self, callable).get(); + } } @Override @@ -654,53 +798,25 @@ public MCNote GetNote(int octave, MCTone tone, boolean sharp) { return new BukkitMCNote(octave, tone, sharp); } - private static int maxBlockID = -1; - private static int maxItemID = -1; - private static int maxRecordID = -1; - @Override - public synchronized int getMaxBlockID() { - if (maxBlockID == -1) { - calculateIDs(); - } - return maxBlockID; + public MCColor GetColor(int red, int green, int blue) { + return BukkitMCColor.GetMCColor(Color.fromRGB(red, green, blue)); } @Override - public synchronized int getMaxItemID() { - if (maxItemID == -1) { - calculateIDs(); - } - return maxItemID; + public MCColor GetColor(int red, int green, int blue, int alpha) { + return BukkitMCColor.GetMCColor(Color.fromARGB(alpha, red, green, blue)); } @Override - public synchronized int getMaxRecordID() { - if (maxRecordID == -1) { - calculateIDs(); - } - return maxRecordID; - } - - private void calculateIDs() { - maxBlockID = 0; - maxItemID = 256; - maxRecordID = 2256; - for (Material m : Material.values()) { - int mID = m.getId(); - if (mID >= maxRecordID) { - maxRecordID = mID; - } else if (mID >= maxItemID) { - maxItemID = mID; - } else if (mID >= maxBlockID) { - maxBlockID = mID; - } - } + public MCColor GetColor(String colorName, Target t) throws CREFormatException { + return ConvertorHelper.GetColor(colorName, t); } @Override - public MCColor GetColor(int red, int green, int blue) { - return BukkitMCColor.GetMCColor(Color.fromRGB(red, green, blue)); + public MCPattern GetPattern(MCDyeColor color, MCPatternShape shape) { + return new BukkitMCPattern(new Pattern(BukkitMCDyeColor.getConvertor().getConcreteEnum(color), + (PatternType) shape.getConcrete())); } @Override @@ -710,7 +826,7 @@ public MCFireworkBuilder GetFireworkBuilder() { @Override public MCPluginMeta GetPluginMeta() { - if(pluginMeta == null){ + if(pluginMeta == null) { pluginMeta = new BukkitMCPluginMeta(CommandHelperPlugin.self); addShutdownHook(new Runnable() { @@ -724,31 +840,67 @@ public void run() { } @Override - public MCRecipe GetNewRecipe(MCRecipeType type, MCItemStack result) { - switch (type) { - case FURNACE: - return new BukkitMCFurnaceRecipe(result); - case SHAPED: - return new BukkitMCShapedRecipe(result); - case SHAPELESS: - return new BukkitMCShapelessRecipe(result); + public MCRecipe GetNewRecipe(String key, MCRecipeType type, MCItemStack result) { + + ItemStack is = ((BukkitMCItemStack) result).asItemStack(); + if(type == MCRecipeType.MERCHANT) { + return new BukkitMCMerchantRecipe(new MerchantRecipe(is, Integer.MAX_VALUE)); } - return null; + NamespacedKey nskey = new NamespacedKey(CommandHelperPlugin.self, key); + try { + switch(type) { + case BLASTING: + return new BukkitMCCookingRecipe(new BlastingRecipe(nskey, is, Material.STRUCTURE_VOID, 0.0F, 100), type); + case CAMPFIRE: + return new BukkitMCCookingRecipe(new CampfireRecipe(nskey, is, Material.STRUCTURE_VOID, 0.0F, 100), type); + case FURNACE: + return new BukkitMCCookingRecipe(new FurnaceRecipe(nskey, is, Material.STRUCTURE_VOID, 0.0F, 200), type); + case SHAPED: + return new BukkitMCShapedRecipe(new ShapedRecipe(nskey, is)); + case SHAPELESS: + return new BukkitMCShapelessRecipe(new ShapelessRecipe(nskey, is)); + case SMOKING: + return new BukkitMCCookingRecipe(new SmokingRecipe(nskey, is, Material.STRUCTURE_VOID, 0.0F, 200), type); + case STONECUTTING: + return new BukkitMCStonecuttingRecipe(new StonecuttingRecipe(nskey, is, Material.STRUCTURE_VOID)); + case SMITHING: + case COMPLEX: + throw new IllegalArgumentException("Unable to generate recipe type: " + type.name()); + } + } catch (NoClassDefFoundError ex) { + // doesn't exist on this version. + // eg. 1.14 recipe type on 1.13 + } + throw new IllegalArgumentException("Server version does not support this recipe type: " + type.name()); } @Override public MCRecipe GetRecipe(MCRecipe unspecific) { - Recipe r = ((BukkitMCRecipe) unspecific).r; + Recipe r = (Recipe) unspecific.getHandle(); return BukkitGetRecipe(r); } public static MCRecipe BukkitGetRecipe(Recipe r) { - if (r instanceof ShapelessRecipe) { + if(r instanceof BlastingRecipe) { + return new BukkitMCCookingRecipe(r, MCRecipeType.BLASTING); + } else if(r instanceof CampfireRecipe) { + return new BukkitMCCookingRecipe(r, MCRecipeType.CAMPFIRE); + } else if(r instanceof SmokingRecipe) { + return new BukkitMCCookingRecipe(r, MCRecipeType.SMOKING); + } else if(r instanceof FurnaceRecipe) { + return new BukkitMCCookingRecipe(r, MCRecipeType.FURNACE); + } else if(r instanceof StonecuttingRecipe) { + return new BukkitMCStonecuttingRecipe((StonecuttingRecipe) r); + } else if(r instanceof ComplexRecipe) { + return new BukkitMCComplexRecipe(r); + } else if(r instanceof SmithingRecipe) { + return new BukkitMCSmithingRecipe((SmithingRecipe) r); + } else if(r instanceof ShapelessRecipe) { return new BukkitMCShapelessRecipe((ShapelessRecipe) r); - } else if (r instanceof ShapedRecipe) { + } else if(r instanceof ShapedRecipe) { return new BukkitMCShapedRecipe((ShapedRecipe) r); - } else if (r instanceof FurnaceRecipe) { - return new BukkitMCFurnaceRecipe((FurnaceRecipe) r); + } else if(r instanceof MerchantRecipe) { + return new BukkitMCMerchantRecipe((MerchantRecipe) r); } else { return null; } @@ -761,29 +913,28 @@ public MCCommand getNewCommand(String name) { @Override public MCCommandSender GetCorrectSender(MCCommandSender unspecific) { - if (unspecific == null) { + if(unspecific == null) { return null; } return BukkitGetCorrectSender(((BukkitMCCommandSender) unspecific)._CommandSender()); } public static MCCommandSender BukkitGetCorrectSender(CommandSender sender) { - if (sender instanceof Player) { - return new BukkitMCPlayer((Player) sender); - } else if (sender instanceof ConsoleCommandSender) { - return new BukkitMCConsoleCommandSender((ConsoleCommandSender) sender); - } else if (sender instanceof BlockCommandSender) { - return new BukkitMCBlockCommandSender((BlockCommandSender) sender); + if(sender instanceof Player player) { + return new BukkitMCPlayer(player); + } else if(sender instanceof ConsoleCommandSender consoleCommandSender) { + return new BukkitMCConsoleCommandSender(consoleCommandSender); + } else if(sender instanceof RemoteConsoleCommandSender remoteConsoleCommandSender) { + return new BukkitMCRemoteConsoleCommandSender(remoteConsoleCommandSender); + } else if(sender instanceof BlockCommandSender blockCommandSender) { + return new BukkitMCBlockCommandSender(blockCommandSender); + } else if(sender instanceof CommandMinecart commandMinecart) { + return new BukkitMCCommandMinecart(commandMinecart); } else { - return null; + return new BukkitMCCommandSender(sender); } } - @Override - public MCMaterial GetMaterial(String name) { - return new BukkitMCMaterial(Material.valueOf(name)); - } - @Override public String GetPluginName() { return (String) new Yaml().loadAs(getClass().getResourceAsStream("/plugin.yml"), Map.class).get("name"); @@ -795,7 +946,32 @@ public MCPlugin GetPlugin() { } @Override - public MCColor GetColor(String colorName, Target t) throws Exceptions.FormatException { - return ConvertorHelper.GetColor(colorName, t); + public String GetUser(Environment env) { + MCCommandSender cs = env.getEnv(CommandHelperEnvironment.class).GetCommandSender(); + if(cs == null) { + return null; + } else { + String name = cs.getName(); + if("CONSOLE".equals(name)) { + name = "~console"; + } + return name; + } + } + + @Override + public MCNamespacedKey GetNamespacedKey(String key) { + return new BukkitMCNamespacedKey(NamespacedKey.fromString(key, CommandHelperPlugin.self)); + } + + @Override + public MCTransformation GetTransformation(Quaternionf leftRotation, Quaternionf rightRotation, Vector3f scale, Vector3f translation) { + return new BukkitMCTransformation(new Transformation(translation, leftRotation, scale, rightRotation)); + } + + @Override + public boolean IsMainThread() { + return Bukkit.isPrimaryThread(); } + } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCAgeable.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCAgeable.java deleted file mode 100644 index 6c30e44bf8..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCAgeable.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.laytonsmith.abstraction.bukkit; - -import com.laytonsmith.abstraction.AbstractionObject; -import com.laytonsmith.abstraction.MCAgeable; -import org.bukkit.entity.Ageable; -import org.bukkit.entity.Entity; -import org.bukkit.entity.LivingEntity; - -/** - * - * @author jb_aero - */ -public class BukkitMCAgeable extends BukkitMCLivingEntity implements MCAgeable { - - Ageable a; - public BukkitMCAgeable(Entity be) { - super((LivingEntity) be); - this.a = (Ageable) be; - } - - public BukkitMCAgeable(AbstractionObject ao){ - super((LivingEntity)ao.getHandle()); - this.a = ((Ageable)ao.getHandle()); - } - - @Override - public boolean getCanBreed() { - return a.canBreed(); - } - - @Override - public void setCanBreed(boolean breed) { - a.setBreed(breed); - } - - @Override - public int getAge() { - return a.getAge(); - } - - @Override - public void setAge(int age) { - a.setAge(age); - } - - @Override - public boolean getAgeLock() { - return a.getAgeLock(); - } - - @Override - public void setAgeLock(boolean lock) { - a.setAgeLock(lock); - } - - @Override - public boolean isAdult() { - return a.isAdult(); - } - - @Override - public void setAdult() { - a.setAdult(); - } - - @Override - public void setBaby() { - a.setBaby(); - } - -} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCAnimalTamer.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCAnimalTamer.java index 9db6854265..ee5cd9b5a7 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCAnimalTamer.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCAnimalTamer.java @@ -1,61 +1,37 @@ - - package com.laytonsmith.abstraction.bukkit; import com.laytonsmith.abstraction.AbstractionObject; import com.laytonsmith.abstraction.MCAnimalTamer; -import com.laytonsmith.abstraction.MCHumanEntity; -import com.laytonsmith.abstraction.MCOfflinePlayer; -import org.bukkit.OfflinePlayer; import org.bukkit.entity.AnimalTamer; -import org.bukkit.entity.HumanEntity; -/** - * - * - */ -public class BukkitMCAnimalTamer implements MCAnimalTamer{ - AnimalTamer at; - public BukkitMCAnimalTamer(AnimalTamer at){ - this.at = at; - } - - public BukkitMCAnimalTamer(AbstractionObject a){ - this((AnimalTamer)null); - if(a instanceof MCAnimalTamer){ - this.at = ((AnimalTamer)a.getHandle()); - } else { - throw new ClassCastException(); - } - } - - @Override - public Object getHandle(){ - return at; - } +import java.util.UUID; + +public class BukkitMCAnimalTamer implements MCAnimalTamer { + + AnimalTamer at; + + public BukkitMCAnimalTamer(AnimalTamer at) { + this.at = at; + } - public MCOfflinePlayer getOfflinePlayer() { - if(at instanceof OfflinePlayer){ - return new BukkitMCOfflinePlayer((OfflinePlayer)at); - } - return null; - } + public BukkitMCAnimalTamer(AbstractionObject a) { + this((AnimalTamer) null); + if(a instanceof MCAnimalTamer) { + this.at = ((AnimalTamer) a.getHandle()); + } else { + throw new ClassCastException(); + } + } - public boolean isOfflinePlayer() { - return at instanceof OfflinePlayer; - } + @Override + public Object getHandle() { + return at; + } - public boolean isHumanEntity() { - return at instanceof HumanEntity; - } + public AnimalTamer _tamer() { + return at; + } - public MCHumanEntity getHumanEntity() { - if(at instanceof HumanEntity){ - return new BukkitMCHumanEntity((HumanEntity)at); - } - return null; - } - @Override public String toString() { return at.toString(); @@ -63,7 +39,7 @@ public String toString() { @Override public boolean equals(Object obj) { - return (obj instanceof BukkitMCAnimalTamer?at.equals(((BukkitMCAnimalTamer)obj).at):false); + return obj instanceof BukkitMCAnimalTamer && at.equals(((BukkitMCAnimalTamer) obj).at); } @Override @@ -75,4 +51,9 @@ public int hashCode() { public String getName() { return at.getName(); } + + @Override + public UUID getUniqueID() { + return at.getUniqueId(); + } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCAnvilInventory.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCAnvilInventory.java new file mode 100644 index 0000000000..9961fad70d --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCAnvilInventory.java @@ -0,0 +1,80 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCAnvilInventory; +import com.laytonsmith.abstraction.MCItemStack; +import org.bukkit.inventory.AnvilInventory; + +public class BukkitMCAnvilInventory extends BukkitMCInventory implements MCAnvilInventory { + + AnvilInventory ai; + + public BukkitMCAnvilInventory(AnvilInventory inventory) { + super(inventory); + ai = inventory; + } + + @Override + public MCItemStack getFirstItem() { + return new BukkitMCItemStack(ai.getItem(0)); + } + + @Override + public MCItemStack getSecondItem() { + return new BukkitMCItemStack(ai.getItem(1)); + } + + @Override + public MCItemStack getResult() { + return new BukkitMCItemStack(ai.getItem(2)); + } + + @Override + public int getMaximumRepairCost() { + return ai.getMaximumRepairCost(); + } + + @Override + public int getRepairCost() { + return ai.getRepairCost(); + } + + @Override + public int getRepairCostAmount() { + return ai.getRepairCostAmount(); + } + + @Override + public String getRenameText() { + return ai.getRenameText(); + } + + @Override + public void setFirstItem(MCItemStack stack) { + ai.setItem(0, ((BukkitMCItemStack) stack).asItemStack()); + } + + @Override + public void setSecondItem(MCItemStack stack) { + ai.setItem(1, ((BukkitMCItemStack) stack).asItemStack()); + } + + @Override + public void setResult(MCItemStack stack) { + ai.setItem(2, ((BukkitMCItemStack) stack).asItemStack()); + } + + @Override + public void setMaximumRepairCost(int levels) { + ai.setMaximumRepairCost(levels); + } + + @Override + public void setRepairCost(int levels) { + ai.setRepairCost(levels); + } + + @Override + public void setRepairCostAmount(int levels) { + ai.setRepairCostAmount(levels); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCArmorMeta.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCArmorMeta.java new file mode 100644 index 0000000000..72bb75808a --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCArmorMeta.java @@ -0,0 +1,41 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCArmorMeta; +import com.laytonsmith.abstraction.enums.MCTrimMaterial; +import com.laytonsmith.abstraction.enums.MCTrimPattern; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCTrimMaterial; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCTrimPattern; +import org.bukkit.inventory.meta.ArmorMeta; +import org.bukkit.inventory.meta.trim.ArmorTrim; +import org.bukkit.inventory.meta.trim.TrimMaterial; +import org.bukkit.inventory.meta.trim.TrimPattern; + +public class BukkitMCArmorMeta extends BukkitMCItemMeta implements MCArmorMeta { + + ArmorMeta am; + + public BukkitMCArmorMeta(ArmorMeta im) { + super(im); + this.am = im; + } + + @Override + public boolean hasTrim() { + return am.hasTrim(); + } + + @Override + public void setTrim(MCTrimPattern pattern, MCTrimMaterial material) { + am.setTrim(new ArmorTrim((TrimMaterial) material.getConcrete(), (TrimPattern) pattern.getConcrete())); + } + + @Override + public MCTrimPattern getTrimPattern() { + return BukkitMCTrimPattern.valueOfConcrete(am.getTrim().getPattern()); + } + + @Override + public MCTrimMaterial getTrimMaterial() { + return BukkitMCTrimMaterial.valueOfConcrete(am.getTrim().getMaterial()); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCAttributeModifier.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCAttributeModifier.java new file mode 100644 index 0000000000..e0ced15ae4 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCAttributeModifier.java @@ -0,0 +1,122 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.Implementation; +import com.laytonsmith.abstraction.MCAttributeModifier; +import com.laytonsmith.abstraction.MCNamespacedKey; +import com.laytonsmith.abstraction.enums.EnumConvertor; +import com.laytonsmith.abstraction.enums.MCAttribute; +import com.laytonsmith.abstraction.enums.MCEquipmentSlot; +import com.laytonsmith.abstraction.enums.MCEquipmentSlotGroup; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCAttribute; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCEquipmentSlot; +import com.laytonsmith.annotations.abstractionenum; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeModifier; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.EquipmentSlotGroup; + +import java.util.UUID; + +public class BukkitMCAttributeModifier implements MCAttributeModifier { + + private Attribute a; + private AttributeModifier am; + + public BukkitMCAttributeModifier(Attribute a, AttributeModifier am) { + this.a = a; + this.am = am; + } + + @Override + public Object getHandle() { + return am; + } + + @Override + public MCNamespacedKey getKey() { + return new BukkitMCNamespacedKey(am.getKey()); + } + + @Override + public String getAttributeName() { + return am.getName(); + } + + @Override + public MCAttribute getAttribute() { + return BukkitMCAttribute.valueOfConcrete(a); + } + + @Override + public MCEquipmentSlot getEquipmentSlot() { + EquipmentSlot slot = am.getSlot(); + if(slot == null) { + return null; + } + return BukkitMCEquipmentSlot.getConvertor().getAbstractedEnum(slot); + } + + @Override + public MCEquipmentSlotGroup getEquipmentSlotGroup() { + EquipmentSlotGroup slotGroup = am.getSlotGroup(); + if(slotGroup == EquipmentSlotGroup.ANY) { + return MCEquipmentSlotGroup.ANY; + } else if(slotGroup == EquipmentSlotGroup.ARMOR) { + return MCEquipmentSlotGroup.ARMOR; + } else if(slotGroup == EquipmentSlotGroup.HAND) { + return MCEquipmentSlotGroup.HAND; + } else if(slotGroup.toString().equals("body")) { // BODY slot group is missing from Spigot + return MCEquipmentSlotGroup.BODY; + } else if(slotGroup.toString().equals("saddle")) { // 1.21.5 + return MCEquipmentSlotGroup.SADDLE; + } + return null; + } + + @Override + public MCAttributeModifier.Operation getOperation() { + return Operation.getConvertor().getAbstractedEnum(am.getOperation()); + } + + @Override + public double getAmount() { + return am.getAmount(); + } + + @Override + public UUID getUniqueId() { + return am.getUniqueId(); + } + + @Override + public String toString() { + return am.toString(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof MCAttributeModifier && am.equals(((MCAttributeModifier) obj).getHandle()); + } + + @Override + public int hashCode() { + return am.hashCode(); + } + + @abstractionenum( + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCAttributeModifier.Operation.class, + forConcreteEnum = AttributeModifier.Operation.class + ) + public static class Operation extends EnumConvertor { + + private static Operation instance; + + public static Operation getConvertor() { + if(instance == null) { + instance = new Operation(); + } + return instance; + } + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCAxolotlBucketMeta.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCAxolotlBucketMeta.java new file mode 100644 index 0000000000..9dca655a6e --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCAxolotlBucketMeta.java @@ -0,0 +1,29 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCAxolotlBucketMeta; +import com.laytonsmith.abstraction.enums.MCAxolotlType; +import org.bukkit.entity.Axolotl; +import org.bukkit.inventory.meta.AxolotlBucketMeta; + +public class BukkitMCAxolotlBucketMeta extends BukkitMCItemMeta implements MCAxolotlBucketMeta { + + AxolotlBucketMeta sm; + + public BukkitMCAxolotlBucketMeta(AxolotlBucketMeta im) { + super(im); + this.sm = im; + } + + @Override + public MCAxolotlType getAxolotlType() { + if(!sm.hasVariant()) { + return MCAxolotlType.LUCY; + } + return MCAxolotlType.valueOf(sm.getVariant().name()); + } + + @Override + public void setAxolotlType(MCAxolotlType type) { + sm.setVariant(Axolotl.Variant.valueOf(type.name())); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCBannerMeta.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCBannerMeta.java new file mode 100644 index 0000000000..c59f7320f6 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCBannerMeta.java @@ -0,0 +1,69 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCBannerMeta; +import com.laytonsmith.abstraction.MCPattern; +import org.bukkit.block.banner.Pattern; +import org.bukkit.inventory.meta.BannerMeta; + +import java.util.ArrayList; +import java.util.List; + +public class BukkitMCBannerMeta extends BukkitMCItemMeta implements MCBannerMeta { + + BannerMeta bm; + + public BukkitMCBannerMeta(BannerMeta meta) { + super(meta); + bm = meta; + } + + @Override + public Object getHandle() { + return bm; + } + + @Override + public void addPattern(MCPattern pattern) { + bm.addPattern((Pattern) pattern.getHandle()); + } + + @Override + public MCPattern getPattern(int i) { + return new BukkitMCPattern(bm.getPattern(i)); + } + + @Override + public List getPatterns() { + List bukkitPatterns = bm.getPatterns(); + List patterns = new ArrayList<>(bukkitPatterns.size()); + for(Pattern p : bukkitPatterns) { + patterns.add(new BukkitMCPattern(p)); + } + return patterns; + } + + @Override + public int numberOfPatterns() { + return bm.numberOfPatterns(); + } + + @Override + public void removePattern(int i) { + bm.removePattern(i); + } + + @Override + public void setPattern(int i, MCPattern pattern) { + bm.setPattern(i, (Pattern) pattern.getHandle()); + } + + @Override + public void setPatterns(List patterns) { + List bukkitPatterns = new ArrayList<>(patterns.size()); + for(MCPattern pattern : patterns) { + bukkitPatterns.add((Pattern) pattern.getHandle()); + } + bm.setPatterns(bukkitPatterns); + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCBlockCommandSender.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCBlockCommandSender.java index 223b7beafe..96f2b3024b 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCBlockCommandSender.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCBlockCommandSender.java @@ -6,27 +6,24 @@ import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCBlock; import org.bukkit.command.BlockCommandSender; -/** - * - * - */ public class BukkitMCBlockCommandSender extends BukkitMCCommandSender implements MCBlockCommandSender { BlockCommandSender bcs; - public BukkitMCBlockCommandSender(AbstractionObject a){ - this((BlockCommandSender)null); - if(a instanceof MCBlockCommandSender){ - this.c = ((BlockCommandSender)a.getHandle()); - } else { - throw new ClassCastException(); - } + + public BukkitMCBlockCommandSender(AbstractionObject a) { + this((BlockCommandSender) null); + if(a instanceof MCBlockCommandSender) { + this.c = ((BlockCommandSender) a.getHandle()); + } else { + throw new ClassCastException(); + } } - - public BukkitMCBlockCommandSender(BlockCommandSender bcs){ + + public BukkitMCBlockCommandSender(BlockCommandSender bcs) { super(bcs); this.bcs = bcs; } - + @Override public MCBlock getBlock() { return new BukkitMCBlock(bcs.getBlock()); diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCBlockStateMeta.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCBlockStateMeta.java new file mode 100644 index 0000000000..5524b9aaba --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCBlockStateMeta.java @@ -0,0 +1,82 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.PureUtilities.Common.ReflectionUtils; +import com.laytonsmith.abstraction.blocks.MCBlockState; +import com.laytonsmith.abstraction.MCBlockStateMeta; +import com.laytonsmith.abstraction.enums.MCVersion; +import com.laytonsmith.core.Static; +import org.bukkit.Material; +import org.bukkit.block.BlockState; +import org.bukkit.inventory.meta.BlockStateMeta; + +import java.util.logging.Level; + +public class BukkitMCBlockStateMeta extends BukkitMCItemMeta implements MCBlockStateMeta { + + BlockStateMeta bsm; + Material mat; + + public BukkitMCBlockStateMeta(BlockStateMeta meta) { + super(meta); + this.bsm = meta; + this.mat = null; + } + + public BukkitMCBlockStateMeta(BlockStateMeta meta, Material mat) { + super(meta); + this.bsm = meta; + this.mat = mat; + } + + @Override + public boolean hasBlockState() { + return bsm.hasBlockState(); + } + + @Override + public MCBlockState getBlockState() { + return getBlockState(false); + } + + @Override + public MCBlockState getBlockState(boolean copy) { + BlockStateMeta meta = bsm; + if(copy && Static.getServer().getMinecraftVersion().lt(MCVersion.MC1_18_X)) { + // For some types, getBlockState() writes to the block entity tags, which are not copied prior to 1.18.2. + // Since the tags are no longer equal when compared later, unexpected behavior can occur. + // For example, when getting a shulker box's BlockState on BlockPlaceEvent, it can duplicate the item. + // Copying the meta before getting the block state ensures the original tags are unaffected. + meta = (BlockStateMeta) meta.clone(); + } else if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_20_1) + && mat == Material.DECORATED_POT + && Static.getServer().getMinecraftVersion().lte(MCVersion.MC1_20_4)) { + // Workaround upstream bug with decorated pots missing the "id" tag in the BlockEntityTag when broken. + // Without this fix, getting the BlockState from this meta may result in a default decorated pot. + try { + Class craftMetaBlockStateClass = Class.forName(((BukkitMCServer) Static.getServer()).getCraftBukkitPackage() + + ".inventory.CraftMetaBlockState"); + Class nbtTagCompoundClass = Class.forName("net.minecraft.nbt.NBTTagCompound"); + Object nbt = ReflectionUtils.get(craftMetaBlockStateClass, meta, "blockEntityTag"); + if(nbt != null) { + ReflectionUtils.invokeMethod(nbtTagCompoundClass, nbt, "a", new Class[]{String.class, String.class}, + new Object[]{"id", "minecraft:decorated_pot"}); + } + } catch (Exception ex) { + Static.getLogger().log(Level.WARNING, "Failed to fix decorated pot tag.", ex); + } + } + try { + return BukkitConvertor.BukkitGetCorrectBlockState(meta.getBlockState()); + } catch (Exception ex) { + // Broken server implementation. + Static.getLogger().log(Level.WARNING, ex.getMessage() + " when" + + " trying to get the BlockState from " + bsm.toString()); + return null; + } + } + + @Override + public void setBlockState(MCBlockState state) { + bsm.setBlockState((BlockState) state.getHandle()); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCBookMeta.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCBookMeta.java index 67088b66a9..6e00f7bb8d 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCBookMeta.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCBookMeta.java @@ -8,6 +8,7 @@ public class BukkitMCBookMeta extends BukkitMCItemMeta implements MCBookMeta { BookMeta bm; + public BukkitMCBookMeta(BookMeta im) { super(im); this.bm = im; @@ -82,4 +83,18 @@ public void setPages(String... pages) { bm.setPages(pages); } + @Override + public Generation getGeneration() { + BookMeta.Generation gen = bm.getGeneration(); + if(gen == null) { + return Generation.ORIGINAL; + } + return Generation.valueOf(gen.name()); + } + + @Override + public void setGeneration(Generation gen) { + bm.setGeneration(BookMeta.Generation.valueOf(gen.name())); + } + } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCBossBar.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCBossBar.java new file mode 100644 index 0000000000..3896c8bd33 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCBossBar.java @@ -0,0 +1,117 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCBossBar; +import com.laytonsmith.abstraction.MCPlayer; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCPlayer; +import com.laytonsmith.abstraction.enums.MCBarColor; +import com.laytonsmith.abstraction.enums.MCBarStyle; +import org.bukkit.boss.BarColor; +import org.bukkit.boss.BarStyle; +import org.bukkit.boss.BossBar; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; + +public class BukkitMCBossBar implements MCBossBar { + + private final BossBar bb; + + public BukkitMCBossBar(BossBar bb) { + this.bb = bb; + } + + @Override + public Object getHandle() { + return bb; + } + + @Override + public String getTitle() { + return bb.getTitle(); + } + + @Override + public void setTitle(String title) { + bb.setTitle(title); + } + + @Override + public MCBarColor getColor() { + return MCBarColor.valueOf(bb.getColor().name()); + } + + @Override + public void setColor(MCBarColor color) { + bb.setColor(BarColor.valueOf(color.name())); + } + + @Override + public MCBarStyle getStyle() { + return MCBarStyle.valueOf(bb.getStyle().name()); + } + + @Override + public void setStyle(MCBarStyle style) { + bb.setStyle(BarStyle.valueOf(style.name())); + } + + @Override + public double getProgress() { + return bb.getProgress(); + } + + @Override + public void setProgress(double progress) { + bb.setProgress(progress); + } + + @Override + public void addPlayer(MCPlayer player) { + bb.addPlayer((Player) player.getHandle()); + } + + @Override + public void removePlayer(MCPlayer player) { + bb.removePlayer((Player) player.getHandle()); + } + + @Override + public void removeAllPlayers() { + bb.removeAll(); + } + + @Override + public List getPlayers() { + List players = new ArrayList<>(); + for(Player player : bb.getPlayers()) { + players.add(new BukkitMCPlayer(player)); + } + return players; + } + + @Override + public boolean isVisible() { + return bb.isVisible(); + } + + @Override + public void setVisible(boolean visible) { + bb.setVisible(visible); + } + + @Override + public String toString() { + return bb.toString(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof BukkitMCBossBar && bb.equals(((BukkitMCBossBar) obj).bb); + } + + @Override + public int hashCode() { + return bb.hashCode(); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCBrewerInventory.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCBrewerInventory.java new file mode 100644 index 0000000000..afe99c62b5 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCBrewerInventory.java @@ -0,0 +1,75 @@ +package com.laytonsmith.abstraction.bukkit; + +import org.bukkit.block.BrewingStand; +import org.bukkit.inventory.BrewerInventory; +import org.bukkit.inventory.ItemStack; + +import com.laytonsmith.abstraction.MCBrewerInventory; +import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.blocks.MCBrewingStand; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCBrewingStand; + +public class BukkitMCBrewerInventory extends BukkitMCInventory implements MCBrewerInventory { + + private BrewerInventory inv; + + public BukkitMCBrewerInventory(BrewerInventory inv) { + super(inv); + this.inv = inv; + } + + @Override + public MCItemStack getFuel() { + return new BukkitMCItemStack(this.inv.getFuel()); + } + + @Override + public MCItemStack getIngredient() { + return new BukkitMCItemStack(this.inv.getIngredient()); + } + + @Override + public MCItemStack getLeftBottle() { + return new BukkitMCItemStack(this.inv.getItem(0)); + } + + @Override + public MCItemStack getMiddleBottle() { + return new BukkitMCItemStack(this.inv.getItem(1)); + } + + @Override + public MCItemStack getRightBottle() { + return new BukkitMCItemStack(this.inv.getItem(2)); + } + + @Override + public void setFuel(MCItemStack stack) { + this.inv.setFuel((ItemStack) stack.getHandle()); + } + + @Override + public void setIngredient(MCItemStack stack) { + this.inv.setIngredient((ItemStack) stack.getHandle()); + } + + @Override + public void setLeftBottle(MCItemStack stack) { + this.inv.setItem(0, (ItemStack) stack.getHandle()); + } + + @Override + public void setMiddleBottle(MCItemStack stack) { + this.inv.setItem(1, (ItemStack) stack.getHandle()); + } + + @Override + public void setRightBottle(MCItemStack stack) { + this.inv.setItem(2, (ItemStack) stack.getHandle()); + } + + @Override + public MCBrewingStand getHolder() { + return new BukkitMCBrewingStand((BrewingStand) this.inv.getHolder()); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCBundleMeta.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCBundleMeta.java new file mode 100644 index 0000000000..0e8f0dd4e7 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCBundleMeta.java @@ -0,0 +1,40 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.AbstractionObject; +import com.laytonsmith.abstraction.MCBundleMeta; +import com.laytonsmith.abstraction.MCItemStack; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.BundleMeta; + +import java.util.ArrayList; +import java.util.List; + +public class BukkitMCBundleMeta extends BukkitMCItemMeta implements MCBundleMeta { + + BundleMeta bm; + + public BukkitMCBundleMeta(BundleMeta m) { + super(m); + this.bm = m; + } + + public BukkitMCBundleMeta(AbstractionObject o) { + super(o); + this.bm = (BundleMeta) o; + } + + @Override + public List getItems() { + List items = this.bm.getItems(); + List abstractItems = new ArrayList<>(items.size()); + for(ItemStack is : items) { + abstractItems.add(new BukkitMCItemStack(is)); + } + return abstractItems; + } + + @Override + public void addItem(MCItemStack abstractItem) { + this.bm.addItem((ItemStack) abstractItem.getHandle()); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCChunk.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCChunk.java index 3a2d5c5e2f..559d63780b 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCChunk.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCChunk.java @@ -3,20 +3,18 @@ import com.laytonsmith.abstraction.MCChunk; import com.laytonsmith.abstraction.MCEntity; import com.laytonsmith.abstraction.MCWorld; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCEntity; import org.bukkit.Chunk; import org.bukkit.entity.Entity; -/** - * - * @author import - */ public class BukkitMCChunk implements MCChunk { + Chunk c; public BukkitMCChunk(Chunk c) { this.c = c; } - + @Override public int getX() { return c.getX(); @@ -31,7 +29,7 @@ public int getZ() { public MCEntity[] getEntities() { Entity[] entities = c.getEntities(); MCEntity[] r = new MCEntity[entities.length]; - for (int i = 0 ; i < r.length ; i++) { + for(int i = 0; i < r.length; i++) { r[i] = new BukkitMCEntity(entities[i]); } return r; @@ -41,7 +39,7 @@ public MCEntity[] getEntities() { public MCWorld getWorld() { return new BukkitMCWorld(c.getWorld()); } - + @Override public Object getHandle() { return c; @@ -49,13 +47,13 @@ public Object getHandle() { @Override public boolean equals(Object o) { - return o instanceof MCChunk ? this.c.equals(((BukkitMCChunk)o).c) : false; + return o instanceof MCChunk && this.c.equals(((BukkitMCChunk) o).c); } @Override public int hashCode() { return c.hashCode(); -} + } @Override public String toString() { diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCColor.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCColor.java index ab26bcbb7d..23ff80fdc1 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCColor.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCColor.java @@ -1,34 +1,42 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ package com.laytonsmith.abstraction.bukkit; import com.laytonsmith.abstraction.MCColor; +import com.laytonsmith.abstraction.enums.MCVersion; +import com.laytonsmith.core.Static; import org.bukkit.Color; -/** - * - * - */ -public class BukkitMCColor implements MCColor { - - private static final BukkitMCColor builder = new BukkitMCColor(); - public static MCColor GetMCColor(Color c){ - return builder.build(c.getRed(), c.getGreen(), c.getBlue()); +public final class BukkitMCColor implements MCColor { + + private static final BukkitMCColor BUILDER = new BukkitMCColor(); + + public static MCColor GetMCColor(Color c) { + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_19_4)) { + return BUILDER.build(c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()); + } else { + return BUILDER.build(c.getRed(), c.getGreen(), c.getBlue()); + } } - - public static Color GetColor(MCColor c){ - return Color.fromRGB(c.getRed(), c.getGreen(), c.getBlue()); + + public static Color GetColor(MCColor c) { + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_19_4)) { + return Color.fromARGB(c.getAlpha(), c.getRed(), c.getGreen(), c.getBlue()); + } else { + return Color.fromRGB(c.getRed(), c.getGreen(), c.getBlue()); + } } - - private BukkitMCColor(){ - // + + private BukkitMCColor() { } - + private int red; private int green; private int blue; + private int alpha; + + @Override + public int getAlpha() { + return alpha; + } @Override public int getRed() { @@ -48,16 +56,27 @@ public int getBlue() { @Override public MCColor build(int red, int green, int blue) { BukkitMCColor color = new BukkitMCColor(); + color.alpha = 255; color.red = red; color.green = green; color.blue = blue; return color; } + @Override + public MCColor build(int red, int green, int blue, int alpha) { + BukkitMCColor color = new BukkitMCColor(); + color.alpha = alpha; + color.red = red; + color.green = green; + color.blue = blue; + return color; + } @Override public int hashCode() { int hash = 5; + hash = 11 * hash + this.alpha; hash = 11 * hash + this.red; hash = 11 * hash + this.green; hash = 11 * hash + this.blue; @@ -66,26 +85,25 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (obj == null) { + if(obj == null) { return false; } - if (getClass() != obj.getClass()) { + if(getClass() != obj.getClass()) { return false; } final BukkitMCColor other = (BukkitMCColor) obj; - if (this.red != other.red) { + if(this.red != other.red) { return false; } - if (this.green != other.green) { + if(this.green != other.green) { return false; } - if (this.blue != other.blue) { + if(this.blue != other.blue) { + return false; + } + if(this.alpha != other.alpha) { return false; } return true; } - - - - } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCColorableArmorMeta.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCColorableArmorMeta.java new file mode 100644 index 0000000000..562597bb6c --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCColorableArmorMeta.java @@ -0,0 +1,26 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCColor; +import com.laytonsmith.abstraction.MCColorableArmorMeta; +import org.bukkit.inventory.meta.ColorableArmorMeta; + +public class BukkitMCColorableArmorMeta extends BukkitMCArmorMeta implements MCColorableArmorMeta { + + ColorableArmorMeta lam; + + public BukkitMCColorableArmorMeta(ColorableArmorMeta im) { + super(im); + lam = im; + } + + @Override + public MCColor getColor() { + return BukkitMCColor.GetMCColor(lam.getColor()); + } + + @Override + public void setColor(MCColor color) { + lam.setColor(BukkitMCColor.GetColor(color)); + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCCommand.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCCommand.java index 15524139ec..9ce6045e67 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCCommand.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCCommand.java @@ -1,6 +1,7 @@ package com.laytonsmith.abstraction.bukkit; import com.laytonsmith.PureUtilities.Common.ReflectionUtils; +import com.laytonsmith.PureUtilities.Common.StringUtils; import com.laytonsmith.abstraction.MCCommand; import com.laytonsmith.abstraction.MCCommandMap; import com.laytonsmith.abstraction.MCCommandSender; @@ -10,29 +11,32 @@ import com.laytonsmith.core.constructs.CArray; import com.laytonsmith.core.constructs.CBoolean; import com.laytonsmith.core.constructs.CString; -import com.laytonsmith.core.constructs.Construct; import com.laytonsmith.core.constructs.Target; +import com.laytonsmith.core.environments.CommandHelperEnvironment; import com.laytonsmith.core.events.Driver; import com.laytonsmith.core.events.EventUtils; -import com.laytonsmith.core.exceptions.FunctionReturnException; +import com.laytonsmith.core.exceptions.CRE.CREPluginInternalException; +import com.laytonsmith.core.exceptions.ConfigRuntimeException; import com.laytonsmith.core.functions.Commands; +import com.laytonsmith.core.natives.interfaces.Callable; +import com.laytonsmith.core.natives.interfaces.Mixed; import java.util.ArrayList; import java.util.List; import org.bukkit.command.Command; +import org.bukkit.command.CommandException; +import org.bukkit.command.CommandSender; import org.bukkit.command.PluginCommand; +import org.bukkit.command.PluginIdentifiableCommand; import org.bukkit.plugin.Plugin; -/** - * - * @author jb_aero - */ public class BukkitMCCommand implements MCCommand { Command cmd; + public BukkitMCCommand(Command command) { cmd = command; } - + @Override public Object getHandle() { return cmd; @@ -60,7 +64,7 @@ public String getName() { @Override public String getPermission() { - return cmd.getPermission() == null ? null : cmd.getPermission(); + return cmd.getPermission(); } @Override @@ -133,7 +137,7 @@ public boolean isRegistered() { public boolean unregister(MCCommandMap map) { return cmd.unregister(((BukkitMCCommandMap) map).scm); } - + public static MCCommand newCommand(String name) { return new BukkitMCCommand(ReflectionUtils.newInstance(PluginCommand.class, new Class[]{String.class, Plugin.class}, new Object[]{name, CommandHelperPlugin.self})); @@ -141,16 +145,16 @@ public static MCCommand newCommand(String name) { @Override public MCPlugin getPlugin() { - if (!(cmd instanceof PluginCommand)) { + if(!(cmd instanceof PluginIdentifiableCommand)) { return null; } - return ((PluginCommand) cmd).getPlugin() == null ? null : new BukkitMCPlugin(((PluginCommand) cmd).getPlugin()); + return new BukkitMCPlugin(((PluginIdentifiableCommand) cmd).getPlugin()); } @Override public MCPlugin getExecutor() { // TODO Not all plugins execute commands in their main class, so this cast won't always work - if (!(cmd instanceof PluginCommand)) { + if(!(cmd instanceof PluginCommand)) { return null; } return new BukkitMCPlugin((Plugin) ((PluginCommand) cmd).getExecutor()); @@ -159,7 +163,7 @@ public MCPlugin getExecutor() { @Override public MCPlugin getTabCompleter() { // TODO see above - if (!(cmd instanceof PluginCommand)) { + if(!(cmd instanceof PluginCommand)) { return null; } return new BukkitMCPlugin((Plugin) ((PluginCommand) cmd).getTabCompleter()); @@ -167,82 +171,103 @@ public MCPlugin getTabCompleter() { @Override public void setExecutor(MCPlugin plugin) { - if (cmd instanceof PluginCommand) { + if(cmd instanceof PluginCommand) { ((PluginCommand) cmd).setExecutor(((BukkitMCPlugin) plugin).getHandle()); } } @Override public void setTabCompleter(MCPlugin plugin) { - if (cmd instanceof PluginCommand) { + if(cmd instanceof PluginCommand) { ((PluginCommand) cmd).setTabCompleter(((BukkitMCPlugin) plugin).getHandle()); } } - + + @Override + public List tabComplete(MCCommandSender sender, String alias, String[] args) { + try { + return cmd.tabComplete((CommandSender) sender.getHandle(), alias, args, null); + } catch (CommandException ex) { + throw new CREPluginInternalException(ex.getMessage(), Target.UNKNOWN); + } + } + @Override public int hashCode() { return cmd.hashCode(); } - + @Override public boolean equals(Object obj) { return cmd.equals(obj); } - + @Override public String toString() { return cmd.toString(); } - // I may be able to move these to c.l.c.f.Commands.java @Override public List handleTabComplete(MCCommandSender sender, String alias, String[] args) { - if (Commands.onTabComplete.containsKey(cmd.getName().toLowerCase())) { + if(Commands.onTabComplete.containsKey(cmd.getName().toLowerCase())) { Target t = Target.UNKNOWN; CArray cargs = new CArray(t); - for (String arg : args) { - cargs.push(new CString(arg, t)); + for(String arg : args) { + cargs.push(new CString(arg, t), t); } + Callable closure = Commands.onTabComplete.get(cmd.getName().toLowerCase()); + closure.getEnv().getEnv(CommandHelperEnvironment.class).SetCommandSender(sender); try { - Commands.onTabComplete.get(cmd.getName().toLowerCase()).execute(new Construct[]{ - new CString(alias, t), new CString(sender.getName(), t), cargs, - new CArray(t) // reserved for an obgen style command array - }); - } catch (FunctionReturnException e) { - Construct fret = e.getReturn(); - if (fret instanceof CArray) { - List ret = new ArrayList(); - for (Construct key : ((CArray) fret).asList()) { - ret.add(key.val()); + Mixed fret = closure.executeCallable(null, t, new CString(alias, t), new CString(sender.getName(), t), cargs, + new CArray(t) // reserved for an obgen style command array + ); + if(fret.isInstanceOf(CArray.TYPE)) { + List ret = new ArrayList<>(); + if(((CArray) fret).inAssociativeMode()) { + for(Mixed key : ((CArray) fret).keySet()) { + ret.add(((CArray) fret).get(key, Target.UNKNOWN).val()); + } + } else { + for(Mixed value : ((CArray) fret).asList()) { + ret.add(value.val()); + } } return ret; } + } catch (ConfigRuntimeException cre) { + ConfigRuntimeException.HandleUncaughtException(cre, closure.getEnv()); + return new ArrayList<>(); } } BukkitMCCommandTabCompleteEvent event = new BukkitMCCommandTabCompleteEvent(sender, cmd, alias, args); - EventUtils.TriggerExternal(event); EventUtils.TriggerListener(Driver.TAB_COMPLETE, "tab_complete_command", event); return event.getCompletions(); } - + @Override public boolean handleCustomCommand(MCCommandSender sender, String label, String[] args) { - if (Commands.onCommand.containsKey(cmd.getName().toLowerCase())) { + if(Commands.onCommand.containsKey(cmd.getName().toLowerCase())) { Target t = Target.UNKNOWN; CArray cargs = new CArray(t); - for (String arg : args) { - cargs.push(new CString(arg, t)); + for(String arg : args) { + cargs.push(new CString(arg, t), t); } + + Callable closure = Commands.onCommand.get(cmd.getName().toLowerCase()); + CommandHelperEnvironment cEnv = closure.getEnv().getEnv(CommandHelperEnvironment.class); + cEnv.SetCommandSender(sender); + cEnv.SetCommand("/" + label + StringUtils.Join(args, " ")); + try { - Commands.onCommand.get(cmd.getName().toLowerCase()).execute(new Construct[]{ - new CString(label, t), new CString(sender.getName(), t), cargs, - new CArray(t) // reserved for an obgen style command array - }); - } catch (FunctionReturnException e) { - Construct fret = e.getReturn(); - if (fret instanceof CBoolean) { + Mixed fret = closure.executeCallable(null, t, new CString(label, t), new CString(sender.getName(), t), cargs, + new CArray(t) // reserved for an obgen style command array + ); + if(fret.isInstanceOf(CBoolean.TYPE)) { return ((CBoolean) fret).getBoolean(); } + } catch (ConfigRuntimeException cre) { + cre.setEnv(closure.getEnv()); + ConfigRuntimeException.HandleUncaughtException(cre, closure.getEnv()); } return true; } else { diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCCommandMap.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCCommandMap.java index ee2ab640bf..ea9221d3a1 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCCommandMap.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCCommandMap.java @@ -5,17 +5,21 @@ import com.laytonsmith.abstraction.MCCommandMap; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.Map; + +import com.laytonsmith.commandhelper.CommandHelperPlugin; +import org.bukkit.Bukkit; +import org.bukkit.Server; import org.bukkit.command.Command; import org.bukkit.command.SimpleCommandMap; -/** - * - * @author jb_aero - */ public class BukkitMCCommandMap implements MCCommandMap { + private static boolean syncScheduled = false; + SimpleCommandMap scm; + public BukkitMCCommandMap(SimpleCommandMap invokeMethod) { scm = invokeMethod; } @@ -25,11 +29,30 @@ public Object getHandle() { return scm; } + /** + * Syncs the command list with players and other command senders that use the vanilla dispatcher next tick. + */ + private static void SyncCommands() { + if(CommandHelperPlugin.self.isFirstLoad()) { + // Craftbukkit already syncs after plugins are enabled, so we don't have to. + return; + } + if(!syncScheduled) { + Bukkit.getScheduler().runTask(CommandHelperPlugin.self, () -> { + Server s = Bukkit.getServer(); + ReflectionUtils.invokeMethod(s.getClass(), s, "syncCommands"); + syncScheduled = false; + }); + syncScheduled = true; + } + } + @Override public void clearCommands() { scm.clearCommands(); + SyncCommands(); } - + @Override public boolean isCommand(String name) { return scm.getCommand(name) != null; @@ -37,13 +60,17 @@ public boolean isCommand(String name) { @Override public MCCommand getCommand(String name) { - return scm.getCommand(name) == null ? null : new BukkitMCCommand(scm.getCommand(name)); + Command cmd = scm.getCommand(name); + if(cmd == null) { + return null; + } + return new BukkitMCCommand(cmd); } @Override public List getCommands() { - List cmds = new ArrayList(); - for (Command c : scm.getCommands()) { + List cmds = new ArrayList<>(); + for(Command c : scm.getCommands()) { cmds.add(new BukkitMCCommand(c)); } return cmds; @@ -51,35 +78,67 @@ public List getCommands() { @Override public boolean register(String fallback, MCCommand cmd) { - return scm.register(fallback, ((BukkitMCCommand) cmd).cmd); + boolean success = scm.register(fallback, ((BukkitMCCommand) cmd).cmd); + SyncCommands(); + return success; } @Override public boolean register(String label, String fallback, MCCommand cmd) { - return scm.register(label, fallback, ((BukkitMCCommand) cmd).cmd); + boolean success = scm.register(label, fallback, ((BukkitMCCommand) cmd).cmd); + SyncCommands(); + return success; } - @SuppressWarnings("unchecked") @Override public boolean unregister(MCCommand cmd) { - if (cmd.isRegistered()) { - ((Map) ReflectionUtils.get(scm.getClass(), scm, "knownCommands")).remove(cmd.getName()); - return cmd.unregister(this); + if(cmd.isRegistered()) { + Map knownCommands = getKnownCommands(); + String prefix = null; + if(cmd.getPlugin() != null) { + prefix = cmd.getPlugin().getName().toLowerCase(Locale.ENGLISH); + knownCommands.remove(prefix + ":" + cmd.getName()); + } + knownCommands.remove(cmd.getName()); + for(String alias : cmd.getAliases()) { + if(prefix != null) { + knownCommands.remove(prefix + ":" + alias); + } + knownCommands.remove(alias); + } + SyncCommands(); + return true; } else { return false; } } - + + @Override + public boolean unregister(String label) { + Command cmd = getKnownCommands().remove(label); + if(cmd != null) { + cmd.getAliases().remove(label); + SyncCommands(); + return true; + } + return false; + } + + @SuppressWarnings("unchecked") + private Map getKnownCommands() { + return (Map) ReflectionUtils.invokeMethod(scm.getClass(), scm, "getKnownCommands"); + } + @Override public boolean equals(Object obj) { return scm.equals(obj); } - + @Override public int hashCode() { return scm.hashCode(); } - + @Override public String toString() { return scm.toString(); diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCCommandSender.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCCommandSender.java index ef41f2076e..d3e40511d1 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCCommandSender.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCCommandSender.java @@ -1,73 +1,71 @@ - - package com.laytonsmith.abstraction.bukkit; import com.laytonsmith.abstraction.AbstractionObject; import com.laytonsmith.abstraction.MCCommandSender; import com.laytonsmith.abstraction.MCServer; +import com.laytonsmith.core.Static; import org.bukkit.command.CommandSender; import org.bukkit.command.ConsoleCommandSender; import org.bukkit.entity.Player; +import org.bukkit.permissions.PermissionAttachmentInfo; + +import java.util.ArrayList; +import java.util.List; + +public class BukkitMCCommandSender implements MCCommandSender { + + CommandSender c; + + public BukkitMCCommandSender(CommandSender c) { + this.c = c; + } + + public BukkitMCCommandSender(AbstractionObject a) { + this((CommandSender) null); + if(a instanceof MCCommandSender) { + this.c = ((CommandSender) a.getHandle()); + } else { + throw new ClassCastException(); + } + } -/** - * - * - */ -public class BukkitMCCommandSender implements MCCommandSender{ - - CommandSender c; - public BukkitMCCommandSender(CommandSender c){ - this.c = c; - } - - public BukkitMCCommandSender(AbstractionObject a){ - this((CommandSender)null); - if(a instanceof MCCommandSender){ - this.c = ((CommandSender)a.getHandle()); - } else { - throw new ClassCastException(); - } - } - @Override - public Object getHandle(){ - return c; - } - + public Object getHandle() { + return c; + } + @Override - public void sendMessage(String string) { - c.sendMessage(string); - } + public void sendMessage(String string) { + c.sendMessage(string); + } - @Override - public MCServer getServer() { - return new BukkitMCServer(); - } + public MCServer getServer() { + return new BukkitMCServer(); + } - @Override - public String getName() { - return c.getName(); - } + public String getName() { + return c.getName(); + } @Override - public boolean isOp() { - return c.isOp(); - } - - public CommandSender _CommandSender() { - return c; - } - - public boolean instanceofPlayer() { - return c instanceof Player; - } - - public boolean instanceofMCConsoleCommandSender() { - return c instanceof ConsoleCommandSender; - } - + public boolean isOp() { + return c.isOp(); + } + + public CommandSender _CommandSender() { + return c; + } + + public boolean instanceofPlayer() { + return c instanceof Player; + } + + public boolean instanceofMCConsoleCommandSender() { + return c instanceof ConsoleCommandSender; + } + @Override public String toString() { return c.toString(); @@ -75,12 +73,41 @@ public String toString() { @Override public boolean equals(Object obj) { - return (obj instanceof BukkitMCCommandSender?c.equals(((BukkitMCCommandSender)obj).c):false); + return obj instanceof BukkitMCCommandSender && c.equals(((BukkitMCCommandSender) obj).c); } @Override public int hashCode() { return c.hashCode(); } - + + @Override + public boolean hasPermission(String perm) { + return c.hasPermission(perm); + } + + @Override + public boolean isPermissionSet(String perm) { + return c.isPermissionSet(perm); + } + + @Override + public List getGroups() { + // As in https://github.com/sk89q/WorldEdit/blob/master/ + // worldedit-bukkit/src/main/java/com/sk89q/wepif/DinnerPermsResolver.java#L112-L126 + List groupNames = new ArrayList<>(); + for(PermissionAttachmentInfo permAttach : c.getEffectivePermissions()) { + String perm = permAttach.getPermission(); + if(!(perm.startsWith(Static.GROUP_PREFIX) && permAttach.getValue())) { + continue; + } + groupNames.add(perm.substring(Static.GROUP_PREFIX.length(), perm.length())); + } + return groupNames; + } + + @Override + public boolean inGroup(String groupName) { + return getGroups().contains(groupName); + } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCCompassMeta.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCCompassMeta.java new file mode 100644 index 0000000000..ffb7e728c6 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCCompassMeta.java @@ -0,0 +1,44 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCCompassMeta; +import com.laytonsmith.abstraction.MCLocation; +import org.bukkit.Location; +import org.bukkit.inventory.meta.CompassMeta; + +public class BukkitMCCompassMeta extends BukkitMCItemMeta implements MCCompassMeta { + + CompassMeta cm; + + public BukkitMCCompassMeta(CompassMeta im) { + super(im); + this.cm = im; + } + + @Override + public MCLocation getTargetLocation() { + Location l = cm.getLodestone(); + if(l == null || l.getWorld() == null) { + return null; + } + return new BukkitMCLocation(l); + } + + @Override + public void setTargetLocation(MCLocation location) { + if(location == null) { + cm.setLodestone(null); + } else { + cm.setLodestone((Location) location.getHandle()); + } + } + + @Override + public boolean isLodestoneTracked() { + return cm.isLodestoneTracked(); + } + + @Override + public void setLodestoneTracked(boolean tracked) { + cm.setLodestoneTracked(tracked); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCComplexRecipe.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCComplexRecipe.java new file mode 100644 index 0000000000..ba200301a2 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCComplexRecipe.java @@ -0,0 +1,39 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCComplexRecipe; +import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.enums.MCRecipeType; +import org.bukkit.Keyed; +import org.bukkit.inventory.Recipe; + +public class BukkitMCComplexRecipe extends BukkitMCRecipe implements MCComplexRecipe { + + public BukkitMCComplexRecipe(Recipe recipe) { + super(recipe); + } + + @Override + public String getKey() { + return ((Keyed) getHandle()).getKey().getKey(); + } + + @Override + public MCRecipeType getRecipeType() { + return MCRecipeType.COMPLEX; + } + + @Override + public String getGroup() { + return null; + } + + @Override + public void setGroup(String group) { + // complex recipes are basically dummy recipes with no group + } + + @Override + public MCItemStack getResult() { + return new BukkitMCItemStack(((Recipe) getHandle()).getResult()); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCConsoleCommandSender.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCConsoleCommandSender.java index 08425e164c..578f1fa5ce 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCConsoleCommandSender.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCConsoleCommandSender.java @@ -1,20 +1,15 @@ - - package com.laytonsmith.abstraction.bukkit; import com.laytonsmith.abstraction.MCConsoleCommandSender; import org.bukkit.command.ConsoleCommandSender; -/** - * - * - */ -public class BukkitMCConsoleCommandSender extends BukkitMCCommandSender implements MCConsoleCommandSender{ +public class BukkitMCConsoleCommandSender extends BukkitMCCommandSender implements MCConsoleCommandSender { + + ConsoleCommandSender ccs; + + public BukkitMCConsoleCommandSender(ConsoleCommandSender ccs) { + super(ccs); + this.ccs = ccs; + } - ConsoleCommandSender ccs; - public BukkitMCConsoleCommandSender(ConsoleCommandSender ccs){ - super(ccs); - this.ccs = ccs; - } - } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCCookingRecipe.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCCookingRecipe.java new file mode 100644 index 0000000000..ca6c5182e1 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCCookingRecipe.java @@ -0,0 +1,124 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCCookingRecipe; +import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.MCRecipeChoice; +import com.laytonsmith.abstraction.MCRecipeChoice.ExactChoice; +import com.laytonsmith.abstraction.MCRecipeChoice.MaterialChoice; +import com.laytonsmith.abstraction.blocks.MCMaterial; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCMaterial; +import com.laytonsmith.abstraction.enums.MCRecipeType; +import org.bukkit.Material; +import org.bukkit.inventory.CookingRecipe; +import org.bukkit.inventory.Recipe; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.RecipeChoice; + +import java.util.ArrayList; +import java.util.List; + +public class BukkitMCCookingRecipe extends BukkitMCRecipe implements MCCookingRecipe { + + private final MCRecipeType type; + + public BukkitMCCookingRecipe(Recipe recipe, MCRecipeType type) { + super(recipe); + this.type = type; + } + + @Override + public String getKey() { + return getHandle().getKey().getKey(); + } + + @Override + public MCRecipeType getRecipeType() { + return type; + } + + @Override + public String getGroup() { + return getHandle().getGroup(); + } + + @Override + public void setGroup(String group) { + getHandle().setGroup(group); + } + + @Override + public MCItemStack getResult() { + return new BukkitMCItemStack(getHandle().getResult()); + } + + @Override + public CookingRecipe getHandle() { + return (CookingRecipe) super.getHandle(); + } + + @Override + public MCRecipeChoice getInput() { + RecipeChoice recipeChoice = getHandle().getInputChoice(); + if(recipeChoice instanceof RecipeChoice.MaterialChoice materialChoice) { + MCRecipeChoice.MaterialChoice choice = new MCRecipeChoice.MaterialChoice(); + for(Material material : materialChoice.getChoices()) { + choice.addMaterial(BukkitMCMaterial.valueOfConcrete(material)); + } + return choice; + } else if(recipeChoice instanceof RecipeChoice.ExactChoice exactChoice) { + MCRecipeChoice.ExactChoice choice = new MCRecipeChoice.ExactChoice(); + for(ItemStack itemStack : exactChoice.getChoices()) { + choice.addItem(new BukkitMCItemStack(itemStack)); + } + return choice; + } + throw new UnsupportedOperationException("Unsupported recipe choice"); + } + + @Override + public void setInput(MCItemStack input) { + getHandle().setInputChoice(new RecipeChoice.ExactChoice((ItemStack) input.getHandle())); + } + + @Override + public void setInput(MCMaterial mat) { + getHandle().setInput((Material) mat.getHandle()); + } + + @Override + public void setInput(MCRecipeChoice choice) { + if(choice instanceof MCRecipeChoice.ExactChoice) { + List itemChoice = new ArrayList<>(); + for(MCItemStack itemStack : ((ExactChoice) choice).getItems()) { + itemChoice.add((ItemStack) itemStack.getHandle()); + } + getHandle().setInputChoice(new RecipeChoice.ExactChoice(itemChoice)); + } else if(choice instanceof MCRecipeChoice.MaterialChoice) { + List materialChoice = new ArrayList<>(); + for(MCMaterial material : ((MaterialChoice) choice).getMaterials()) { + materialChoice.add((Material) material.getHandle()); + } + getHandle().setInputChoice(new RecipeChoice.MaterialChoice(materialChoice)); + } + } + + @Override + public int getCookingTime() { + return getHandle().getCookingTime(); + } + + @Override + public void setCookingTime(int ticks) { + getHandle().setCookingTime(ticks); + } + + @Override + public float getExperience() { + return getHandle().getExperience(); + } + + @Override + public void setExperience(float exp) { + getHandle().setExperience(exp); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCCooldownComponent.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCCooldownComponent.java new file mode 100644 index 0000000000..e028254e6d --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCCooldownComponent.java @@ -0,0 +1,63 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCCooldownComponent; +import com.laytonsmith.commandhelper.CommandHelperPlugin; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.meta.components.UseCooldownComponent; + +public class BukkitMCCooldownComponent implements MCCooldownComponent { + + private final UseCooldownComponent cooldownComponent; + + public BukkitMCCooldownComponent(UseCooldownComponent foodComponent) { + this.cooldownComponent = foodComponent; + } + + @Override + public float getSeconds() { + return cooldownComponent.getCooldownSeconds(); + } + + @Override + public void setSeconds(float seconds) { + cooldownComponent.setCooldownSeconds(seconds); + } + + @Override + public String getCooldownGroup() { + NamespacedKey group = cooldownComponent.getCooldownGroup(); + if(group == null) { + return null; + } + return group.toString(); + } + + @Override + public void setCooldownGroup(String cooldownGroup) { + if(cooldownGroup == null) { + cooldownComponent.setCooldownGroup(null); + } else { + cooldownComponent.setCooldownGroup(NamespacedKey.fromString(cooldownGroup, CommandHelperPlugin.self)); + } + } + + @Override + public Object getHandle() { + return cooldownComponent; + } + + @Override + public String toString() { + return cooldownComponent.toString(); + } + + @Override + public boolean equals(Object o) { + return o instanceof BukkitMCCooldownComponent && cooldownComponent.equals(((BukkitMCCooldownComponent) o).cooldownComponent); + } + + @Override + public int hashCode() { + return cooldownComponent.hashCode(); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCCraftingInventory.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCCraftingInventory.java index 4837afd0bf..5ce3fe2084 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCCraftingInventory.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCCraftingInventory.java @@ -9,6 +9,7 @@ public class BukkitMCCraftingInventory extends BukkitMCInventory implements MCCraftingInventory { CraftingInventory ci; + public BukkitMCCraftingInventory(CraftingInventory inventory) { super(inventory); ci = inventory; @@ -17,7 +18,7 @@ public BukkitMCCraftingInventory(CraftingInventory inventory) { @Override public MCItemStack[] getMatrix() { MCItemStack[] matrix = new MCItemStack[ci.getMatrix().length]; - for (int i=0; i getChargedProjectiles() { + List projectiles = new ArrayList<>(); + for(ItemStack item : cbm.getChargedProjectiles()) { + projectiles.add(new BukkitMCItemStack(item)); + } + return projectiles; + } + + @Override + public void setChargedProjectiles(List projectiles) { + List proj = new ArrayList<>(); + for(MCItemStack item : projectiles) { + if(item.getHandle() != null) { + proj.add((ItemStack) item.getHandle()); + } + } + cbm.setChargedProjectiles(proj); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCDoubleChest.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCDoubleChest.java index 9048d451b5..0d0b080aed 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCDoubleChest.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCDoubleChest.java @@ -1,77 +1,30 @@ - package com.laytonsmith.abstraction.bukkit; -import com.laytonsmith.abstraction.MCItemStack; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; +import com.laytonsmith.abstraction.MCDoubleChest; +import com.laytonsmith.abstraction.MCInventory; +import com.laytonsmith.abstraction.MCLocation; +import org.bukkit.block.DoubleChest; -/** - * - * - */ -public class BukkitMCDoubleChest extends BukkitMCInventory { - - Inventory left; - Inventory right; - public BukkitMCDoubleChest(Inventory left, Inventory right){ - super(left); - } +public class BukkitMCDoubleChest implements MCDoubleChest { - @Override - public int getSize() { - return left.getSize() + right.getSize(); - } + DoubleChest dc; - @Override - public MCItemStack getItem(int slot) { - ItemStack is; - if(slot < left.getSize()){ - is = left.getItem(slot); - } else { - is = right.getItem(slot - left.getSize()); - } - return new BukkitMCItemStack(is); + public BukkitMCDoubleChest(DoubleChest chest) { + this.dc = chest; } @Override - public void setItem(int slot, MCItemStack stack) { - ItemStack is = (ItemStack)((BukkitMCItemStack)stack).getHandle(); - if(slot < left.getSize()){ - left.setItem(slot, is); - } else { - right.setItem(slot - left.getSize(), is); - } - } - - @Override - public String toString() { - return left.toString() + ":" + right.toString(); + public MCInventory getInventory() { + return new BukkitMCInventory(this.dc.getInventory()); } - + @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final BukkitMCDoubleChest other = (BukkitMCDoubleChest) obj; - if (this.left != other.left && (this.left == null || !this.left.equals(other.left))) { - return false; - } - if (this.right != other.right && (this.right == null || !this.right.equals(other.right))) { - return false; - } - return true; + public MCLocation getLocation() { + return new BukkitMCLocation(this.dc.getLocation()); } @Override - public int hashCode() { - int hash = 7; - hash = 59 * hash + (this.left != null ? this.left.hashCode() : 0); - hash = 59 * hash + (this.right != null ? this.right.hashCode() : 0); - return hash; + public Object getHandle() { + return this.dc; } - } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCEnchantment.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCEnchantment.java deleted file mode 100644 index 08b36be4f4..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCEnchantment.java +++ /dev/null @@ -1,74 +0,0 @@ - - -package com.laytonsmith.abstraction.bukkit; - -import com.laytonsmith.abstraction.AbstractionObject; -import com.laytonsmith.abstraction.MCEnchantment; -import com.laytonsmith.abstraction.MCItemStack; -import org.bukkit.enchantments.Enchantment; - -/** - * - * - */ -public class BukkitMCEnchantment implements MCEnchantment{ - Enchantment e; - - public BukkitMCEnchantment(Enchantment e){ - if(e == null){ - throw new NullPointerException(); - } - this.e = e; - } - - public BukkitMCEnchantment(AbstractionObject a){ - if(a instanceof MCEnchantment){ - this.e = ((Enchantment)a.getHandle()); - } else { - throw new ClassCastException(); - } - } - - @Override - public Object getHandle(){ - return e; - } - - Enchantment __Enchantment() { - return e; - } - - public Enchantment asEnchantment() { - return e; - } - - @Override - public boolean canEnchantItem(MCItemStack is) { - return e.canEnchantItem(((BukkitMCItemStack)is).is); - } - - @Override - public int getMaxLevel() { - return e.getMaxLevel(); - } - - @Override - public String getName() { - return e.getName(); - } - - @Override - public String toString() { - return e.toString(); - } - - @Override - public boolean equals(Object obj) { - return (obj instanceof BukkitMCEnchantment?e.equals(((BukkitMCEnchantment)obj).e):false); - } - - @Override - public int hashCode() { - return e.hashCode(); - } -} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCEnchantmentOffer.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCEnchantmentOffer.java new file mode 100644 index 0000000000..c8eb1905c0 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCEnchantmentOffer.java @@ -0,0 +1,45 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCEnchantmentOffer; +import com.laytonsmith.abstraction.enums.MCEnchantment; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCEnchantment; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.enchantments.EnchantmentOffer; + +public class BukkitMCEnchantmentOffer implements MCEnchantmentOffer { + private final EnchantmentOffer handle; + + public BukkitMCEnchantmentOffer(EnchantmentOffer handle) { + this.handle = handle; + } + + @Override + public MCEnchantment getEnchantment() { + return BukkitMCEnchantment.valueOfConcrete(handle.getEnchantment()); + } + + @Override + public void setEnchantment(MCEnchantment enchant) { + handle.setEnchantment((Enchantment) enchant.getConcrete()); + } + + @Override + public int getEnchantmentLevel() { + return handle.getEnchantmentLevel(); + } + + @Override + public void setEnchantmentLevel(int level) { + handle.setEnchantmentLevel(level); + } + + @Override + public int getCost() { + return handle.getCost(); + } + + @Override + public void setCost(int cost) { + handle.setCost(cost); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCEnchantmentStorageMeta.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCEnchantmentStorageMeta.java index 8ce5dfd78d..15c242d7d6 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCEnchantmentStorageMeta.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCEnchantmentStorageMeta.java @@ -1,48 +1,45 @@ package com.laytonsmith.abstraction.bukkit; -import com.laytonsmith.abstraction.MCEnchantment; import com.laytonsmith.abstraction.MCEnchantmentStorageMeta; import java.util.HashMap; import java.util.Map; + +import com.laytonsmith.abstraction.enums.MCEnchantment; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCEnchantment; import org.bukkit.enchantments.Enchantment; import org.bukkit.inventory.meta.EnchantmentStorageMeta; -/** - * - * @author jb_aero - */ -public class BukkitMCEnchantmentStorageMeta extends BukkitMCItemMeta implements - MCEnchantmentStorageMeta { +public class BukkitMCEnchantmentStorageMeta extends BukkitMCItemMeta implements MCEnchantmentStorageMeta { EnchantmentStorageMeta es; + public BukkitMCEnchantmentStorageMeta(EnchantmentStorageMeta im) { super(im); this.es = im; } @Override - public boolean addStoredEnchant(MCEnchantment ench, int level, - boolean ignoreRestriction) { - return es.addStoredEnchant(((BukkitMCEnchantment) ench).__Enchantment(), level, ignoreRestriction); + public boolean addStoredEnchant(MCEnchantment ench, int level, boolean ignoreRestriction) { + return es.addStoredEnchant((Enchantment) ench.getConcrete(), level, ignoreRestriction); } @Override public int getStoredEnchantLevel(MCEnchantment ench) { - return es.getStoredEnchantLevel(((BukkitMCEnchantment) ench).__Enchantment()); + return es.getStoredEnchantLevel((Enchantment) ench.getConcrete()); } @Override public Map getStoredEnchants() { - Map ret = new HashMap(); - for (Map.Entry entry : es.getStoredEnchants().entrySet()) { - ret.put(new BukkitMCEnchantment(entry.getKey()), entry.getValue()); + Map ret = new HashMap<>(); + for(Map.Entry entry : es.getStoredEnchants().entrySet()) { + ret.put(BukkitMCEnchantment.valueOfConcrete(entry.getKey()), entry.getValue()); } return ret; } @Override public boolean hasStoredEnchant(MCEnchantment ench) { - return es.hasStoredEnchant(((BukkitMCEnchantment) ench).__Enchantment()); + return es.hasStoredEnchant((Enchantment) ench.getConcrete()); } @Override @@ -52,7 +49,7 @@ public boolean hasStoredEnchants() { @Override public boolean removeStoredEnchant(MCEnchantment ench) { - return es.removeStoredEnchant(((BukkitMCEnchantment) ench).__Enchantment()); + return es.removeStoredEnchant((Enchantment) ench.getConcrete()); } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCEnderCrystal.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCEnderCrystal.java deleted file mode 100644 index b86fffe484..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCEnderCrystal.java +++ /dev/null @@ -1,20 +0,0 @@ - -package com.laytonsmith.abstraction.bukkit; - -import com.laytonsmith.abstraction.MCEnderCrystal; -import org.bukkit.entity.EnderCrystal; - -/** - * - * - */ -public class BukkitMCEnderCrystal extends BukkitMCEntity implements MCEnderCrystal{ - - EnderCrystal ec; - public BukkitMCEnderCrystal(EnderCrystal ec){ - super(ec); - this.ec = ec; - } - - -} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCEntity.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCEntity.java deleted file mode 100644 index 77593c1719..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCEntity.java +++ /dev/null @@ -1,267 +0,0 @@ - - -package com.laytonsmith.abstraction.bukkit; - -import com.laytonsmith.abstraction.MCEntity; -import com.laytonsmith.abstraction.MCLivingEntity; -import com.laytonsmith.abstraction.MCLocation; -import com.laytonsmith.abstraction.MCServer; -import com.laytonsmith.abstraction.MCWorld; -import com.laytonsmith.abstraction.Velocity; -import com.laytonsmith.abstraction.bukkit.events.BukkitEntityEvents; -import com.laytonsmith.abstraction.enums.MCDamageCause; -import com.laytonsmith.abstraction.enums.MCEntityEffect; -import com.laytonsmith.abstraction.enums.MCEntityType; -import com.laytonsmith.abstraction.enums.MCTeleportCause; -import com.laytonsmith.abstraction.enums.bukkit.BukkitMCEntityType; -import com.laytonsmith.abstraction.events.MCEntityDamageEvent; -import com.laytonsmith.commandhelper.CommandHelperPlugin; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; -import org.bukkit.EntityEffect; -import org.bukkit.entity.Entity; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Tameable; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDamageEvent; -import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; -import org.bukkit.util.Vector; - -/** - * - * - */ -public class BukkitMCEntity extends BukkitMCMetadatable implements MCEntity { - - Entity e; - public BukkitMCEntity(Entity e) { - super(e); - this.e = e; - } - - @Override - public boolean eject() { - return e.eject(); - } - - @Override - public void fireEntityDamageEvent(MCDamageCause dc) { - EntityDamageEvent ede = new EntityDamageEvent(e, EntityDamageEvent.DamageCause.valueOf(dc.name()), 9001); - CommandHelperPlugin.self.getServer().getPluginManager().callEvent(ede); - } - - @Override - public int getEntityId(){ - return e.getEntityId(); - } - - @Override - public float getFallDistance() { - return e.getFallDistance(); - } - - @Override - public int getFireTicks() { - return e.getFireTicks(); - } - - @Override - public Entity getHandle(){ - return e; - } - - @Override - public MCEntityDamageEvent getLastDamageCause() { - EntityDamageEvent ldc = e.getLastDamageCause(); - if (ldc == null) { - return null; - } - if (ldc instanceof EntityDamageByEntityEvent) { - return new BukkitEntityEvents.BukkitMCEntityDamageByEntityEvent( - (EntityDamageByEntityEvent) ldc); - } - return new BukkitEntityEvents.BukkitMCEntityDamageEvent(ldc); - } - - public MCLivingEntity getLivingEntity() { - if(e instanceof LivingEntity){ - return new BukkitMCLivingEntity((LivingEntity)e); - } - return null; - } - - @Override - public MCLocation getLocation() { - if(e.getLocation() == null){ - return null; - } - return new BukkitMCLocation(e.getLocation()); - } - - @Override - public int getMaxFireTicks() { - return e.getMaxFireTicks(); - } - - @Override - public List getNearbyEntities(double x, double y, double z) { - List lst = e.getNearbyEntities(x, y, z); - List retn = new ArrayList(); - - for(Entity e : lst) { - retn.add(BukkitConvertor.BukkitGetCorrectEntity(e)); - } - - return retn; - } - - @Override - public MCEntity getPassenger() { - return BukkitConvertor.BukkitGetCorrectEntity(e.getPassenger()); - } - - @Override - public MCServer getServer() { - return new BukkitMCServer(e.getServer()); - } - - @Override - public int getTicksLived() { - return e.getTicksLived(); - } - - @Override - public MCEntityType getType() { - return BukkitMCEntityType.getConvertor().getAbstractedEnum(e.getType()); - } - - @Override - public UUID getUniqueId() { - return e.getUniqueId(); - } - - @Override - public MCEntity getVehicle() { - return new BukkitMCEntity(e); - } - - @Override - public Velocity getVelocity() { - Vector v = e.getVelocity(); - return new Velocity(v.length(), v.getX(), v.getY(), v.getZ()); - } - - @Override - public MCWorld getWorld() { - if (e == null || e.getWorld() == null) { - return null; - } - return new BukkitMCWorld(e.getWorld()); - } - - @Override - public boolean isDead() { - return e.isDead(); - } - - @Override - public boolean isEmpty() { - return e.isEmpty(); - } - - @Override - public boolean isInsideVehicle() { - return e.isInsideVehicle(); - } - - @Override - public boolean isOnGround() { - return e.isOnGround(); - } - - public boolean isLivingEntity() { - return e instanceof LivingEntity; - } - - public boolean isTameable() { - return e instanceof Tameable; - } - - @Override - public boolean leaveVehicle() { - return e.leaveVehicle(); - } - - @Override - public void playEffect(MCEntityEffect type) { - e.playEffect(EntityEffect.valueOf(type.name())); - } - - @Override - public void remove() { - e.remove(); - } - - @Override - public void setFallDistance(float distance) { - e.setFallDistance(distance); - } - - @Override - public void setFireTicks(int ticks) { - e.setFireTicks(ticks); - } - - @Override - public void setLastDamageCause(MCEntityDamageEvent event) { - e.setLastDamageCause((EntityDamageEvent)event._GetObject()); - } - - @Override - public boolean setPassenger(MCEntity passenger) { - return e.setPassenger((Entity)passenger.getHandle()); - } - - @Override - public void setTicksLived(int value) { - e.setTicksLived(value); - } - - @Override - public void setVelocity(Velocity velocity) { - Vector v = new Vector(velocity.x, velocity.y, velocity.z); - e.setVelocity(v); - } - - @Override - public boolean teleport(MCEntity destination) { - Entity ent = ((BukkitMCEntity)destination).getHandle(); - return e.teleport(ent.getLocation()); - } - - @Override - public boolean teleport(MCEntity destination, MCTeleportCause cause) { - return e.teleport(((BukkitMCEntity)destination).getHandle(), TeleportCause.valueOf(cause.name())); - } - - @Override - public boolean teleport(MCLocation location) { - return e.teleport(((BukkitMCLocation)location).asLocation()); - } - - @Override - public boolean teleport(MCLocation location, MCTeleportCause cause) { - return e.teleport(((BukkitMCLocation)location).asLocation(), TeleportCause.valueOf(cause.name())); - } - - /** - * This only works with craftbukkit - * @return - */ - @Override - public MCLocation asyncGetLocation() { - return new BukkitMCLocation(e.getLocation()); - } - -} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCEntityEquipment.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCEntityEquipment.java index 37719a2d27..1ee2318b0b 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCEntityEquipment.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCEntityEquipment.java @@ -4,32 +4,28 @@ import com.laytonsmith.abstraction.MCEntityEquipment; import com.laytonsmith.abstraction.MCItemStack; import com.laytonsmith.abstraction.enums.MCEquipmentSlot; +import com.laytonsmith.abstraction.enums.MCVersion; +import com.laytonsmith.core.Static; +import org.bukkit.inventory.EntityEquipment; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; + import java.util.EnumMap; import java.util.Map; -import org.bukkit.inventory.EntityEquipment; -/** - * - * @author jb_aero - */ public class BukkitMCEntityEquipment implements MCEntityEquipment { - private EntityEquipment ee; + private final EntityEquipment ee; public BukkitMCEntityEquipment(EntityEquipment equipment) { this.ee = equipment; } - + @Override public void clearEquipment() { ee.clear(); } - @Override - public int getSize() { - return MCEquipmentSlot.values().length; - } - @Override public MCEntity getHolder() { return BukkitConvertor.BukkitGetCorrectEntity(ee.getHolder()); @@ -37,24 +33,24 @@ public MCEntity getHolder() { @Override public Map getAllEquipment() { - Map slots = new EnumMap(MCEquipmentSlot.class); - for (MCEquipmentSlot key : MCEquipmentSlot.values()) { - switch (key) { - case WEAPON: - slots.put(key, getWeapon()); - break; - case HELMET: - slots.put(key, getHelmet()); - break; - case CHESTPLATE: - slots.put(key, getChestplate()); - break; - case LEGGINGS: - slots.put(key, getLeggings()); - break; - case BOOTS: - slots.put(key, getBoots()); - break; + Map slots = new EnumMap<>(MCEquipmentSlot.class); + slots.put(MCEquipmentSlot.WEAPON, getWeapon()); + slots.put(MCEquipmentSlot.OFF_HAND, getItemInOffHand()); + slots.put(MCEquipmentSlot.HELMET, getHelmet()); + slots.put(MCEquipmentSlot.CHESTPLATE, getChestplate()); + slots.put(MCEquipmentSlot.LEGGINGS, getLeggings()); + slots.put(MCEquipmentSlot.BOOTS, getBoots()); + BukkitMCServer server = (BukkitMCServer) Static.getServer(); + if(server.getMinecraftVersion().gte(MCVersion.MC1_20_6)) { + try { + slots.put(MCEquipmentSlot.BODY, new BukkitMCItemStack(ee.getItem(EquipmentSlot.BODY))); + } catch(IllegalArgumentException ignored) { + // API says it can throw an exception here, but it never seems to do so. + } + if(server.getMinecraftVersion().gte(MCVersion.MC1_21_5)) { + try { + slots.put(MCEquipmentSlot.SADDLE, new BukkitMCItemStack(ee.getItem(EquipmentSlot.SADDLE))); + } catch(IllegalArgumentException ignored) {} } } return slots; @@ -62,63 +58,79 @@ public Map getAllEquipment() { @Override public void setAllEquipment(Map slots) { - MCItemStack stack = null; - for (MCEquipmentSlot key : slots.keySet()) { - stack = slots.get(key); - switch (key) { - case WEAPON: - setWeapon(stack); - break; - case HELMET: - setHelmet(stack); - break; - case CHESTPLATE: - setChestplate(stack); - break; - case LEGGINGS: - setLeggings(stack); - break; - case BOOTS: - setBoots(stack); - break; - } - } - } - - @Override - public Map getAllDropChances() { - Map slots = new EnumMap(MCEquipmentSlot.class); - for (MCEquipmentSlot key : MCEquipmentSlot.values()) { - switch (key) { + MCItemStack stack; + for(Map.Entry entry : slots.entrySet()) { + stack = entry.getValue(); + switch(entry.getKey()) { case WEAPON: - slots.put(key, getWeaponDropChance()); + setWeapon(stack); + break; + case OFF_HAND: + setItemInOffHand(stack); break; case HELMET: - slots.put(key, getHelmetDropChance()); + setHelmet(stack); break; case CHESTPLATE: - slots.put(key, getChestplateDropChance()); + setChestplate(stack); break; case LEGGINGS: - slots.put(key, getLeggingsDropChance()); + setLeggings(stack); break; case BOOTS: - slots.put(key, getBootsDropChance()); + setBoots(stack); + break; + case BODY: + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_20_6)) { + try { + ee.setItem(EquipmentSlot.BODY, (ItemStack) stack.getHandle()); + } catch(IllegalArgumentException ignored) { + // API says it can throw an exception here, but it never seems to do so. + } + } + break; + case SADDLE: + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_21_5)) { + try { + ee.setItem(EquipmentSlot.SADDLE, (ItemStack) stack.getHandle()); + } catch(IllegalArgumentException ignored) {} + } break; } } + } + + @Override + public Map getAllDropChances() { + Map slots = new EnumMap<>(MCEquipmentSlot.class); + slots.put(MCEquipmentSlot.WEAPON, getWeaponDropChance()); + slots.put(MCEquipmentSlot.OFF_HAND, getOffHandDropChance()); + slots.put(MCEquipmentSlot.HELMET, getHelmetDropChance()); + slots.put(MCEquipmentSlot.CHESTPLATE, getChestplateDropChance()); + slots.put(MCEquipmentSlot.LEGGINGS, getLeggingsDropChance()); + slots.put(MCEquipmentSlot.BOOTS, getBootsDropChance()); + BukkitMCServer server = (BukkitMCServer) Static.getServer(); + if(server.getMinecraftVersion().gte(MCVersion.MC1_20_6)) { + slots.put(MCEquipmentSlot.BODY, ee.getDropChance(EquipmentSlot.BODY)); + if(server.getMinecraftVersion().gte(MCVersion.MC1_21_5)) { + slots.put(MCEquipmentSlot.SADDLE, ee.getDropChance(EquipmentSlot.SADDLE)); + } + } return slots; } - + @Override public void setAllDropChances(Map slots) { float chance; - for (MCEquipmentSlot key : slots.keySet()) { - chance = slots.get(key); - switch (key) { + for(Map.Entry entry : slots.entrySet()) { + chance = entry.getValue(); + switch(entry.getKey()) { case WEAPON: setWeaponDropChance(chance); break; + case OFF_HAND: + setOffHandDropChance(chance); + break; case HELMET: setHelmetDropChance(chance); break; @@ -131,16 +143,28 @@ public void setAllDropChances(Map slots) { case BOOTS: setBootsDropChance(chance); break; + case BODY: + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_20_6)) { + ee.setDropChance(EquipmentSlot.BODY, chance); + } + break; + case SADDLE: + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_21_5)) { + ee.setDropChance(EquipmentSlot.SADDLE, chance); + } + break; } } } - // For the purposes of faking a normal inventory, we most likely will not be accessing - // anything below this line, but they are here for flexibility and completion - @Override public MCItemStack getWeapon() { - return new BukkitMCItemStack(ee.getItemInHand()); + return new BukkitMCItemStack(ee.getItemInMainHand()); + } + + @Override + public MCItemStack getItemInOffHand() { + return new BukkitMCItemStack(ee.getItemInOffHand()); } @Override @@ -165,7 +189,12 @@ public MCItemStack getBoots() { @Override public void setWeapon(MCItemStack stack) { - ee.setItemInHand(((BukkitMCItemStack) stack).asItemStack()); + ee.setItemInMainHand(((BukkitMCItemStack) stack).asItemStack()); + } + + @Override + public void setItemInOffHand(MCItemStack stack) { + ee.setItemInOffHand(((BukkitMCItemStack) stack).asItemStack()); } @Override @@ -190,7 +219,12 @@ public void setBoots(MCItemStack stack) { @Override public float getWeaponDropChance() { - return ee.getItemInHandDropChance(); + return ee.getItemInMainHandDropChance(); + } + + @Override + public float getOffHandDropChance() { + return ee.getItemInOffHandDropChance(); } @Override @@ -215,7 +249,12 @@ public float getBootsDropChance() { @Override public void setWeaponDropChance(float chance) { - ee.setItemInHandDropChance(chance); + ee.setItemInMainHandDropChance(chance); + } + + @Override + public void setOffHandDropChance(float chance) { + ee.setItemInOffHandDropChance(chance); } @Override diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCEquippableComponent.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCEquippableComponent.java new file mode 100644 index 0000000000..3f37ed0efa --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCEquippableComponent.java @@ -0,0 +1,183 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCEquippableComponent; +import com.laytonsmith.abstraction.enums.MCEntityType; +import com.laytonsmith.abstraction.enums.MCEquipmentSlot; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCEntityType; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCEquipmentSlot; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; +import org.bukkit.Sound; +import org.bukkit.entity.EntityType; +import org.bukkit.inventory.meta.components.EquippableComponent; + +import java.util.ArrayList; +import java.util.Collection; + +public class BukkitMCEquippableComponent implements MCEquippableComponent { + + private final EquippableComponent equippableComponent; + + public BukkitMCEquippableComponent(EquippableComponent foodComponent) { + this.equippableComponent = foodComponent; + } + + @Override + public MCEquipmentSlot getSlot() { + return BukkitMCEquipmentSlot.getConvertor().getAbstractedEnum(this.equippableComponent.getSlot()); + } + + @Override + public void setSlot(MCEquipmentSlot slot) { + this.equippableComponent.setSlot(BukkitMCEquipmentSlot.getConvertor().getConcreteEnum(slot)); + } + + @Override + public Collection getAllowedEntities() { + Collection allowedEntities = this.equippableComponent.getAllowedEntities(); + if(allowedEntities == null) { + return null; + } + Collection ret = new ArrayList<>(); + for(EntityType type : allowedEntities) { + ret.add(BukkitMCEntityType.valueOfConcrete(type)); + } + return ret; + } + + @Override + public void setAllowedEntities(Collection types) { + if(types == null) { + this.equippableComponent.setAllowedEntities((EntityType) null); + } else { + Collection entityTypes = new ArrayList<>(); + for(MCEntityType type : types) { + entityTypes.add((EntityType) type.getConcrete()); + } + this.equippableComponent.setAllowedEntities(entityTypes); + } + } + + @Override + public String getCameraOverlay() { + if(this.equippableComponent.getCameraOverlay() == null) { + return null; + } + return this.equippableComponent.getCameraOverlay().toString(); + } + + @Override + public void setCameraOverlay(String overlay) { + if(overlay == null) { + this.equippableComponent.setCameraOverlay(null); + } else { + this.equippableComponent.setCameraOverlay(NamespacedKey.fromString(overlay)); + } + } + + @Override + public String getAssetId() { + if(this.equippableComponent.getModel() == null) { + return null; + } + return this.equippableComponent.getModel().toString(); + } + + @Override + public void setAssetId(String assetId) { + if(assetId == null) { + this.equippableComponent.setModel(null); + } else { + this.equippableComponent.setModel(NamespacedKey.fromString(assetId)); + } + } + + @Override + public String getEquipSound() { + Sound sound = this.equippableComponent.getEquipSound(); + // API may say this cannot be null, but it can be at runtime. + if(sound == null) { + return null; + } + try { + return sound.getKey().toString(); + } catch(NullPointerException | IllegalStateException ex) { + // Probably a new sound event definition instead of a key, so we have no choice but to return null for now. + // The ISException is probably a server bug. NPE catch is just in case getKey() nullability is changed. + // Registry.SOUNDS.getKey(sound) is cleaner, as it can return null, but it's Paper only. + return null; + } + } + + @Override + public void setEquipSound(String sound) { + if(sound == null) { + this.equippableComponent.setEquipSound(null); + } else { + NamespacedKey key = NamespacedKey.fromString(sound); + if(key != null) { + this.equippableComponent.setEquipSound(Registry.SOUNDS.get(key)); + } + } + } + + @Override + public boolean isDispensable() { + return this.equippableComponent.isDispensable(); + } + + @Override + public void setDispensable(boolean dispensable) { + this.equippableComponent.setDispensable(dispensable); + } + + @Override + public boolean isEquipOnInteract() { + return this.equippableComponent.isEquipOnInteract(); + } + + @Override + public void setEquipOnInteract(boolean equipOnInteract) { + this.equippableComponent.setEquipOnInteract(equipOnInteract); + } + + @Override + public boolean isSwappable() { + return this.equippableComponent.isSwappable(); + } + + @Override + public void setSwappable(boolean swappable) { + this.equippableComponent.setSwappable(swappable); + } + + @Override + public boolean isDamageOnHurt() { + return this.equippableComponent.isDamageOnHurt(); + } + + @Override + public void setDamageOnHurt(boolean damagedOnHurt) { + this.equippableComponent.setDamageOnHurt(damagedOnHurt); + } + + @Override + public Object getHandle() { + return equippableComponent; + } + + @Override + public String toString() { + return equippableComponent.toString(); + } + + @Override + public boolean equals(Object o) { + return o instanceof BukkitMCEquippableComponent && equippableComponent.equals(((BukkitMCEquippableComponent) o).equippableComponent); + } + + @Override + public int hashCode() { + return equippableComponent.hashCode(); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCExperienceOrb.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCExperienceOrb.java deleted file mode 100644 index f664278850..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCExperienceOrb.java +++ /dev/null @@ -1,29 +0,0 @@ - -package com.laytonsmith.abstraction.bukkit; - -import com.laytonsmith.abstraction.MCExperienceOrb; -import org.bukkit.entity.ExperienceOrb; - -/** - * - * @author Jim - */ -public class BukkitMCExperienceOrb extends BukkitMCEntity implements MCExperienceOrb { - - ExperienceOrb eo; - - public BukkitMCExperienceOrb(ExperienceOrb eo){ - super(eo); - this.eo = eo; - } - - @Override - public int getExperience(){ - return eo.getExperience(); - } - - @Override - public void setExperience(int amount){ - eo.setExperience(amount); - } -} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCFireball.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCFireball.java deleted file mode 100644 index f5b8f4074f..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCFireball.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.laytonsmith.abstraction.bukkit; - -import com.laytonsmith.abstraction.MCFireball; -import com.laytonsmith.abstraction.Velocity; -import org.bukkit.entity.Fireball; -import org.bukkit.util.Vector; - -/** - * - * @author jb_aero - */ -public class BukkitMCFireball extends BukkitMCProjectile implements MCFireball { - - Fireball f; - public BukkitMCFireball(Fireball be) { - super(be); - f = be; - } - - @Override - public Velocity getDirection() { - return new Velocity(1, f.getDirection().getX(), f.getDirection().getY(), f.getDirection().getZ()); - } - - @Override - public void setDirection(Velocity vector) { - f.setDirection(new Vector(vector.x, vector.y, vector.z)); - } - -} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCFireworkBuilder.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCFireworkBuilder.java index 269f059ca1..ec43fa0c18 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCFireworkBuilder.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCFireworkBuilder.java @@ -1,35 +1,18 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ package com.laytonsmith.abstraction.bukkit; import com.laytonsmith.abstraction.MCColor; import com.laytonsmith.abstraction.MCFireworkBuilder; -import com.laytonsmith.abstraction.MCFireworkMeta; -import com.laytonsmith.abstraction.MCLocation; +import com.laytonsmith.abstraction.MCFireworkEffect; import com.laytonsmith.abstraction.enums.MCFireworkType; import com.laytonsmith.abstraction.enums.bukkit.BukkitMCFireworkType; -import java.util.List; -import java.util.Map; import org.bukkit.FireworkEffect; -import org.bukkit.Location; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.Firework; -import org.bukkit.inventory.meta.FireworkEffectMeta; -import org.bukkit.inventory.meta.FireworkMeta; -/** - * - * - */ public class BukkitMCFireworkBuilder implements MCFireworkBuilder { - private FireworkEffect.Builder builder; - private int strength; - public BukkitMCFireworkBuilder(){ + + private final FireworkEffect.Builder builder; + + public BukkitMCFireworkBuilder() { builder = FireworkEffect.builder(); - strength = 0; } @Override @@ -57,37 +40,14 @@ public MCFireworkBuilder addFadeColor(MCColor color) { } @Override - public MCFireworkBuilder setType(MCFireworkType type){ + public MCFireworkBuilder setType(MCFireworkType type) { builder.with(BukkitMCFireworkType.getConvertor().getConcreteEnum(type)); return this; } @Override - public MCFireworkBuilder setStrength(int i) { - strength = i; - return this; - } - - @Override - public int launch(MCLocation l) { - FireworkEffect fe = builder.build(); - Location ll = ((BukkitMCLocation)l).asLocation(); - Firework fw = (Firework)ll.getWorld().spawnEntity(ll, EntityType.FIREWORK); - FireworkMeta fwmeta = fw.getFireworkMeta(); - fwmeta.addEffect(fe); - fwmeta.setPower(strength); - fw.setFireworkMeta(fwmeta); - return fw.getEntityId(); + public MCFireworkEffect build() { + return new BukkitMCFireworkEffect(builder.build()); } - @Override - public void createFireworkMeta(MCFireworkMeta meta) { - FireworkMeta m = ((BukkitMCFireworkMeta)meta).fm; - FireworkEffect fem = builder.build(); - m.clearEffects(); - m.addEffect(fem); - m.setPower(strength); - } - - } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCFireworkEffect.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCFireworkEffect.java new file mode 100644 index 0000000000..467eed9c8b --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCFireworkEffect.java @@ -0,0 +1,75 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCColor; +import com.laytonsmith.abstraction.MCFireworkEffect; +import com.laytonsmith.abstraction.enums.MCFireworkType; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCFireworkType; +import org.bukkit.Color; +import org.bukkit.FireworkEffect; + +import java.util.ArrayList; +import java.util.List; + +public class BukkitMCFireworkEffect implements MCFireworkEffect { + + FireworkEffect fe; + + public BukkitMCFireworkEffect(FireworkEffect fe) { + this.fe = fe; + } + + @Override + public boolean hasFlicker() { + return fe.hasFlicker(); + } + + @Override + public boolean hasTrail() { + return fe.hasTrail(); + } + + @Override + public List getColors() { + List colors = fe.getColors(); + List c = new ArrayList<>(); + for(Color cc : colors) { + c.add(BukkitMCColor.GetMCColor(cc)); + } + return c; + } + + @Override + public List getFadeColors() { + List colors = fe.getFadeColors(); + List c = new ArrayList<>(); + for(Color cc : colors) { + c.add(BukkitMCColor.GetMCColor(cc)); + } + return c; + } + + @Override + public MCFireworkType getType() { + return BukkitMCFireworkType.getConvertor().getAbstractedEnum(fe.getType()); + } + + @Override + public FireworkEffect getHandle() { + return fe; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof BukkitMCFireworkEffect && fe.equals(obj); + } + + @Override + public int hashCode() { + return fe.hashCode(); + } + + @Override + public String toString() { + return fe.toString(); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCFireworkEffectMeta.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCFireworkEffectMeta.java new file mode 100644 index 0000000000..e88de97f00 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCFireworkEffectMeta.java @@ -0,0 +1,36 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCFireworkEffect; +import com.laytonsmith.abstraction.MCFireworkEffectMeta; +import org.bukkit.FireworkEffect; +import org.bukkit.inventory.meta.FireworkEffectMeta; + +public class BukkitMCFireworkEffectMeta extends BukkitMCItemMeta implements MCFireworkEffectMeta { + + FireworkEffectMeta fem; + + public BukkitMCFireworkEffectMeta(FireworkEffectMeta im) { + super(im); + fem = im; + } + + @Override + public boolean hasEffect() { + return fem.hasEffect(); + } + + @Override + public MCFireworkEffect getEffect() { + FireworkEffect effect = fem.getEffect(); + if(effect == null) { + return null; + } + return new BukkitMCFireworkEffect(fem.getEffect()); + } + + @Override + public void setEffect(MCFireworkEffect effect) { + fem.setEffect((FireworkEffect) effect.getHandle()); + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCFireworkMeta.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCFireworkMeta.java index b0cc0e9308..449e377965 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCFireworkMeta.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCFireworkMeta.java @@ -1,18 +1,17 @@ package com.laytonsmith.abstraction.bukkit; -import com.laytonsmith.abstraction.MCColor; +import com.laytonsmith.abstraction.MCFireworkEffect; import com.laytonsmith.abstraction.MCFireworkMeta; -import com.laytonsmith.abstraction.enums.MCFireworkType; -import com.laytonsmith.abstraction.enums.bukkit.BukkitMCFireworkType; -import java.util.ArrayList; -import java.util.List; -import org.bukkit.Color; import org.bukkit.FireworkEffect; import org.bukkit.inventory.meta.FireworkMeta; +import java.util.ArrayList; +import java.util.List; + public class BukkitMCFireworkMeta extends BukkitMCItemMeta implements MCFireworkMeta { FireworkMeta fm; + public BukkitMCFireworkMeta(FireworkMeta im) { super(im); fm = im; @@ -34,58 +33,22 @@ public void setStrength(int strength) { } @Override - public boolean getFlicker() { - for(FireworkEffect effect : fm.getEffects()){ - if(effect.hasFlicker()){ - return true; - } - } - return false; - } - - @Override - public boolean getTrail() { - for(FireworkEffect effect : fm.getEffects()){ - if(effect.hasTrail()){ - return true; - } - } - return false; - } - - @Override - public List getColors() { - for(FireworkEffect effect : fm.getEffects()){ - List colors = effect.getColors(); - List c = new ArrayList<>(); - for(Color cc : colors){ - c.add(BukkitMCColor.GetMCColor(cc)); - } - return c; + public List getEffects() { + List effects = new ArrayList<>(); + for(FireworkEffect effect : fm.getEffects()) { + effects.add(new BukkitMCFireworkEffect(effect)); } - return new ArrayList<>(); + return effects; } @Override - public List getFadeColors() { - for(FireworkEffect effect : fm.getEffects()){ - List colors = effect.getFadeColors(); - List c = new ArrayList<>(); - for(Color cc : colors){ - c.add(BukkitMCColor.GetMCColor(cc)); - } - return c; - } - return new ArrayList<>(); + public void addEffect(MCFireworkEffect effect) { + fm.addEffect((FireworkEffect) effect.getHandle()); } @Override - public MCFireworkType getType() { - for(FireworkEffect effect : fm.getEffects()){ - return BukkitMCFireworkType.getConvertor().getAbstractedEnum(effect.getType()); - } - // This is the default type - return null; + public void clearEffects() { + fm.clearEffects(); } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCFoodComponent.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCFoodComponent.java new file mode 100644 index 0000000000..8f5ce0f8cc --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCFoodComponent.java @@ -0,0 +1,63 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCFoodComponent; +import org.bukkit.inventory.meta.components.FoodComponent; + +public class BukkitMCFoodComponent implements MCFoodComponent { + + private final FoodComponent foodComponent; + + public BukkitMCFoodComponent(FoodComponent foodComponent) { + this.foodComponent = foodComponent; + } + + @Override + public int getNutrition() { + return foodComponent.getNutrition(); + } + + @Override + public void setNutrition(int nutrition) { + foodComponent.setNutrition(nutrition); + } + + @Override + public float getSaturation() { + return foodComponent.getSaturation(); + } + + @Override + public void setSaturation(float saturation) { + foodComponent.setSaturation(saturation); + } + + @Override + public boolean getCanAlwaysEat() { + return foodComponent.canAlwaysEat(); + } + + @Override + public void setCanAlwaysEat(boolean canAlwaysEat) { + foodComponent.setCanAlwaysEat(canAlwaysEat); + } + + @Override + public Object getHandle() { + return foodComponent; + } + + @Override + public String toString() { + return foodComponent.toString(); + } + + @Override + public boolean equals(Object o) { + return o instanceof BukkitMCFoodComponent && foodComponent.equals(((BukkitMCFoodComponent) o).foodComponent); + } + + @Override + public int hashCode() { + return foodComponent.hashCode(); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCFurnaceInventory.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCFurnaceInventory.java new file mode 100644 index 0000000000..59d8dba1de --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCFurnaceInventory.java @@ -0,0 +1,54 @@ +package com.laytonsmith.abstraction.bukkit; + +import org.bukkit.inventory.FurnaceInventory; +import org.bukkit.inventory.ItemStack; + +import com.laytonsmith.abstraction.MCFurnaceInventory; +import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.blocks.MCFurnace; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCFurnace; + +public class BukkitMCFurnaceInventory extends BukkitMCInventory implements MCFurnaceInventory { + + private FurnaceInventory inv; + + public BukkitMCFurnaceInventory(FurnaceInventory inv) { + super(inv); + this.inv = inv; + } + + @Override + public MCItemStack getResult() { + return new BukkitMCItemStack(this.inv.getResult()); + } + + @Override + public MCItemStack getFuel() { + return new BukkitMCItemStack(this.inv.getFuel()); + } + + @Override + public MCItemStack getSmelting() { + return new BukkitMCItemStack(this.inv.getSmelting()); + } + + @Override + public void setFuel(MCItemStack stack) { + this.inv.setFuel((ItemStack) stack.getHandle()); + } + + @Override + public void setResult(MCItemStack stack) { + this.inv.setResult((ItemStack) stack.getHandle()); + } + + @Override + public void setSmelting(MCItemStack stack) { + this.inv.setSmelting((ItemStack) stack.getHandle()); + } + + @Override + public MCFurnace getHolder() { + return new BukkitMCFurnace(this.inv.getHolder()); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCFurnaceRecipe.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCFurnaceRecipe.java deleted file mode 100644 index 9b5e96e32e..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCFurnaceRecipe.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.laytonsmith.abstraction.bukkit; - -import com.laytonsmith.abstraction.MCFurnaceRecipe; -import com.laytonsmith.abstraction.MCItemStack; -import com.laytonsmith.abstraction.enums.MCRecipeType; -import org.bukkit.Material; -import org.bukkit.inventory.FurnaceRecipe; - -/** - * - * @author jb_aero - */ -public class BukkitMCFurnaceRecipe extends BukkitMCRecipe implements MCFurnaceRecipe { - - FurnaceRecipe fr; - public BukkitMCFurnaceRecipe(FurnaceRecipe recipe) { - super(recipe); - fr = recipe; - } - - public BukkitMCFurnaceRecipe(MCItemStack result) { // Why? because no one has ever thought to put his in a furnace - this(new FurnaceRecipe(((BukkitMCItemStack) result).asItemStack(), Material.PISTON_MOVING_PIECE)); - } - - @Override - public MCRecipeType getRecipeType() { - return MCRecipeType.FURNACE; - } - - @Override - public MCItemStack getResult() { - return new BukkitMCItemStack(fr.getResult()); - } - - @Override - public Object getHandle() { - return fr; - } - - @Override - public MCItemStack getInput() { - return new BukkitMCItemStack(fr.getInput()); - } - - @Override - public MCFurnaceRecipe setInput(MCItemStack input) { - int type = input.getTypeId(); - int data = 0; - if (type < 256) { - data = input.getData() != null ? input.getData().getData() : 0; - } else { - data = input.getDurability(); - } - return this.setInput(type, data); - } - - @Override - public MCFurnaceRecipe setInput(int type, int data) { - fr.setInput(Material.getMaterial(type), data); - return this; - } -} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCGrindstoneInventory.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCGrindstoneInventory.java new file mode 100644 index 0000000000..e81be95857 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCGrindstoneInventory.java @@ -0,0 +1,45 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCGrindstoneInventory; +import com.laytonsmith.abstraction.MCItemStack; +import org.bukkit.inventory.GrindstoneInventory; + +public class BukkitMCGrindstoneInventory extends BukkitMCInventory implements MCGrindstoneInventory { + + GrindstoneInventory gi; + + public BukkitMCGrindstoneInventory(GrindstoneInventory inventory) { + super(inventory); + gi = inventory; + } + + @Override + public MCItemStack getUpperItem() { + return new BukkitMCItemStack(gi.getItem(0)); + } + + @Override + public MCItemStack getLowerItem() { + return new BukkitMCItemStack(gi.getItem(1)); + } + + @Override + public MCItemStack getResult() { + return new BukkitMCItemStack(gi.getItem(2)); + } + + @Override + public void setUpperItem(MCItemStack stack) { + gi.setItem(0, ((BukkitMCItemStack) stack).asItemStack()); + } + + @Override + public void setLowerItem(MCItemStack stack) { + gi.setItem(1, ((BukkitMCItemStack) stack).asItemStack()); + } + + @Override + public void setResult(MCItemStack stack) { + gi.setItem(2, ((BukkitMCItemStack) stack).asItemStack()); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCHumanEntity.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCHumanEntity.java deleted file mode 100644 index d092d2fe73..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCHumanEntity.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.laytonsmith.abstraction.bukkit; - -import com.laytonsmith.abstraction.MCHumanEntity; -import com.laytonsmith.abstraction.MCInventory; -import com.laytonsmith.abstraction.MCInventoryView; -import com.laytonsmith.abstraction.MCItemStack; -import com.laytonsmith.abstraction.MCLocation; -import com.laytonsmith.abstraction.enums.MCGameMode; -import org.bukkit.Location; -import org.bukkit.entity.HumanEntity; -import org.bukkit.inventory.Inventory; - -/** - * - * - */ -public class BukkitMCHumanEntity extends BukkitMCLivingEntity implements MCHumanEntity { - - HumanEntity he; - - public BukkitMCHumanEntity(HumanEntity humanEntity) { - super(humanEntity); - he = humanEntity; - } - - public HumanEntity asHumanEntity() { - return he; - } - - @Override - public String getName() { - return he.getName(); - } - - @Override - public void closeInventory() { - he.closeInventory(); - } - - @Override - public MCGameMode getGameMode() { - return MCGameMode.valueOf(he.getGameMode().name()); - } - - @Override - public MCItemStack getItemInHand() { - if (he == null || he.getItemInHand() == null) { - return null; - } - - return new BukkitMCItemStack(he.getItemInHand()); - } - - @Override - public MCItemStack getItemOnCursor() { - return new BukkitMCItemStack(he.getItemOnCursor()); - } - - @Override - public int getSleepTicks() { - return he.getSleepTicks(); - } - - @Override - public boolean isBlocking() { - return he.isBlocking(); - } - - @Override - public boolean isSleeping() { - return he.isSleeping(); - } - - @Override - public void setGameMode(MCGameMode mode) { - he.setGameMode(org.bukkit.GameMode.valueOf(mode.name())); - } - - @Override - public void setItemInHand(MCItemStack item) { - he.setItemInHand(((BukkitMCItemStack)item).asItemStack()); - } - - @Override - public void setItemOnCursor(MCItemStack item) { - he.setItemOnCursor(((BukkitMCItemStack)item).asItemStack()); - } - - @Override - public MCInventoryView openInventory(MCInventory inventory) { - return new BukkitMCInventoryView(he.openInventory((Inventory)inventory.getHandle())); - } - - @Override - public MCInventoryView getOpenInventory() { - return new BukkitMCInventoryView(he.getOpenInventory()); - } - - @Override - public MCInventory getInventory() { - return new BukkitMCInventory(he.getInventory()); - } - - @Override - public MCInventory getEnderChest() { - return new BukkitMCInventory(he.getEnderChest()); - } - - @Override - public MCInventoryView openWorkbench(MCLocation loc, boolean force) { - return new BukkitMCInventoryView(he.openWorkbench((Location)loc.getHandle(), force)); - } - - @Override - public MCInventoryView openEnchanting(MCLocation loc, boolean force) { - return new BukkitMCInventoryView(he.openEnchanting((Location)loc.getHandle(), force)); - } -} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCInventory.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCInventory.java index 5fe19d2034..3be5912aff 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCInventory.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCInventory.java @@ -5,30 +5,36 @@ import com.laytonsmith.abstraction.MCInventory; import com.laytonsmith.abstraction.MCInventoryHolder; import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCHumanEntity; import com.laytonsmith.abstraction.enums.MCInventoryType; -import com.laytonsmith.core.CHLog; -import com.laytonsmith.core.CHLog.Tags; +import com.laytonsmith.core.MSLog; +import com.laytonsmith.core.MSLog.Tags; import com.laytonsmith.core.LogLevel; import com.laytonsmith.core.constructs.Target; -import com.laytonsmith.core.functions.Exceptions; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import com.laytonsmith.core.exceptions.CRE.CRERangeException; +import com.laytonsmith.core.functions.InventoryManagement; +import org.bukkit.Nameable; +import org.bukkit.block.BlockState; +import org.bukkit.block.DoubleChest; +import org.bukkit.entity.Entity; import org.bukkit.entity.HumanEntity; import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; -/** - * - * - */ +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + public class BukkitMCInventory implements MCInventory { - private Inventory i; - public BukkitMCInventory(Inventory inventory) { - this.i = inventory; - } + + private final Inventory i; + + public BukkitMCInventory(Inventory inventory) { + this.i = inventory; + } @Override public MCInventoryType getType() { @@ -45,42 +51,39 @@ public MCItemStack getItem(int slot) { try { return new BukkitMCItemStack(i.getItem(slot)); } catch (ArrayIndexOutOfBoundsException aioobe) { - if (slot > 0 && slot < getSize()) { - CHLog.GetLogger().Log(Tags.RUNTIME, LogLevel.WARNING, "The API claims that a particular slot is" + if(slot > 0 && slot < getSize()) { + MSLog.GetLogger().Log(Tags.RUNTIME, LogLevel.WARNING, "The API claims that a particular slot is" + " accessible, however the server implementation does not give access." + " This is the fault of the server and can't be helped by " + Implementation.GetServerType().getBranding() + ".", Target.UNKNOWN); } else { - throw new Exceptions.RangeException("No slot " + slot + " exists in the given inventory", Target.UNKNOWN); + throw new CRERangeException("No slot " + slot + " exists in the given inventory", Target.UNKNOWN); } return null; } - } + } @Override public void setItem(int slot, MCItemStack stack) { try { - this.i.setItem(slot, stack == null ? null : ((BukkitMCItemStack) stack).is); + this.i.setItem(slot, stack == null ? null : (ItemStack) stack.getHandle()); } catch (ArrayIndexOutOfBoundsException aioobe) { - if (slot > 0 && slot < getSize()) { - CHLog.GetLogger().Log(Tags.RUNTIME, LogLevel.WARNING, "The API claims that a particular slot is" + if(slot > 0 && slot < getSize()) { + MSLog.GetLogger().Log(Tags.RUNTIME, LogLevel.WARNING, "The API claims that a particular slot is" + " accessible, however the server implementation does not give access." + " This is the fault of the server and can't be helped by " + Implementation.GetServerType().getBranding() + ".", Target.UNKNOWN); } else { - throw new Exceptions.RangeException("No slot " + slot + " exists in the given inventory", Target.UNKNOWN); + throw new CRERangeException("No slot " + slot + " exists in the given inventory", Target.UNKNOWN); } } - if(this.i.getHolder() instanceof Player){ - ((Player)this.i.getHolder()).updateInventory(); - } - } - + } + @Override public void clear() { i.clear(); } - + @Override public void clear(int index) { i.clear(index); @@ -90,7 +93,7 @@ public void clear(int index) { public Object getHandle() { return i; } - + @Override public String toString() { return i.toString(); @@ -98,20 +101,19 @@ public String toString() { @Override public boolean equals(Object obj) { - return (obj instanceof BukkitMCInventory?i.equals(((BukkitMCInventory)obj).i):false); + return obj instanceof BukkitMCInventory && i.equals(((BukkitMCInventory) obj).i); } @Override public int hashCode() { return i.hashCode(); } - + @Override public Map addItem(MCItemStack stack) { - Map h = i.addItem(stack==null?null:((BukkitMCItemStack)stack).is); - Map m = new HashMap(); - - for (Map.Entry entry : h.entrySet()) { + Map h = i.addItem((ItemStack) stack.getHandle()); + Map m = new HashMap<>(); + for(Map.Entry entry : h.entrySet()) { Integer key = entry.getKey(); ItemStack value = entry.getValue(); m.put(key, new BukkitMCItemStack(value)); @@ -121,22 +123,52 @@ public Map addItem(MCItemStack stack) { @Override public List getViewers() { - List retn = new ArrayList(); - - for (HumanEntity human: i.getViewers()) { + List retn = new ArrayList<>(); + for(HumanEntity human : i.getViewers()) { retn.add(new BukkitMCHumanEntity((human))); } - return retn; } - + + @Override + public void updateViewers() { + for(HumanEntity human : i.getViewers()) { + if(human instanceof Player) { + ((Player) human).updateInventory(); + } + } + } + @Override public MCInventoryHolder getHolder() { - return new BukkitMCInventoryHolder(i.getHolder()); + InventoryHolder ih = i.getHolder(); + if(ih instanceof BlockState) { + return (MCInventoryHolder) BukkitConvertor.BukkitGetCorrectBlockState((BlockState) ih); + } else if(ih instanceof Entity) { + return (MCInventoryHolder) BukkitConvertor.BukkitGetCorrectEntity((Entity) ih); + } else if(ih instanceof BukkitMCVirtualInventoryHolder.VirtualHolder) { + return new BukkitMCVirtualInventoryHolder(ih); + } else if(ih instanceof DoubleChest) { + return new BukkitMCDoubleChest((DoubleChest) ih); + } else if(ih == null) { + for(Map.Entry entry : InventoryManagement.VIRTUAL_INVENTORIES.entrySet()) { + if(entry.getValue().equals(this)) { + return new BukkitMCVirtualInventoryHolder(entry.getKey(), getTitle()); + } + } + } + return new BukkitMCInventoryHolder(ih); } - + @Override public String getTitle() { - return i.getTitle(); + InventoryHolder h = i.getHolder(); + if(h instanceof Nameable) { + return ((Nameable) h).getCustomName(); + } + if(h instanceof BukkitMCVirtualInventoryHolder.VirtualHolder) { + return ((BukkitMCVirtualInventoryHolder.VirtualHolder) h).getTitle(); + } + return null; } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCInventoryHolder.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCInventoryHolder.java index 0dbf92ac1d..7e724335e7 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCInventoryHolder.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCInventoryHolder.java @@ -4,17 +4,14 @@ import com.laytonsmith.abstraction.MCInventoryHolder; import org.bukkit.inventory.InventoryHolder; -/** - * - * @author import - */ public class BukkitMCInventoryHolder implements MCInventoryHolder { + InventoryHolder holder; - + public BukkitMCInventoryHolder(InventoryHolder i) { holder = i; } - + @Override public MCInventory getInventory() { return new BukkitMCInventory(holder.getInventory()); diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCInventoryView.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCInventoryView.java index e9948f3644..69625cfe0f 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCInventoryView.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCInventoryView.java @@ -1,24 +1,24 @@ package com.laytonsmith.abstraction.bukkit; +import com.laytonsmith.PureUtilities.Common.ReflectionUtils; import com.laytonsmith.abstraction.MCHumanEntity; import com.laytonsmith.abstraction.MCInventory; import com.laytonsmith.abstraction.MCInventoryView; import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCHumanEntity; import com.laytonsmith.abstraction.enums.MCInventoryType; +import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.ItemStack; -/** - * - * - */ public class BukkitMCInventoryView implements MCInventoryView { - + InventoryView iv; public BukkitMCInventoryView(InventoryView iv) { this.iv = iv; } - + @Override public String toString() { return iv.toString(); @@ -26,7 +26,7 @@ public String toString() { @Override public boolean equals(Object obj) { - return (obj instanceof BukkitMCInventoryView?iv.equals(((BukkitMCInventoryView)obj).iv):false); + return obj instanceof BukkitMCInventoryView && iv.equals(((BukkitMCInventoryView) obj).iv); } @Override @@ -36,57 +36,56 @@ public int hashCode() { @Override public MCInventory getBottomInventory() { - return new BukkitMCInventory(iv.getBottomInventory()); + return new BukkitMCInventory(ReflectionUtils.invokeMethod(InventoryView.class, iv, "getBottomInventory")); } @Override public MCInventory getTopInventory() { - return new BukkitMCInventory(iv.getTopInventory()); + return new BukkitMCInventory(ReflectionUtils.invokeMethod(InventoryView.class, iv, "getTopInventory")); } @Override public void close() { - iv.close(); + ReflectionUtils.invokeMethod(InventoryView.class, iv, "close"); } @Override public int countSlots() { - return iv.countSlots(); + return ReflectionUtils.invokeMethod(InventoryView.class, iv, "countSlots"); } @Override public int convertSlot(int rawSlot) { - return iv.convertSlot(rawSlot); + return ReflectionUtils.invokeMethod(iv, "convertSlot", rawSlot); } @Override public MCItemStack getItem(int slot) { - return new BukkitMCItemStack(iv.getItem(slot)); + return new BukkitMCItemStack((ItemStack) ReflectionUtils.invokeMethod(iv, "getItem", slot)); } @Override public MCHumanEntity getPlayer() { - return new BukkitMCHumanEntity(iv.getPlayer()); + return new BukkitMCHumanEntity(ReflectionUtils.invokeMethod(InventoryView.class, iv, "getPlayer")); } @Override public String getTitle() { - return iv.getTitle(); + return ReflectionUtils.invokeMethod(InventoryView.class, iv, "getTitle"); } - + @Override public MCInventoryType getType() { - return MCInventoryType.valueOf(this.iv.getType().name()); + return MCInventoryType.valueOf(((InventoryType) ReflectionUtils.invokeMethod(InventoryView.class, iv, "getType")).name()); } - + @Override public void setCursor(MCItemStack item) { - iv.setCursor(((BukkitMCItemStack)item).__ItemStack()); + ReflectionUtils.invokeMethod(iv, "setCursor", ((BukkitMCItemStack) item).__ItemStack()); } @Override public void setItem(int slot, MCItemStack item) { - iv.setItem(slot, (((BukkitMCItemStack)item).__ItemStack())); + ReflectionUtils.invokeMethod(iv, "setItem", slot, (((BukkitMCItemStack) item).__ItemStack())); } - } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCItem.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCItem.java deleted file mode 100644 index 5e7efb8796..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCItem.java +++ /dev/null @@ -1,40 +0,0 @@ - -package com.laytonsmith.abstraction.bukkit; - -import com.laytonsmith.abstraction.MCItem; -import com.laytonsmith.abstraction.MCItemStack; -import org.bukkit.entity.Item; - -/** - * - * - */ -public class BukkitMCItem extends BukkitMCEntity implements MCItem{ - - Item i; - - public BukkitMCItem(Item i){ - super(i); - this.i = i; - } - - @Override - public MCItemStack getItemStack(){ - return new BukkitMCItemStack(i.getItemStack()); - } - - @Override - public int getPickupDelay(){ - return i.getPickupDelay(); - } - - @Override - public void setItemStack(MCItemStack stack){ - i.setItemStack(((BukkitMCItemStack) stack).asItemStack()); - } - - @Override - public void setPickupDelay(int delay){ - i.setPickupDelay(delay); - } -} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCItemFactory.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCItemFactory.java index 93eecfe3e3..5a0ec401bb 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCItemFactory.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCItemFactory.java @@ -10,13 +10,10 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; -/** - * - * @author jb_aero - */ public class BukkitMCItemFactory implements MCItemFactory { ItemFactory f; + public BukkitMCItemFactory(ItemFactory itemFactory) { this.f = itemFactory; } @@ -31,7 +28,7 @@ public MCItemMeta asMetaFor(MCItemMeta meta, MCItemStack stack) { @Override public MCItemMeta asMetaFor(MCItemMeta meta, MCMaterial material) { ItemMeta bmeta = ((BukkitMCItemMeta) meta).asItemMeta(); - Material bmat = Material.getMaterial(material.getType()); + Material bmat = (Material) material.getHandle(); return BukkitConvertor.BukkitGetCorrectMeta(f.asMetaFor(bmeta, bmat)); } @@ -47,10 +44,13 @@ public MCColor getDefaultLeatherColor() { @Override public MCItemMeta getItemMeta(MCMaterial material) { - if(material == null){ + if(material == null) { + return null; + } + ItemMeta im = f.getItemMeta((Material) material.getHandle()); + if(im == null) { return null; } - ItemMeta im = f.getItemMeta(Material.getMaterial(material.getType())); return BukkitConvertor.BukkitGetCorrectMeta(im); } @@ -64,7 +64,7 @@ public boolean isApplicable(MCItemMeta meta, MCItemStack stack) { @Override public boolean isApplicable(MCItemMeta meta, MCMaterial material) { ItemMeta bmeta = ((BukkitMCItemMeta) meta).asItemMeta(); - Material bmat = Material.getMaterial(material.getType()); + Material bmat = (Material) material.getHandle(); return f.isApplicable(bmeta, bmat); } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCItemMeta.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCItemMeta.java index 284f053e1a..c80022c509 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCItemMeta.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCItemMeta.java @@ -1,35 +1,72 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ package com.laytonsmith.abstraction.bukkit; +import com.google.common.collect.LinkedHashMultimap; +import com.google.common.collect.Multimap; +import com.laytonsmith.PureUtilities.Common.ReflectionUtils; +import com.laytonsmith.PureUtilities.Common.ReflectionUtils.ReflectionException; import com.laytonsmith.abstraction.AbstractionObject; -import com.laytonsmith.abstraction.MCEnchantment; +import com.laytonsmith.abstraction.MCAttributeModifier; +import com.laytonsmith.abstraction.MCCooldownComponent; +import com.laytonsmith.abstraction.MCEquippableComponent; +import com.laytonsmith.abstraction.MCFoodComponent; import com.laytonsmith.abstraction.MCItemMeta; +import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.MCTagContainer; +import com.laytonsmith.abstraction.blocks.MCBlockData; +import com.laytonsmith.abstraction.blocks.MCMaterial; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCBlockData; +import com.laytonsmith.abstraction.enums.MCEnchantment; +import com.laytonsmith.abstraction.enums.MCItemFlag; + +import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; + +import com.laytonsmith.abstraction.enums.MCItemRarity; +import com.laytonsmith.abstraction.enums.MCVersion; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCEnchantment; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCItemFlag; +import com.laytonsmith.core.MSLog; +import com.laytonsmith.core.MSLog.Tags; +import com.laytonsmith.core.Static; +import com.laytonsmith.core.constructs.Target; +import org.bukkit.Bukkit; +import org.bukkit.JukeboxSong; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeModifier; +import org.bukkit.block.data.BlockData; import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemRarity; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.BlockDataMeta; +import org.bukkit.inventory.meta.Damageable; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.Repairable; +import org.bukkit.inventory.meta.components.EquippableComponent; +import org.bukkit.inventory.meta.components.FoodComponent; +import org.bukkit.inventory.meta.components.JukeboxPlayableComponent; +import org.bukkit.inventory.meta.components.UseCooldownComponent; -/** - * - * - */ public class BukkitMCItemMeta implements MCItemMeta { ItemMeta im; + public BukkitMCItemMeta(ItemMeta im) { this.im = im; } public BukkitMCItemMeta(AbstractionObject o) { - im = (ItemMeta)o; + im = (ItemMeta) o; } - + @Override public boolean hasDisplayName() { return im.hasDisplayName(); @@ -59,7 +96,7 @@ public List getLore() { public void setLore(List lore) { im.setLore(lore); } - + @Override public boolean hasEnchants() { return im.hasEnchants(); @@ -67,35 +104,35 @@ public boolean hasEnchants() { @Override public Map getEnchants() { - Map map = new HashMap(); + Map map = new HashMap<>(); for(Entry entry : im.getEnchants().entrySet()) { - map.put(new BukkitMCEnchantment(entry.getKey()), entry.getValue()); + map.put(BukkitMCEnchantment.valueOfConcrete(entry.getKey()), entry.getValue()); } return map; } - + @Override public boolean addEnchant(MCEnchantment ench, int level, boolean ignoreLevelRestriction) { - return im.addEnchant(((BukkitMCEnchantment) ench).__Enchantment(), level, ignoreLevelRestriction); + return im.addEnchant((Enchantment) ench.getConcrete(), level, ignoreLevelRestriction); } - + @Override public boolean removeEnchant(MCEnchantment ench) { - return im.removeEnchant(((BukkitMCEnchantment) ench).__Enchantment()); + return im.removeEnchant((Enchantment) ench.getConcrete()); } @Override public Object getHandle() { return im; } - + public ItemMeta asItemMeta() { return im; } @Override public boolean equals(Object obj) { - return im.equals(obj); + return obj instanceof MCItemMeta && im.equals(((MCItemMeta) obj).getHandle()); } @Override @@ -107,19 +144,367 @@ public int hashCode() { public String toString() { return im.toString(); } - + @Override public boolean hasRepairCost() { return ((Repairable) im).hasRepairCost(); } - + @Override public int getRepairCost() { return ((Repairable) im).getRepairCost(); } - + @Override public void setRepairCost(int cost) { ((Repairable) im).setRepairCost(cost); } + + @Override + public void addItemFlags(MCItemFlag... flags) { + for(MCItemFlag flag : flags) { + im.addItemFlags(BukkitMCItemFlag.getConvertor().getConcreteEnum(flag)); + } + } + + @Override + public Set getItemFlags() { + Set flags = im.getItemFlags(); + Set ret = new HashSet<>(flags.size()); + for(ItemFlag flag : flags) { + try { + ret.add(MCItemFlag.valueOf(flag.name())); + } catch(IllegalArgumentException ignore) {} + } + return ret; + } + + @Override + public boolean isUnbreakable() { + return im.isUnbreakable(); + } + + @Override + public void setUnbreakable(boolean unbreakable) { + im.setUnbreakable(unbreakable); + } + + @Override + public boolean hasDamage() { + if(((BukkitMCServer) Static.getServer()).isPaper() + && Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_21)) { + return ((Damageable) im).hasDamageValue(); + } + return ((Damageable) im).hasDamage(); + } + + @Override + public int getDamage() { + return ((Damageable) im).getDamage(); + } + + @Override + public void setDamage(int damage) { + ((Damageable) im).setDamage(damage); + } + + @Override + public boolean hasMaxDamage() { + return ((Damageable) im).hasMaxDamage(); + } + + @Override + public int getMaxDamage() { + return ((Damageable) im).getMaxDamage(); + } + + @Override + public void setMaxDamage(Integer damage) { + ((Damageable) im).setMaxDamage(damage); + } + + @Override + public MCBlockData getBlockData(MCMaterial material) { + return new BukkitMCBlockData(((BlockDataMeta) this.im).getBlockData((Material) material.getHandle())); + } + + @Override + public Map getExistingBlockData() { + try { + Class clz = Class.forName(Bukkit.getServer().getClass().getPackage().getName() + ".inventory.CraftMetaItem"); + return (Map) ReflectionUtils.get(clz, this.im, "blockData"); + } catch (ClassNotFoundException e) { + MSLog.GetLogger().e(Tags.GENERAL, "Failed to get CraftMetaItem class.", Target.UNKNOWN); + return null; + } catch (ReflectionException ex) { + MSLog.GetLogger().e(Tags.GENERAL, "Failed to get blockData from CraftMetaItem.", Target.UNKNOWN); + return null; + } + } + + @Override + public boolean hasBlockData() { + return ((BlockDataMeta) this.im).hasBlockData(); + } + + @Override + public void setBlockData(MCBlockData blockData) { + ((BlockDataMeta) this.im).setBlockData((BlockData) blockData.getHandle()); + } + + @Override + public boolean hasCustomModelData() { + return im.hasCustomModelData(); + } + + @Override + public int getCustomModelData() { + return im.getCustomModelData(); + } + + @Override + public void setCustomModelData(int id) { + im.setCustomModelData(id); + } + + @Override + public List getAttributeModifiers() { + Multimap modifiers = im.getAttributeModifiers(); + if(modifiers == null) { + return null; + } + List ret = new ArrayList<>(); + for(Entry modifier : modifiers.entries()) { + ret.add(new BukkitMCAttributeModifier(modifier.getKey(), modifier.getValue())); + } + return ret; + } + + @Override + public void setAttributeModifiers(List modifiers) { + Multimap map = LinkedHashMultimap.create(); + for(MCAttributeModifier m : modifiers) { + map.put((Attribute) m.getAttribute().getConcrete(), (AttributeModifier) m.getHandle()); + } + im.setAttributeModifiers(map); + } + + @Override + public boolean hasCustomTags() { + return !im.getPersistentDataContainer().isEmpty(); + } + + public MCTagContainer getCustomTags() { + return new BukkitMCTagContainer(im.getPersistentDataContainer()); + } + + @Override + public boolean hasItemName() { + return im.hasItemName(); + } + + @Override + public String getItemName() { + return im.getItemName(); + } + + @Override + public void setItemName(String name) { + im.setItemName(name); + } + + @Override + public boolean isHideTooltip() { + return im.isHideTooltip(); + } + + @Override + public void setHideTooltip(boolean hide) { + im.setHideTooltip(hide); + } + + @Override + public boolean hasEnchantmentGlintOverride() { + return im.hasEnchantmentGlintOverride(); + } + + @Override + public boolean getEnchantmentGlintOverride() { + return im.getEnchantmentGlintOverride(); + } + + @Override + public void setEnchantmentGlintOverride(boolean glint) { + im.setEnchantmentGlintOverride(glint); + } + + @Override + public boolean hasMaxStackSize() { + return im.hasMaxStackSize(); + } + + @Override + public int getMaxStackSize() { + return im.getMaxStackSize(); + } + + @Override + public void setMaxStackSize(Integer size) { + im.setMaxStackSize(size); + } + + @Override + public boolean hasRarity() { + return im.hasRarity(); + } + + @Override + public MCItemRarity getRarity() { + return MCItemRarity.valueOf(im.getRarity().name()); + } + + @Override + public void setRarity(MCItemRarity rarity) { + im.setRarity(ItemRarity.valueOf(rarity.name())); + } + + @Override + public boolean hasEnchantable() { + return im.hasEnchantable(); + } + + @Override + public int getEnchantable() { + return im.getEnchantable(); + } + + @Override + public void setEnchantable(Integer enchantability) { + im.setEnchantable(enchantability); + } + + @Override + public boolean hasJukeboxPlayable() { + return im.hasJukeboxPlayable(); + } + + @Override + public String getJukeboxPlayable() { + return im.getJukeboxPlayable().getSongKey().toString(); + } + + @Override + public void setJukeboxPlayable(String playable) { + if(playable == null) { + im.setJukeboxPlayable(null); + } else { + JukeboxSong song = Registry.JUKEBOX_SONG.get(NamespacedKey.fromString(playable)); + if(song != null) { + JukeboxPlayableComponent component = im.getJukeboxPlayable(); + component.setSong(song); + im.setJukeboxPlayable(component); + } + } + } + + @Override + public boolean isGlider() { + return im.isGlider(); + } + + @Override + public void setGlider(boolean glider) { + im.setGlider(glider); + } + + @Override + public boolean hasUseRemainder() { + return im.hasUseRemainder(); + } + + @Override + public MCItemStack getUseRemainder() { + return new BukkitMCItemStack(im.getUseRemainder()); + } + + @Override + public void setUseRemainder(MCItemStack remainder) { + im.setUseRemainder((ItemStack) remainder.getHandle()); + } + + @Override + public boolean hasFood() { + return im.hasFood(); + } + + @Override + public MCFoodComponent getFood() { + return new BukkitMCFoodComponent(im.getFood()); + } + + @Override + public void setFood(MCFoodComponent component) { + im.setFood((FoodComponent) component.getHandle()); + } + + @Override + public boolean hasItemModel() { + return im.hasItemModel(); + } + + @Override + public String getItemModel() { + return im.getItemModel().toString(); + } + + @Override + public void setItemModel(String key) { + im.setItemModel(NamespacedKey.fromString(key)); + } + + @Override + public boolean hasTooltipStyle() { + return im.hasTooltipStyle(); + } + + @Override + public String getTooltipStyle() { + return im.getTooltipStyle().toString(); + } + + @Override + public void setTooltipStyle(String key) { + im.setTooltipStyle(NamespacedKey.fromString(key)); + } + + @Override + public boolean hasUseCooldown() { + return im.hasUseCooldown(); + } + + @Override + public MCCooldownComponent getUseCooldown() { + return new BukkitMCCooldownComponent(im.getUseCooldown()); + } + + @Override + public void setUseCooldown(MCCooldownComponent component) { + im.setUseCooldown((UseCooldownComponent) component.getHandle()); + } + + @Override + public boolean hasEquippable() { + return im.hasEquippable(); + } + + @Override + public MCEquippableComponent getEquippable() { + return new BukkitMCEquippableComponent(im.getEquippable()); + } + + @Override + public void setEquippable(MCEquippableComponent component) { + im.setEquippable((EquippableComponent) component.getHandle()); + } + } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCItemStack.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCItemStack.java index 750d55fb73..426d45a1ac 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCItemStack.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCItemStack.java @@ -1,160 +1,120 @@ - - package com.laytonsmith.abstraction.bukkit; import com.laytonsmith.abstraction.AbstractionObject; -import com.laytonsmith.abstraction.MCEnchantment; import com.laytonsmith.abstraction.MCItemMeta; import com.laytonsmith.abstraction.MCItemStack; -import com.laytonsmith.abstraction.MCMaterialData; import com.laytonsmith.abstraction.blocks.MCMaterial; import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCMaterial; -import java.util.HashMap; -import java.util.Map; +import com.laytonsmith.abstraction.enums.MCEnchantment; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCEnchantment; +import org.bukkit.Material; import org.bukkit.enchantments.Enchantment; import org.bukkit.inventory.ItemStack; -import org.bukkit.material.MaterialData; +import org.bukkit.inventory.meta.BlockStateMeta; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.HashMap; +import java.util.Map; -/** - * - * - */ public class BukkitMCItemStack implements MCItemStack { - ItemStack is; - public BukkitMCItemStack(ItemStack is){ - this.is = is; - } - - public BukkitMCItemStack(AbstractionObject a){ - this((ItemStack)null); - if(a instanceof MCItemStack){ - this.is = ((ItemStack)a.getHandle()); - } else { - throw new ClassCastException(); - } - } - - @Override - public Object getHandle(){ - return is; - } - - @Override - public MCMaterialData getData(){ - if(is == null || is.getData() == null){ - return null; - } - return new BukkitMCMaterialData(is.getData()); - } - - @Override - public short getDurability(){ - if(is == null){ - return 0; - } - return is.getDurability(); - } - - @Override - public int getTypeId(){ - if(is == null){ - return 0; - } - return is.getTypeId(); - } - - @Override - public void setDurability(short data) { - if(is == null){ - return; - } - is.setDurability(data); - } - - @Override - public void addEnchantment(MCEnchantment e, int level) { - if(is == null){ - return; - } - is.addEnchantment(((BukkitMCEnchantment)e).__Enchantment(), level); - } - - @Override - public void addUnsafeEnchantment(MCEnchantment e, int level){ - if(is == null){ - return; - } - is.addUnsafeEnchantment(((BukkitMCEnchantment)e).__Enchantment(), level); - } - - @Override - public Map getEnchantments(){ - if(is == null || is.getEnchantments() == null){ - return null; - } - Map map = new HashMap(); - for(Map.Entry entry : is.getEnchantments().entrySet()){ - map.put(new BukkitMCEnchantment(entry.getKey()), entry.getValue()); - } - return map; - } - - @Override - public void removeEnchantment(MCEnchantment e){ - if(is == null){ - return; - } - is.removeEnchantment(((BukkitMCEnchantment)e).__Enchantment()); - } - - @Override - public MCMaterial getType() { - if(is == null){ - return null; - } - return new BukkitMCMaterial(is.getType()); - } - - @Override - public void setTypeId(int type) { - if(is == null){ - return; - } - is.setTypeId(type); - } - - @Override - public int getAmount() { - if(is == null){ - return 0; - } - return is.getAmount(); - } - - public ItemStack __ItemStack() { - return is; - } - - @Override - public void setData(int data) { - if(is == null){ - return; - } - is.setData(new MaterialData(is.getTypeId(), (byte)data)); - } - - @Override - public int maxStackSize() { - if(is == null){ - return 0; - } - return is.getMaxStackSize(); - } - - public ItemStack asItemStack() { - return is; - } - + + ItemStack is; + + public BukkitMCItemStack(ItemStack is) { + this.is = is; + } + + public BukkitMCItemStack(AbstractionObject a) { + this((ItemStack) null); + if(a instanceof MCItemStack) { + this.is = ((ItemStack) a.getHandle()); + } else { + throw new ClassCastException(); + } + } + + @Override + public Object getHandle() { + return is; + } + + @Override + public void addEnchantment(MCEnchantment e, int level) { + if(is == null) { + return; + } + is.addEnchantment((Enchantment) e.getConcrete(), level); + } + + @Override + public void addUnsafeEnchantment(MCEnchantment e, int level) { + if(is == null) { + return; + } + is.addUnsafeEnchantment((Enchantment) e.getConcrete(), level); + } + + @Override + public Map getEnchantments() { + Map map = new HashMap<>(); + try { + for(Map.Entry entry : is.getEnchantments().entrySet()) { + map.put(BukkitMCEnchantment.valueOfConcrete(entry.getKey()), entry.getValue()); + } + } catch (NullPointerException npe) { + // Probably invalid enchantment, always return map + } + return map; + } + + @Override + public void removeEnchantment(MCEnchantment e) { + if(is == null) { + return; + } + is.removeEnchantment((Enchantment) e.getConcrete()); + } + + @Override + public MCMaterial getType() { + if(is == null) { + return BukkitMCMaterial.valueOfConcrete(Material.AIR); + } + return BukkitMCMaterial.valueOfConcrete(is.getType()); + } + + @Override + public int getAmount() { + if(is == null) { + return 0; + } + return is.getAmount(); + } + + @Override + public void setAmount(int amt) { + if(is == null) { + return; + } + is.setAmount(amt); + } + + public ItemStack __ItemStack() { + return is; + } + + @Override + public int maxStackSize() { + if(is == null) { + return 0; + } + return is.getMaxStackSize(); + } + + public ItemStack asItemStack() { + return is; + } + @Override public String toString() { return is.toString(); @@ -162,7 +122,7 @@ public String toString() { @Override public boolean equals(Object obj) { - return (obj instanceof BukkitMCItemStack?is.equals(((BukkitMCItemStack)obj).is):false); + return obj instanceof BukkitMCItemStack && is.equals(((BukkitMCItemStack) obj).asItemStack()); } @Override @@ -172,23 +132,32 @@ public int hashCode() { @Override public boolean hasItemMeta() { - return is.hasItemMeta(); + return is != null && is.hasItemMeta(); } - + @Override public MCItemMeta getItemMeta() { - return BukkitConvertor.BukkitGetCorrectMeta(is.getItemMeta()); + ItemMeta im = is.getItemMeta(); + if(im instanceof BlockStateMeta) { + return new BukkitMCBlockStateMeta((BlockStateMeta) im, is.getType()); + } + return BukkitConvertor.BukkitGetCorrectMeta(im); } @Override public void setItemMeta(MCItemMeta im) { - if (is == null) { + if(is == null) { return; } - if (im == null) { + if(im == null) { is.setItemMeta(null); return; } - is.setItemMeta(((BukkitMCItemMeta)im).asItemMeta()); + is.setItemMeta(((BukkitMCItemMeta) im).asItemMeta()); + } + + @Override + public boolean isEmpty() { + return is == null || is.getAmount() == 0 || is.getType().isAir(); } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCKnowledgeBookMeta.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCKnowledgeBookMeta.java new file mode 100644 index 0000000000..3a4385d21f --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCKnowledgeBookMeta.java @@ -0,0 +1,42 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCKnowledgeBookMeta; +import com.laytonsmith.abstraction.MCNamespacedKey; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.meta.KnowledgeBookMeta; + +import java.util.ArrayList; +import java.util.List; + +public class BukkitMCKnowledgeBookMeta extends BukkitMCItemMeta implements MCKnowledgeBookMeta { + + private final KnowledgeBookMeta kbm; + + public BukkitMCKnowledgeBookMeta(KnowledgeBookMeta im) { + super(im); + kbm = im; + } + + @Override + public boolean hasRecipes() { + return kbm.hasRecipes(); + } + + @Override + public List getRecipes() { + List keys = new ArrayList<>(); + for(NamespacedKey key : kbm.getRecipes()) { + keys.add(new BukkitMCNamespacedKey(key)); + } + return keys; + } + + @Override + public void setRecipes(List recipes) { + List keys = new ArrayList<>(recipes.size()); + for(MCNamespacedKey key : recipes) { + keys.add((NamespacedKey) key.getHandle()); + } + kbm.setRecipes(keys); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCLeatherArmorMeta.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCLeatherArmorMeta.java index 7792f7fb71..6ad17f8e93 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCLeatherArmorMeta.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCLeatherArmorMeta.java @@ -1,7 +1,3 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ package com.laytonsmith.abstraction.bukkit; import com.laytonsmith.abstraction.AbstractionObject; @@ -9,12 +5,10 @@ import com.laytonsmith.abstraction.MCLeatherArmorMeta; import org.bukkit.inventory.meta.LeatherArmorMeta; -/** - * - * - */ public class BukkitMCLeatherArmorMeta extends BukkitMCItemMeta implements MCLeatherArmorMeta { + LeatherArmorMeta lam; + public BukkitMCLeatherArmorMeta(LeatherArmorMeta im) { super(im); lam = im; @@ -22,7 +16,7 @@ public BukkitMCLeatherArmorMeta(LeatherArmorMeta im) { public BukkitMCLeatherArmorMeta(AbstractionObject o) { super(o); - lam = (LeatherArmorMeta)o; + lam = (LeatherArmorMeta) o; } @Override @@ -34,5 +28,5 @@ public MCColor getColor() { public void setColor(MCColor color) { lam.setColor(BukkitMCColor.GetColor(color)); } - + } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCLightningStrike.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCLightningStrike.java deleted file mode 100644 index d19bddb6d9..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCLightningStrike.java +++ /dev/null @@ -1,24 +0,0 @@ - -package com.laytonsmith.abstraction.bukkit; - -import com.laytonsmith.abstraction.MCLightningStrike; -import org.bukkit.entity.LightningStrike; - -/** - * - * @author Jim - */ -public class BukkitMCLightningStrike extends BukkitMCEntity implements MCLightningStrike{ - - LightningStrike ls; - - public BukkitMCLightningStrike(LightningStrike ls){ - super(ls); - this.ls = ls; - } - - @Override - public boolean isEffect(){ - return ls.isEffect(); - } -} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCLivingEntity.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCLivingEntity.java deleted file mode 100644 index aff6eba97a..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCLivingEntity.java +++ /dev/null @@ -1,390 +0,0 @@ -package com.laytonsmith.abstraction.bukkit; - -import com.laytonsmith.PureUtilities.Common.ReflectionUtils; -import com.laytonsmith.abstraction.MCEntity; -import com.laytonsmith.abstraction.MCEntityEquipment; -import com.laytonsmith.abstraction.MCLivingEntity; -import com.laytonsmith.abstraction.MCLocation; -import com.laytonsmith.abstraction.MCPlayer; -import com.laytonsmith.abstraction.blocks.MCBlock; -import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCBlock; -import com.laytonsmith.abstraction.bukkit.entities.BukkitMCEntityProjectileSource; -import com.laytonsmith.core.Static; -import com.laytonsmith.core.constructs.Target; -import com.laytonsmith.core.exceptions.ConfigRuntimeException; -import com.laytonsmith.core.functions.Exceptions.ExceptionType; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; -import org.bukkit.block.Block; -import org.bukkit.entity.Creature; -import org.bukkit.entity.LivingEntity; -import org.bukkit.event.entity.EntityDamageEvent; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.util.BlockIterator; - -/** - * - * - */ -public class BukkitMCLivingEntity extends BukkitMCEntityProjectileSource implements MCLivingEntity { - - LivingEntity le; - - public BukkitMCLivingEntity(LivingEntity le) { - super(le); - this.le = le; - } - - @Override - public double getHealth() { - return le.getHealth(); - } - - @Override - public void setHealth(double i) { - le.setHealth(i); - } - - @Override - public double getMaxHealth() { - return le.getMaxHealth(); - } - - @Override - public void setMaxHealth(double health) { - le.setMaxHealth(health); - } - - @Override - public void resetMaxHealth() { - le.resetMaxHealth(); - } - - @Override - public void damage(double i) { - le.damage(i); - } - - @Override - public void damage(double amount, MCEntity source) { - le.damage(amount, ((BukkitMCEntity) source).getHandle()); - } - - @Override - public double getEyeHeight() { - return le.getEyeHeight(); - } - - @Override - public double getEyeHeight(boolean ignoreSneaking) { - return le.getEyeHeight(ignoreSneaking); - } - - @Override - public MCLocation getEyeLocation() { - return new BukkitMCLocation(le.getEyeLocation()); - } - - @Override - public MCPlayer getKiller() { - return new BukkitMCPlayer(le.getKiller()); - } - - @Override - public double getLastDamage() { - return le.getLastDamage(); - } - - @Override - public List getLastTwoTargetBlocks(HashSet transparent, - int maxDistance) { - List lst = le.getLastTwoTargetBlocks(transparent, maxDistance); - List retn = new ArrayList(); - - for (Block b : lst) { - retn.add(new BukkitMCBlock(b)); - } - - return retn; - } - - @Override - public List getLineOfSight(HashSet transparent, - int maxDistance) { - List lst = le.getLineOfSight(transparent, maxDistance); - List retn = new ArrayList(); - - for (Block b : lst) { - retn.add(new BukkitMCBlock(b)); - } - - return retn; - } - - @Override - public boolean hasLineOfSight(MCEntity other) { - return le.hasLineOfSight(((BukkitMCEntity) other).getHandle()); - } - - @Override - public int getMaximumAir() { - return le.getMaximumAir(); - } - - @Override - public int getMaximumNoDamageTicks() { - return le.getMaximumNoDamageTicks(); - } - - @Override - public int getNoDamageTicks() { - return le.getNoDamageTicks(); - } - - @Override - public int getRemainingAir() { - return le.getRemainingAir(); - } - - @Override - public MCBlock getTargetBlock(HashSet b, int i) { - return new BukkitMCBlock(le.getTargetBlock(b, i)); - } - - @Override - public MCBlock getTargetBlock(HashSet b, int i, boolean castToByte) { - if (castToByte) { - if (b == null) { - return getTargetBlock(null, i); - } - HashSet bb = new HashSet(); - for (int id : b) { - bb.add((byte) id); - } - return getTargetBlock(bb, i); - } - return new BukkitMCBlock(getFirstTargetBlock(b, i)); - } - - private Block getFirstTargetBlock(HashSet transparent, int maxDistance) { - List blocks = getLineOfSight(transparent, maxDistance, 1); - return blocks.get(0); - } - - private List getLineOfSight(HashSet transparent, int maxDistance, int maxLength) { - if (maxDistance > 512) { - maxDistance = 512; - } - ArrayList blocks = new ArrayList(); - Iterator itr = new BlockIterator(le, maxDistance); - - while (itr.hasNext()) { - Block block = itr.next(); - blocks.add(block); - if (maxLength != 0 && blocks.size() > maxLength) { - blocks.remove(0); - } - int id = block.getTypeId(); - if (transparent == null) { - if (id != 0) { - break; - } - } else { - if (!transparent.contains((short) id)) { - break; - } - } - } - return blocks; - } - - @Override - public void addEffect(int potionID, int strength, int seconds, boolean ambient, Target t) { - PotionEffect pe = new PotionEffect(PotionEffectType.getById(potionID), (int)Static.msToTicks(seconds * 1000), - strength, ambient); - try{ - if(le != null){ - le.addPotionEffect(pe, true); - } - } catch(NullPointerException e){ - // - Logger.getLogger(BukkitMCLivingEntity.class.getName()).log(Level.SEVERE, - "Bukkit appears to have derped. This is a problem with Bukkit, not CommandHelper. The effect should have still been applied.", e); - } -// EntityPlayer ep = ((CraftPlayer) p).getHandle(); -// MobEffect me = new MobEffect(potionID, seconds * 20, strength); -// //ep.addEffect(me); -// //ep.b(me); -// -// Class epc = EntityLiving.class; -// try { -// Method meth = epc.getDeclaredMethod("b", net.minecraft.server.MobEffect.class); -// //ep.d(new MobEffect(effect, seconds * 20, strength)); -// //Call it reflectively, because it's deobfuscated in newer versions of CB -// meth.invoke(ep, me); -// } catch (Exception e) { -// try { -// //Look for the addEffect version -// Method meth = epc.getDeclaredMethod("addEffect", MobEffect.class); -// //ep.addEffect(me); -// meth.invoke(ep, me); -// } catch (Exception ex) { -// Logger.getLogger(BukkitMCPlayer.class.getName()).log(Level.SEVERE, null, ex); -// } -// } - } - - @Override - public int getMaxEffect(){ - try { - PotionEffectType[] arr = (PotionEffectType[])ReflectionUtils.get(PotionEffectType.class, "byId"); - return arr.length - 1; - } catch(ReflectionUtils.ReflectionException e){ - return Integer.MAX_VALUE; - } - } - - @Override - public boolean removeEffect(int potionID) { - PotionEffectType t = PotionEffectType.getById(potionID); - boolean hasIt = false; - for(PotionEffect pe : le.getActivePotionEffects()) { - if (pe.getType() == t) { - hasIt = true; - break; - } - } - le.removePotionEffect(t); - return hasIt; - } - - @Override - public List getEffects(){ - List effects = new ArrayList(); - for(PotionEffect pe : le.getActivePotionEffects()){ - MCEffect e = new MCEffect(pe.getType().getId(), pe.getAmplifier(), - (int)(Static.ticksToMs(pe.getDuration()) / 1000), pe.isAmbient()); - effects.add(e); - } - return effects; - } - - @Override - public void setLastDamage(double damage) { - le.setLastDamage(damage); - } - - @Override - public void setMaximumAir(int ticks) { - le.setMaximumAir(ticks); - } - - @Override - public void setMaximumNoDamageTicks(int ticks) { - le.setMaximumNoDamageTicks(ticks); - } - - @Override - public void setNoDamageTicks(int ticks) { - le.setNoDamageTicks(ticks); - } - - @Override - public void setRemainingAir(int ticks) { - le.setRemainingAir(ticks); - } - - public LivingEntity asLivingEntity() { - return le; - } - - @Override - public MCEntityEquipment getEquipment() { - return new BukkitMCEntityEquipment(le.getEquipment()); - } - - @Override - public boolean getCanPickupItems() { - return le.getCanPickupItems(); - } - - @Override - public void setCanPickupItems(boolean pickup) { - le.setCanPickupItems(pickup); - } - - @Override - public boolean getRemoveWhenFarAway() { - return le.getRemoveWhenFarAway(); - } - - @Override - public void setRemoveWhenFarAway(boolean remove) { - le.setRemoveWhenFarAway(remove); - } - - @Override - public MCLivingEntity getTarget(Target t) { - if (!(le instanceof Creature)) { - throw new ConfigRuntimeException("This type of mob does not have a target API", - ExceptionType.BadEntityException, t); - } - LivingEntity target = ((Creature) le).getTarget(); - return target == null ? null : new BukkitMCLivingEntity(target); - } - - @Override - public void setTarget(MCLivingEntity target, Target t) { - if (!(le instanceof Creature)) { - throw new ConfigRuntimeException("This type of mob does not have a target API", - ExceptionType.BadEntityException, t); - } - ((Creature) le).setTarget(target == null ? null : ((BukkitMCLivingEntity) target).asLivingEntity()); - } - - @Override - public String getCustomName() { - return le.getCustomName(); - } - - @Override - public boolean isCustomNameVisible() { - return le.isCustomNameVisible(); - } - - @Override - public void setCustomName(String name) { - le.setCustomName(name); - } - - @Override - public void setCustomNameVisible(boolean visible) { - le.setCustomNameVisible(visible); - } - - @Override - public void kill(){ - le.setLastDamageCause(new EntityDamageEvent(le, EntityDamageEvent.DamageCause.CUSTOM, le.getHealth())); - le.setHealth(0D); - } - - @Override - public MCEntity getLeashHolder() { - return le.isLeashed() ? BukkitConvertor.BukkitGetCorrectEntity(le.getLeashHolder()) : null; - } - - @Override - public boolean isLeashed() { - return le.isLeashed(); - } - - @Override - public void setLeashHolder(MCEntity holder) { - le.setLeashHolder(holder == null ? null : ((BukkitMCEntity) holder).getHandle()); - } -} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCLocation.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCLocation.java index 3c0f4fa539..efbf1f3f10 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCLocation.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCLocation.java @@ -1,107 +1,102 @@ package com.laytonsmith.abstraction.bukkit; +import com.laytonsmith.PureUtilities.Vector3D; import com.laytonsmith.abstraction.AbstractionObject; import com.laytonsmith.abstraction.MCChunk; import com.laytonsmith.abstraction.MCLocation; import com.laytonsmith.abstraction.MCWorld; -import com.laytonsmith.abstraction.Velocity; import com.laytonsmith.abstraction.blocks.MCBlock; import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCBlock; import org.bukkit.Location; import org.bukkit.util.Vector; -/** - * - * - */ public class BukkitMCLocation implements MCLocation { - Location l; - public BukkitMCLocation(Location l) { - this.l = l; - } - public BukkitMCLocation(AbstractionObject a) { - if (a instanceof MCLocation) { - this.l = ((Location)a.getHandle()); - } else { - throw new ClassCastException(); - } - } + Location l; + + public BukkitMCLocation(Location l) { + this.l = l; + } + + public BukkitMCLocation(AbstractionObject a) { + if(a instanceof MCLocation) { + this.l = ((Location) a.getHandle()); + } else { + throw new ClassCastException(); + } + } @Override - public Object getHandle() { - return l; - } + public Object getHandle() { + return l; + } @Override - public double getX() { - return l.getX(); - } + public double getX() { + return l.getX(); + } @Override - public double getY() { - return l.getY(); - } + public double getY() { + return l.getY(); + } @Override - public double getZ() { - return l.getZ(); - } + public double getZ() { + return l.getZ(); + } @Override public double distance(MCLocation o) { - return l.distance(((BukkitMCLocation)o)._Location()); + return l.distance(((BukkitMCLocation) o)._Location()); } - + @Override public double distanceSquared(MCLocation o) { - return l.distanceSquared(((BukkitMCLocation)o)._Location()); + return l.distanceSquared(((BukkitMCLocation) o)._Location()); } @Override - public MCWorld getWorld() { - if (l.getWorld() == null) { - return null; - } - return new BukkitMCWorld(l.getWorld()); - } + public MCWorld getWorld() { + if(l.getWorld() == null) { + return null; + } + return new BukkitMCWorld(l.getWorld()); + } @Override - public float getYaw() { - return l.getYaw(); - } + public float getYaw() { + return l.getYaw(); + } @Override - public float getPitch() { - return l.getPitch(); - } + public float getPitch() { + return l.getPitch(); + } @Override - public int getBlockX() { - return l.getBlockX(); - } + public int getBlockX() { + return l.getBlockX(); + } @Override - public int getBlockY() { - return l.getBlockY(); - } + public int getBlockY() { + return l.getBlockY(); + } @Override - public int getBlockZ() { - return l.getBlockZ(); - } + public int getBlockZ() { + return l.getBlockZ(); + } @Override - public MCBlock getBlock() { - if (l == null || l.getBlock() == null) { - return null; - } - return new BukkitMCBlock(l.getBlock()); - } + public MCBlock getBlock() { + return new BukkitMCBlock(l.getBlock()); + } - public Location _Location() { - return l; - } + public Location _Location() { + return l; + } @Override public void setX(double x) { @@ -119,14 +114,14 @@ public void setZ(double z) { } @Override - public void setPitch(float p) { - l.setPitch(p); - } + public void setPitch(float p) { + l.setPitch(p); + } @Override - public void setYaw(float y) { - l.setYaw(y); - } + public void setYaw(float y) { + l.setYaw(y); + } @Override public MCLocation add(MCLocation vec) { @@ -134,8 +129,8 @@ public MCLocation add(MCLocation vec) { } @Override - public MCLocation add(Velocity vec) { - return new BukkitMCLocation(l.add(new Vector(vec.x, vec.y, vec.z))); + public MCLocation add(Vector3D vec) { + return new BukkitMCLocation(l.add(new Vector(vec.X(), vec.Y(), vec.Z()))); } @Override @@ -149,8 +144,8 @@ public MCLocation multiply(double m) { } @Override - public Velocity toVector() { - return new Velocity(l.getX(), l.getY(), l.getZ()); + public Vector3D toVector() { + return new Vector3D(l.getX(), l.getY(), l.getZ()); } @Override @@ -159,8 +154,8 @@ public MCLocation subtract(MCLocation vec) { } @Override - public MCLocation subtract(Velocity vec) { - return new BukkitMCLocation(l.subtract(new Vector(vec.x, vec.y, vec.z))); + public MCLocation subtract(Vector3D vec) { + return new BukkitMCLocation(l.subtract(new Vector(vec.X(), vec.Y(), vec.Z()))); } @Override @@ -168,14 +163,14 @@ public MCLocation subtract(double x, double y, double z) { return new BukkitMCLocation(l.subtract(x, y, z)); } - @Override - public MCLocation clone() { - return new BukkitMCLocation(l.clone()); - } + @Override + public MCLocation clone() { + return new BukkitMCLocation(l.clone()); + } - public Location asLocation() { - return l; - } + public Location asLocation() { + return l; + } @Override public String toString() { @@ -184,7 +179,7 @@ public String toString() { @Override public boolean equals(Object obj) { - return (obj instanceof BukkitMCLocation?l.equals(((BukkitMCLocation)obj).l):false); + return (obj instanceof BukkitMCLocation && l.equals(((BukkitMCLocation) obj).l)); } @Override @@ -193,13 +188,9 @@ public int hashCode() { } @Override - public void breakBlock() { - l.getBlock().breakNaturally(); - } - - @Override - public Velocity getDirection() { - return new Velocity(1, l.getDirection().getX(), l.getDirection().getY(), l.getDirection().getZ()); + public Vector3D getDirection() { + Vector v = l.getDirection(); + return new Vector3D(v.getX(), v.getY(), v.getZ()); } @Override diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCMapMeta.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCMapMeta.java new file mode 100644 index 0000000000..09952f691a --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCMapMeta.java @@ -0,0 +1,44 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCColor; +import com.laytonsmith.abstraction.MCMapMeta; +import org.bukkit.inventory.meta.MapMeta; + +public class BukkitMCMapMeta extends BukkitMCItemMeta implements MCMapMeta { + + MapMeta mm; + + public BukkitMCMapMeta(MapMeta im) { + super(im); + mm = im; + } + + @Override + public boolean hasMapId() { + return mm.hasMapId(); + } + + @Override + public int getMapId() { + return mm.getMapId(); + } + + @Override + public void setMapId(int id) { + mm.setMapId(id); + } + + @Override + public MCColor getColor() { + if(mm.hasColor()) { + return BukkitMCColor.GetMCColor(mm.getColor()); + } + return null; + } + + @Override + public void setColor(MCColor color) { + mm.setColor(BukkitMCColor.GetColor(color)); + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCMaterialData.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCMaterialData.java deleted file mode 100644 index a1b0685cb2..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCMaterialData.java +++ /dev/null @@ -1,60 +0,0 @@ - - -package com.laytonsmith.abstraction.bukkit; - -import com.laytonsmith.abstraction.AbstractionObject; -import com.laytonsmith.abstraction.MCMaterialData; -import com.laytonsmith.abstraction.blocks.MCMaterial; -import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCMaterial; -import org.bukkit.material.MaterialData; - -/** - * - * - */ -public class BukkitMCMaterialData implements MCMaterialData{ - MaterialData md; - public BukkitMCMaterialData(MaterialData md){ - this.md = md; - } - - public BukkitMCMaterialData(AbstractionObject a){ - this((MaterialData)null); - if(a instanceof MCMaterialData){ - this.md = ((MaterialData)a.getHandle()); - } else { - throw new ClassCastException(); - } - } - - @Override - public Object getHandle(){ - return md; - } - - @Override - public int getData() { - return md.getData(); - } - - @Override - public MCMaterial getMaterial() { - return new BukkitMCMaterial(md.getItemType()); - } - - @Override - public String toString() { - return md.toString(); - } - - @Override - public boolean equals(Object obj) { - return (obj instanceof BukkitMCMaterialData?md.equals(((BukkitMCMaterialData)obj).md):false); - } - - @Override - public int hashCode() { - return md.hashCode(); - } - -} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCMerchant.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCMerchant.java new file mode 100644 index 0000000000..146820e9cf --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCMerchant.java @@ -0,0 +1,84 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCHumanEntity; +import com.laytonsmith.abstraction.MCMerchant; +import com.laytonsmith.abstraction.MCMerchantRecipe; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCHumanEntity; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCPlayer; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Merchant; +import org.bukkit.inventory.MerchantRecipe; + +import java.util.ArrayList; +import java.util.List; + +public class BukkitMCMerchant implements MCMerchant { + + private String title; + private Merchant merchant; + public BukkitMCMerchant(Merchant mer, String title) { + merchant = mer; + this.title = title; + } + + @Override + public Merchant getHandle() { + return merchant; + } + + @Override + public int hashCode() { + return getHandle().hashCode(); + } + + @Override + public boolean equals(Object obj) { + return getHandle().equals(obj); + } + + @Override + public String toString() { + return getHandle().toString(); + } + + @Override + public boolean isTrading() { + return getHandle().isTrading(); + } + + @Override + public MCHumanEntity getTrader() { + HumanEntity he = getHandle().getTrader(); + if(he == null) { + return null; + } + if(he instanceof Player) { + return new BukkitMCPlayer(he); + } + return new BukkitMCHumanEntity(he); + } + + @Override + public List getRecipes() { + List ret = new ArrayList<>(); + for(MerchantRecipe mr : getHandle().getRecipes()) { + ret.add(new BukkitMCMerchantRecipe(mr)); + } + return ret; + } + + @Override + public void setRecipes(List recipes) { + List ret = new ArrayList<>(); + for(MCMerchantRecipe mr : recipes) { + ret.add((MerchantRecipe) mr.getHandle()); + } + getHandle().setRecipes(ret); + } + + @Override + public String getTitle() { + return title; + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCMerchantRecipe.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCMerchantRecipe.java new file mode 100644 index 0000000000..dd36f0c1b5 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCMerchantRecipe.java @@ -0,0 +1,112 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.MCMerchantRecipe; +import com.laytonsmith.abstraction.enums.MCRecipeType; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MerchantRecipe; + +import java.util.ArrayList; +import java.util.List; + +public class BukkitMCMerchantRecipe extends BukkitMCRecipe implements MCMerchantRecipe { + + private final MerchantRecipe handle; + + public BukkitMCMerchantRecipe(MerchantRecipe recipe) { + super(recipe); + handle = recipe; + } + + @Override + public String getKey() { + return null; + } + + @Override + public MerchantRecipe getHandle() { + return handle; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof BukkitMCMerchantRecipe && getHandle().equals(((BukkitMCMerchantRecipe) obj).getHandle()); + } + + @Override + public int hashCode() { + return getHandle().hashCode(); + } + + @Override + public String toString() { + return getHandle().toString(); + } + + @Override + public int getMaxUses() { + return getHandle().getMaxUses(); + } + + @Override + public void setMaxUses(int maxUses) { + getHandle().setMaxUses(maxUses); + } + + @Override + public int getUses() { + return getHandle().getUses(); + } + + @Override + public void setUses(int uses) { + getHandle().setUses(uses); + } + + @Override + public boolean hasExperienceReward() { + return getHandle().hasExperienceReward(); + } + + @Override + public void setHasExperienceReward(boolean flag) { + getHandle().setExperienceReward(flag); + } + + @Override + public List getIngredients() { + List ret = new ArrayList<>(); + for(ItemStack s : getHandle().getIngredients()) { + ret.add(new BukkitMCItemStack(s)); + } + return ret; + } + + @Override + public void setIngredients(List ingredients) { + int i = 0; + List ings = new ArrayList<>(); + for(MCItemStack s : ingredients) { + if(++i > 2) { + break; + // This recipe type only supports two ingredients. + // The Bukkit set method does not include built in enforcement + } + ings.add((ItemStack) s.getHandle()); + } + getHandle().setIngredients(ings); + } + + @Override + public MCRecipeType getRecipeType() { + return MCRecipeType.MERCHANT; + } + + @Override + public String getGroup() { + return ""; + } + + @Override + public void setGroup(String group) {} +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCMetadataValue.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCMetadataValue.java index a3e02bae1d..afc1348abd 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCMetadataValue.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCMetadataValue.java @@ -6,69 +6,69 @@ public class BukkitMCMetadataValue implements MCMetadataValue { - private final MetadataValue _value; + private final MetadataValue value; public BukkitMCMetadataValue(MetadataValue value) { - _value = value; + this.value = value; } @Override public boolean asBoolean() { - return _value.asBoolean(); + return this.value.asBoolean(); } @Override public byte asByte() { - return _value.asByte(); + return this.value.asByte(); } @Override public double asDouble() { - return _value.asDouble(); + return this.value.asDouble(); } @Override public float asFloat() { - return _value.asFloat(); + return this.value.asFloat(); } @Override public int asInt() { - return _value.asInt(); + return this.value.asInt(); } @Override public long asLong() { - return _value.asLong(); + return this.value.asLong(); } @Override public short asShort() { - return _value.asShort(); + return this.value.asShort(); } @Override public String asString() { - return _value.asString(); + return this.value.asString(); } @Override public MCPlugin getOwningPlugin() { - return new BukkitMCPlugin(_value.getOwningPlugin()); + return new BukkitMCPlugin(this.value.getOwningPlugin()); } @Override public void invalidate() { - _value.invalidate(); + this.value.invalidate(); } @Override public Object value() { - return _value.value(); + return this.value.value(); } @Override public MetadataValue getHandle() { - return _value; + return this.value; } -} \ No newline at end of file +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCMetadatable.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCMetadatable.java index 50815bcd7d..61ad35b2aa 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCMetadatable.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCMetadatable.java @@ -10,56 +10,54 @@ public class BukkitMCMetadatable implements MCMetadatable { - private final Metadatable _metadatable; + private final Metadatable metadatable; public BukkitMCMetadatable(Metadatable metadatable) { - _metadatable = metadatable; + this.metadatable = metadatable; } @Override public List getMetadata(String metadataKey) { - List lst = _metadatable.getMetadata(metadataKey); + List lst = this.metadatable.getMetadata(metadataKey); List retn = new ArrayList<>(); - for(MetadataValue val : lst) { retn.add(new BukkitMCMetadataValue(val)); } - return retn; } @Override public boolean hasMetadata(String metadataKey) { - return _metadatable.hasMetadata(metadataKey); + return this.metadatable.hasMetadata(metadataKey); } @Override public void removeMetadata(String metadataKey, MCPlugin owningPlugin) { - _metadatable.removeMetadata(metadataKey, ((BukkitMCPlugin)owningPlugin).getHandle()); + this.metadatable.removeMetadata(metadataKey, ((BukkitMCPlugin) owningPlugin).getHandle()); } @Override public void setMetadata(String metadataKey, MCMetadataValue newMetadataValue) { - _metadatable.setMetadata(metadataKey, ((BukkitMCMetadataValue)newMetadataValue).getHandle()); + this.metadatable.setMetadata(metadataKey, ((BukkitMCMetadataValue) newMetadataValue).getHandle()); } @Override public Metadatable getHandle() { - return _metadatable; + return this.metadatable; } @Override public String toString() { - return _metadatable.toString(); + return this.metadatable.toString(); } @Override public boolean equals(Object obj) { - return (obj instanceof BukkitMCMetadatable?_metadatable.equals(((BukkitMCMetadatable)obj)._metadatable):false); + return obj instanceof BukkitMCMetadatable && this.metadatable.equals(((BukkitMCMetadatable) obj).metadatable); } @Override public int hashCode() { - return _metadatable.hashCode(); + return this.metadatable.hashCode(); } -} \ No newline at end of file +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCMusicInstrumentMeta.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCMusicInstrumentMeta.java new file mode 100644 index 0000000000..0fe2559bff --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCMusicInstrumentMeta.java @@ -0,0 +1,40 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCMusicInstrumentMeta; +import org.bukkit.MusicInstrument; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.meta.MusicInstrumentMeta; + +public class BukkitMCMusicInstrumentMeta extends BukkitMCItemMeta implements MCMusicInstrumentMeta { + + MusicInstrumentMeta mim; + + public BukkitMCMusicInstrumentMeta(MusicInstrumentMeta meta) { + super(meta); + this.mim = meta; + } + + @Override + public String getInstrument() { + MusicInstrument instrument = this.mim.getInstrument(); + if(instrument == null) { + return null; + } + return instrument.getKey().toString(); + } + + @Override + public void setInstrument(String instrument) { + if(instrument == null) { + this.mim.setInstrument(null); + } else { + NamespacedKey key = NamespacedKey.fromString(instrument); + if(key == null) { + this.mim.setInstrument(null); + } else { + MusicInstrument musicInstrument = MusicInstrument.getByKey(key); + this.mim.setInstrument(musicInstrument); + } + } + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCNamespacedKey.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCNamespacedKey.java new file mode 100644 index 0000000000..96db76225a --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCNamespacedKey.java @@ -0,0 +1,33 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCNamespacedKey; +import org.bukkit.NamespacedKey; + +public class BukkitMCNamespacedKey implements MCNamespacedKey { + + NamespacedKey nsk; + + public BukkitMCNamespacedKey(NamespacedKey nsk) { + this.nsk = nsk; + } + + @Override + public Object getHandle() { + return this.nsk; + } + + @Override + public String toString() { + return this.nsk.toString(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof MCNamespacedKey && this.nsk.equals(((MCNamespacedKey) obj).getHandle()); + } + + @Override + public int hashCode() { + return this.nsk.hashCode(); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCNote.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCNote.java index d27c102e0f..ed61ef6adc 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCNote.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCNote.java @@ -1,4 +1,3 @@ - package com.laytonsmith.abstraction.bukkit; import com.laytonsmith.abstraction.AbstractionObject; @@ -6,16 +5,33 @@ import com.laytonsmith.abstraction.enums.MCTone; import org.bukkit.Note; -/** - * - * - */ public class BukkitMCNote implements AbstractionObject, MCNote { + Note n; + public BukkitMCNote(int octave, MCTone tone, boolean sharp) throws IllegalArgumentException { n = new Note(octave, Note.Tone.valueOf(tone.name()), sharp); } + public BukkitMCNote(Note n) { + this.n = n; + } + + @Override + public MCTone getTone() { + return MCTone.valueOf(n.getTone().toString()); + } + + @Override + public int getOctave() { + return n.getOctave(); + } + + @Override + public boolean isSharped() { + return n.isSharped(); + } + @Override public Object getHandle() { return n; diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCObjective.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCObjective.java index eacf60508e..c9114c3618 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCObjective.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCObjective.java @@ -5,19 +5,20 @@ import com.laytonsmith.abstraction.MCScoreboard; import com.laytonsmith.abstraction.enums.MCDisplaySlot; import com.laytonsmith.abstraction.enums.bukkit.BukkitMCDisplaySlot; -import org.bukkit.OfflinePlayer; import org.bukkit.scoreboard.DisplaySlot; import org.bukkit.scoreboard.Objective; public class BukkitMCObjective implements MCObjective { Objective o; + public BukkitMCObjective(Objective obj) { o = obj; } @Override public String getCriteria() { + // deprecated 1.19.2 return o.getCriteria(); } @@ -29,7 +30,7 @@ public String getDisplayName() { @Override public MCDisplaySlot getDisplaySlot() { DisplaySlot ds = o.getDisplaySlot(); - if (ds == null) { + if(ds == null) { return null; } return BukkitMCDisplaySlot.getConvertor().getAbstractedEnum(ds); @@ -62,7 +63,11 @@ public void setDisplayName(String displayName) { @Override public void setDisplaySlot(MCDisplaySlot slot) { - o.setDisplaySlot(BukkitMCDisplaySlot.getConvertor().getConcreteEnum(slot)); + if(slot == null) { + o.setDisplaySlot(null); + } else { + o.setDisplaySlot(BukkitMCDisplaySlot.getConvertor().getConcreteEnum(slot)); + } } @Override diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCOfflinePlayer.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCOfflinePlayer.java index e92780a972..4752bcdc71 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCOfflinePlayer.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCOfflinePlayer.java @@ -1,62 +1,56 @@ - - package com.laytonsmith.abstraction.bukkit; import com.laytonsmith.abstraction.MCLocation; import com.laytonsmith.abstraction.MCOfflinePlayer; import com.laytonsmith.abstraction.MCPlayer; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCPlayer; +import org.bukkit.Location; import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; -/** - * - * - */ -public class BukkitMCOfflinePlayer extends BukkitMCAnimalTamer implements MCOfflinePlayer{ +import java.util.UUID; - OfflinePlayer op; - BukkitMCOfflinePlayer(OfflinePlayer offlinePlayer) { - super(offlinePlayer); - this.op = offlinePlayer; - } +public class BukkitMCOfflinePlayer extends BukkitMCAnimalTamer implements MCOfflinePlayer { - @Override - public boolean isOnline() { - return op.isOnline(); - } + OfflinePlayer op; + + public BukkitMCOfflinePlayer(OfflinePlayer offlinePlayer) { + super(offlinePlayer); + this.op = offlinePlayer; + } @Override - public String getName() { - return op.getName(); - } + public boolean isOnline() { + return op.isOnline(); + } @Override - public boolean isBanned() { - return op.isBanned(); - } + public String getName() { + return op.getName(); + } @Override - public void setBanned(boolean banned) { - op.setBanned(banned); - } + public boolean isBanned() { + return op.isBanned(); + } @Override - public boolean isWhitelisted() { - return op.isWhitelisted(); - } + public boolean isWhitelisted() { + return op.isWhitelisted(); + } @Override - public void setWhitelisted(boolean value) { - op.setWhitelisted(value); - } + public void setWhitelisted(boolean value) { + op.setWhitelisted(value); + } @Override - public MCPlayer getPlayer() { - if(op instanceof Player) { - return new BukkitMCPlayer(((Player)op)); - } - return null; - } + public MCPlayer getPlayer() { + if(op instanceof Player) { + return new BukkitMCPlayer(((Player) op)); + } + return null; + } @Override public long getFirstPlayed() { @@ -75,6 +69,15 @@ public boolean hasPlayedBefore() { @Override public MCLocation getBedSpawnLocation() { - return new BukkitMCLocation(op.getBedSpawnLocation()); - } + Location loc = op.getBedSpawnLocation(); + if(loc == null) { + return null; + } + return new BukkitMCLocation(loc); + } + + @Override + public UUID getUniqueID() { + return op.getUniqueId(); + } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCOminousBottleMeta.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCOminousBottleMeta.java new file mode 100644 index 0000000000..bfe5a6832d --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCOminousBottleMeta.java @@ -0,0 +1,29 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCOminousBottleMeta; +import org.bukkit.inventory.meta.OminousBottleMeta; + +public class BukkitMCOminousBottleMeta extends BukkitMCItemMeta implements MCOminousBottleMeta { + + OminousBottleMeta obm; + + public BukkitMCOminousBottleMeta(OminousBottleMeta im) { + super(im); + this.obm = im; + } + + @Override + public boolean hasAmplifier() { + return this.obm.hasAmplifier(); + } + + @Override + public int getAmplifier() { + return this.obm.getAmplifier(); + } + + @Override + public void setAmplifier(int value) { + this.obm.setAmplifier(value); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPattern.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPattern.java new file mode 100644 index 0000000000..d87b6e0fac --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPattern.java @@ -0,0 +1,48 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCPattern; +import com.laytonsmith.abstraction.enums.MCDyeColor; +import com.laytonsmith.abstraction.enums.MCPatternShape; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCDyeColor; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCPatternShape; +import org.bukkit.block.banner.Pattern; + +public class BukkitMCPattern implements MCPattern { + + Pattern pattern; + + public BukkitMCPattern(Pattern p) { + pattern = p; + } + + @Override + public Object getHandle() { + return pattern; + } + + @Override + public MCDyeColor getColor() { + return BukkitMCDyeColor.getConvertor().getAbstractedEnum(pattern.getColor()); + } + + @Override + public MCPatternShape getShape() { + return BukkitMCPatternShape.valueOfConcrete(pattern.getPattern()); + } + + @Override + public String toString() { + return pattern.toString(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof BukkitMCPattern && pattern.equals(((BukkitMCPattern) obj).pattern); + } + + @Override + public int hashCode() { + return pattern.hashCode(); + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPlayer.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPlayer.java deleted file mode 100644 index 763b56015a..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPlayer.java +++ /dev/null @@ -1,598 +0,0 @@ - - -package com.laytonsmith.abstraction.bukkit; - -import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery; -import com.laytonsmith.PureUtilities.Common.ReflectionUtils; -import com.laytonsmith.abstraction.MCCommandSender; -import com.laytonsmith.abstraction.MCEntity; -import com.laytonsmith.abstraction.MCItemStack; -import com.laytonsmith.abstraction.MCLocation; -import com.laytonsmith.abstraction.MCNote; -import com.laytonsmith.abstraction.MCOfflinePlayer; -import com.laytonsmith.abstraction.MCPlayer; -import com.laytonsmith.abstraction.MCPlayerInventory; -import com.laytonsmith.abstraction.MCScoreboard; -import com.laytonsmith.abstraction.StaticLayer; -import com.laytonsmith.abstraction.enums.MCInstrument; -import com.laytonsmith.abstraction.enums.MCSound; -import com.laytonsmith.abstraction.enums.MCWeather; -import com.laytonsmith.abstraction.enums.bukkit.BukkitMCInstrument; -import com.laytonsmith.abstraction.enums.bukkit.BukkitMCSound; -import com.laytonsmith.abstraction.enums.bukkit.BukkitMCWeather; -import com.laytonsmith.commandhelper.CommandHelperPlugin; -import com.laytonsmith.core.Static; -import com.laytonsmith.core.exceptions.ConfigRuntimeException; -import com.laytonsmith.core.functions.Exceptions; -import java.lang.reflect.InvocationTargetException; -import java.net.InetSocketAddress; -import java.util.Map; -import java.util.Set; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Note; -import org.bukkit.Server; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; - -/** - * - * - */ -public class BukkitMCPlayer extends BukkitMCHumanEntity implements MCPlayer, MCCommandSender, MCOfflinePlayer { - - Player p; - - public BukkitMCPlayer(Player player) { - super(player); - this.p = player; - } - - public Player _Player() { - return p; - } - - @Override - public boolean canSee(MCPlayer p) { - return this.p.canSee(((BukkitMCPlayer)p)._Player()); - } - - @Override - public void chat(String chat) { - p.chat(chat); - } - - @Override - public InetSocketAddress getAddress() { - return p.getAddress(); - } - - @Override - public boolean getAllowFlight() { - return p.getAllowFlight(); - } - - @Override - public MCLocation getCompassTarget() { - return new BukkitMCLocation(p.getCompassTarget()); - } - - @Override - public String getDisplayName() { - return p.getDisplayName(); - } - - @Override - public float getExp() { - return p.getExp(); - } - - @Override - public long getFirstPlayed() { - return p.getFirstPlayed(); - } - - @Override - public int getFoodLevel() { - return p.getFoodLevel(); - } - - @Override - public MCPlayerInventory getInventory() { - if (p == null || p.getInventory() == null) { - return null; - } - return new BukkitMCPlayerInventory(p.getInventory()); - } - - @Override - public MCItemStack getItemAt(Integer slot) { - if (slot == null) { - return new BukkitMCItemStack(p.getItemInHand()); - } - ItemStack is = null; - //Special slots - if (slot == 100) { - is = p.getInventory().getBoots(); - } else if (slot == 101) { - is = p.getInventory().getLeggings(); - } else if (slot == 102) { - is = p.getInventory().getChestplate(); - } else if (slot == 103) { - is = p.getInventory().getHelmet(); - } - if (slot >= 0 && slot <= 35) { - is = p.getInventory().getItem(slot); - } - if (is == null) { - return null; - } else { - return new BukkitMCItemStack(is); - } - } - - @Override - public long getLastPlayed() { - return p.getLastPlayed(); - } - - @Override - public int getLevel() { - return p.getLevel(); - } - - @Override - public MCPlayer getPlayer() { - return new BukkitMCPlayer(p); - } - - @Override - public long getPlayerTime() { - return p.getPlayerTime(); - } - - @Override - public MCWeather getPlayerWeather() { - return BukkitMCWeather.getConvertor().getAbstractedEnum(p.getPlayerWeather()); - } - - @Override - public int getRemainingFireTicks() { - return p.getFireTicks(); - } - -// public int getTotalExperience() { -// return p.getTotalExperience(); -// } - - // Method from Essentials plugin: - // https://raw.github.com/essentials/Essentials/master/Essentials/src/net/ess3/craftbukkit/SetExpFix.java - //This method is required because the bukkit player.getTotalExperience() method, shows exp that has been 'spent'. - //Without this people would be able to use exp and then still sell it. - @Override - public int getTotalExperience() - { - int exp = (int)Math.round(getExpAtLevel(p) * p.getExp()); - int currentLevel = p.getLevel(); - - while (currentLevel > 0) - { - currentLevel--; - exp += getExpAtLevel(currentLevel); - } - return exp; - } - - private static int getExpAtLevel(final Player player) - { - return getExpAtLevel(player.getLevel()); - } - - private static int getExpAtLevel(final int level) - { - if (level > 29) - { - return 62 + (level - 30) * 7; - } - if (level > 15) - { - return 17 + (level - 15) * 3; - } - return 17; - } - - @Override - public void setFlySpeed(float speed) { - p.setFlySpeed(speed); - } - - @Override - public float getFlySpeed() { - return p.getFlySpeed(); - } - - @Override - public void setWalkSpeed(float speed) { - p.setWalkSpeed(speed); - } - - @Override - public float getWalkSpeed() { - return p.getWalkSpeed(); - } - - @Override - public void giveExp(int xp) { - p.giveExp(xp); - } - - @Override - public boolean hasPlayedBefore() { - return p.hasPlayedBefore(); - } - - @Override - public boolean isBanned() { - return p.isBanned(); - } - - @Override - public boolean isOnline() { - return p.isOnline(); - } - - @Override - public boolean isOp() { - return p.isOp(); - } - - @Override - public void setOp(boolean bln) { - p.setOp(bln); - } - - @Override - public boolean isSneaking() { - return p.isSneaking(); - } - - @Override - public boolean isSprinting() { - return p.isSprinting(); - } - - @Override - public boolean isWhitelisted() { - return p.isWhitelisted(); - } - - @Override - public void kickPlayer(String message) { - p.kickPlayer(message); - } - - @Override - public boolean removeEffect(int potionID) { - PotionEffectType t = PotionEffectType.getById(potionID); - boolean hasIt = false; - for(PotionEffect pe : p.getActivePotionEffects()) { - if (pe.getType() == t) { - hasIt = true; - break; - } - } - p.removePotionEffect(t); - return hasIt; - } - - @Override - public void resetPlayerTime() { - p.resetPlayerTime(); - } - - @Override - public void resetPlayerWeather() { - p.resetPlayerWeather(); - } - - @Override - public void sendMessage(String string) { - //The client doesn't like tabs - string = string.replaceAll("\t", " "); - p.sendMessage(string); - } - - @Override - public void sendTexturePack(String url) { - p.setTexturePack(url); - } - - @Override - public void sendResourcePack(String url) { - p.setResourcePack(url); - } - - @Override - public void setAllowFlight(boolean flight) { - p.setAllowFlight(flight); - } - - @Override - public void setBanned(boolean banned) { - p.setBanned(banned); - } - - @Override - public void setCompassTarget(MCLocation l) { - p.setCompassTarget(((BukkitMCLocation)l).l); - } - - @Override - public void setDisplayName(String name) { - p.setDisplayName(name); - } - - @Override - public void setExp(float i) { - p.setExp(i); - } - - @Override - public void setFlying(boolean flight) { - p.setFlying(flight); - } - - @Override - public void setFoodLevel(int f) { - p.setFoodLevel(f); - } - - /*public void setHealth(int i) { - if(i == 0){ - this.fireEntityDamageEvent(MCDamageCause.CUSTOM); - } - p.setHealth(i); - }*/ - - @Override - public void setLevel(int xp) { - p.setLevel(xp); - } - - @Override - public void setPlayerTime(Long time, boolean relative) { - p.setPlayerTime(time, relative); - } - - @Override - public void setPlayerWeather(MCWeather type) { - p.setPlayerWeather(BukkitMCWeather.getConvertor().getConcreteEnum(type)); - } - - @Override - public void setRemainingFireTicks(int i) { - p.setFireTicks(i); - } - - @Override - public void setTempOp(Boolean value) throws ClassNotFoundException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { - Server server = Bukkit.getServer(); - - Class serverClass = ClassDiscovery.getDefaultInstance().forFuzzyName("org.bukkit.craftbukkit.*", "CraftServer").loadClass(); - - if (!server.getClass().isAssignableFrom(serverClass)) { - throw new IllegalStateException("Running server isn't CraftBukkit"); - } - - try { - Set opSet = null; - try{ - //Probably 1.4.5 - /*n.m.s.Server*/ Object nmsServer = ReflectionUtils.invokeMethod(server, "getServer"); - /*o.b.c.ServerConfigurationManagerAbstract*/ Object obcServerConfigurationmanagerAbstract = ReflectionUtils.invokeMethod(nmsServer, "getServerConfigurationManager"); - opSet = (Set) ReflectionUtils.get(ClassDiscovery.getDefaultInstance().forFuzzyName("net.minecraft.server.*", "ServerConfigurationManagerAbstract").loadClass(), obcServerConfigurationmanagerAbstract, "operators"); - } catch(ReflectionUtils.ReflectionException e){ - //Probably 1.4.6 - Class nmsMinecraftServerClass = ClassDiscovery.getDefaultInstance().forFuzzyName("net.minecraft.server.*", "MinecraftServer").loadClass(); - /*n.m.s.MinecraftServer*/ Object nmsServer = ReflectionUtils.invokeMethod(nmsMinecraftServerClass, null, "getServer"); - /*n.m.s.PlayerList*/ Object nmsPlayerList = ReflectionUtils.invokeMethod(nmsServer, "getPlayerList"); - opSet = (Set)ReflectionUtils.get(ClassDiscovery.getDefaultInstance().forFuzzyName("net.minecraft.server.*", "PlayerList").loadClass(), nmsPlayerList, "operators"); - } - - // since all Java objects pass by reference, we don't need to set field back to object - if (value) { - opSet.add(p.getName().toLowerCase()); - } else { - opSet.remove(p.getName().toLowerCase()); - } - } catch(ClassCastException ex){ - // Probably 1.7.8 - Class nmsMinecraftServerClass = ClassDiscovery.getDefaultInstance().forFuzzyName("net.minecraft.server.*", "MinecraftServer").loadClass(); - /*n.m.s.MinecraftServer*/ Object nmsServer = ReflectionUtils.invokeMethod(nmsMinecraftServerClass, null, "getServer"); - /*n.m.s.PlayerList*/ Object nmsPlayerList = ReflectionUtils.invokeMethod(nmsServer, "getPlayerList"); - /*n.m.s.OpList*/ Object opSet = ReflectionUtils.get(ClassDiscovery.getDefaultInstance().forFuzzyName("net.minecraft.server.*", "PlayerList").loadClass(), nmsPlayerList, "operators"); - //opSet.getClass().getSuperclass() == n.m.s.JsonList - Map/**/ d = (Map)ReflectionUtils.get(opSet.getClass().getSuperclass(), opSet, "d"); - if(value){ - /*n.m.s.OpListEntry*/ Class nmsOpListEntry = ClassDiscovery.getDefaultInstance().forFuzzyName("net.minecraft.server.*", "OpListEntry").loadClass(); - /*net.minecraft.util.com.mojang.authlib.GameProfile*/ Class nmucmaGameProfile = Class.forName("net.minecraft.util.com.mojang.authlib.GameProfile"); - Object gameProfile = ReflectionUtils.invokeMethod(p, "getProfile"); - Object opListEntry = ReflectionUtils.newInstance(nmsOpListEntry, new Class[]{nmucmaGameProfile, int.class}, new Object[]{gameProfile, 4}); - d.put(p.getUniqueId().toString(), opListEntry); - } else { - d.remove(p.getUniqueId().toString()); - } - } - p.recalculatePermissions(); - } - -// public void setTotalExperience(int total) { -// p.setTotalExperience(0); -// p.setLevel(0); -// p.setExp(0); -// p.giveExp(total); -// } - - // Method from Essentials plugin: - // https://raw.github.com/essentials/Essentials/master/Essentials/src/net/ess3/craftbukkit/SetExpFix.java - //This method is used to update both the recorded total experience and displayed total experience. - //We reset both types to prevent issues. - @Override - public void setTotalExperience(int total) - { - if (total < 0) - { - throw new ConfigRuntimeException("Experience can't be negative", Exceptions.ExceptionType.RangeException, null); - } - - p.setExp(0); - p.setLevel(0); - p.setTotalExperience(0); - - //This following code is technically redundant now, as bukkit now calulcates levels more or less correctly - //At larger numbers however... player.getExp(3000), only seems to give 2999, putting the below calculations off. - int amount = total; - while (amount > 0) - { - final int expToLevel = getExpAtLevel(p); - amount -= expToLevel; - if (amount >= 0) - { - // give until next level - p.giveExp(expToLevel); - } - else - { - // give the rest - amount += expToLevel; - p.giveExp(amount); - amount = 0; - } - } - } - - @Override - public void setVanished(boolean set, MCPlayer to) { - if (!set) { - p.showPlayer(((BukkitMCPlayer)to)._Player()); - } else { - p.hidePlayer(((BukkitMCPlayer)to)._Player()); - } - } - - @Override - public void setWhitelisted(boolean value) { - p.setWhitelisted(value); - } - - @Override - public void setPlayerListName(String listName) { - p.setPlayerListName(listName); - } - - @Override - public String getPlayerListName() { - return p.getPlayerListName(); - } - - @Override - public boolean isNewPlayer() { - //Note the reversed logic here. If they have NOT played before, they are - //a new player. - return !p.getServer().getOfflinePlayer(p.getName()).hasPlayedBefore(); - } - - @Override - public String getHost() { - return Static.GetHost(this); - } - - @Override - public void sendBlockChange(MCLocation loc, int material, byte data) { - p.sendBlockChange(((Location)loc.getHandle()), material, data); - } - - @Override - public void sendSignTextChange(MCLocation loc, String[] lines) { - p.sendSignChange(((Location)loc.getHandle()), lines); - } - - @Override - public void playNote(MCLocation loc, MCInstrument instrument, MCNote note) { - p.playNote((Location)loc.getHandle(), BukkitMCInstrument.getConvertor().getConcreteEnum(instrument), (Note)note.getHandle()); - } - - @Override - public void playSound(MCLocation l, MCSound sound, float volume, float pitch) { - p.playSound(((BukkitMCLocation) l).asLocation(), - BukkitMCSound.getConvertor().getConcreteEnum(sound), volume, pitch); - } - - @Override - public void playSound(MCLocation l, String sound, float volume, float pitch) { - p.playSound(((BukkitMCLocation)l).asLocation(), sound, volume, pitch); - } - - @Override - public int getHunger() { - return p.getFoodLevel(); - } - - @Override - public void setHunger(int h) { - p.setFoodLevel(h); - } - - @Override - public float getSaturation() { - return p.getSaturation(); - } - - @Override - public void setSaturation(float s) { - p.setSaturation(s); - } - - @Override - public MCLocation getBedSpawnLocation() { - return new BukkitMCLocation(p.getBedSpawnLocation()); - } - - @Override - public void setBedSpawnLocation(MCLocation l) { - p.setBedSpawnLocation((Location)l.getHandle(), true); - } - - @Override - public MCEntity getVehicle() { - return new BukkitMCEntity(p.getVehicle()); - } - - @Override - public void sendPluginMessage(String channel, byte[] message) { - StaticLayer.GetConvertor().GetPluginMeta().openOutgoingChannel(channel); - p.sendPluginMessage(CommandHelperPlugin.self, channel, message); - } - - @Override - public boolean isFlying() { - return p.isFlying(); - } - - @Override - public void updateInventory() { - p.updateInventory(); - } - - @Override - public MCScoreboard getScoreboard() { - return new BukkitMCScoreboard(p.getScoreboard()); - } - - @Override - public void setScoreboard(MCScoreboard board) { - p.setScoreboard(((BukkitMCScoreboard) board).s); - } -} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPlayerInput.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPlayerInput.java new file mode 100644 index 0000000000..a76819c0fe --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPlayerInput.java @@ -0,0 +1,48 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCPlayerInput; +import org.bukkit.Input; + +public class BukkitMCPlayerInput implements MCPlayerInput { + + private final Input input; + + public BukkitMCPlayerInput(Input input) { + this.input = input; + } + + @Override + public boolean forward() { + return this.input.isForward(); + } + + @Override + public boolean backward() { + return this.input.isBackward(); + } + + @Override + public boolean left() { + return this.input.isLeft(); + } + + @Override + public boolean right() { + return this.input.isRight(); + } + + @Override + public boolean jump() { + return this.input.isJump(); + } + + @Override + public boolean sneak() { + return this.input.isSneak(); + } + + @Override + public boolean sprint() { + return this.input.isSprint(); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPlayerInventory.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPlayerInventory.java index e2ec10642a..67d2eea93d 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPlayerInventory.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPlayerInventory.java @@ -1,91 +1,93 @@ - - package com.laytonsmith.abstraction.bukkit; import com.laytonsmith.abstraction.AbstractionObject; import com.laytonsmith.abstraction.MCItemStack; import com.laytonsmith.abstraction.MCPlayerInventory; -import org.bukkit.entity.Player; import org.bukkit.inventory.PlayerInventory; -/** - * - * - */ public class BukkitMCPlayerInventory extends BukkitMCInventory implements MCPlayerInventory { - private PlayerInventory i; - public BukkitMCPlayerInventory(PlayerInventory inventory) { + private PlayerInventory i; + + public BukkitMCPlayerInventory(PlayerInventory inventory) { super(inventory); - this.i = inventory; - } - - public BukkitMCPlayerInventory(AbstractionObject a){ - this((PlayerInventory)null); - if(a instanceof MCPlayerInventory){ - this.i = ((PlayerInventory)a.getHandle()); - } else { - throw new ClassCastException(); - } - } + this.i = inventory; + } - @Override - public void setHelmet(MCItemStack stack) { - this.i.setHelmet(((BukkitMCItemStack)stack).is); - if(this.i.getHolder() instanceof Player){ - ((Player)this.i.getHolder()).updateInventory(); + public BukkitMCPlayerInventory(AbstractionObject a) { + this((PlayerInventory) null); + if(a instanceof MCPlayerInventory) { + this.i = ((PlayerInventory) a.getHandle()); + } else { + throw new ClassCastException(); } - } + } @Override - public void setChestplate(MCItemStack stack) { - this.i.setChestplate(((BukkitMCItemStack)stack).is); - if(this.i.getHolder() instanceof Player){ - ((Player)this.i.getHolder()).updateInventory(); - } - } + public void setHelmet(MCItemStack stack) { + this.i.setHelmet(((BukkitMCItemStack) stack).__ItemStack()); + } @Override - public void setLeggings(MCItemStack stack) { - this.i.setLeggings(((BukkitMCItemStack)stack).is); - if(this.i.getHolder() instanceof Player){ - ((Player)this.i.getHolder()).updateInventory(); - } - } + public void setChestplate(MCItemStack stack) { + this.i.setChestplate(((BukkitMCItemStack) stack).__ItemStack()); + } @Override - public void setBoots(MCItemStack stack) { - this.i.setBoots(((BukkitMCItemStack)stack).is); - if(this.i.getHolder() instanceof Player){ - ((Player)this.i.getHolder()).updateInventory(); - } - } + public void setLeggings(MCItemStack stack) { + this.i.setLeggings(((BukkitMCItemStack) stack).__ItemStack()); + } + + @Override + public void setBoots(MCItemStack stack) { + this.i.setBoots(((BukkitMCItemStack) stack).__ItemStack()); + } + + @Override + public void setItemInMainHand(MCItemStack stack) { + this.i.setItemInMainHand(((BukkitMCItemStack) stack).__ItemStack()); + } + + @Override + public void setItemInOffHand(MCItemStack stack) { + this.i.setItemInOffHand(((BukkitMCItemStack) stack).__ItemStack()); + } + + @Override + public MCItemStack getHelmet() { + return new BukkitMCItemStack(this.i.getHelmet()); + } @Override - public MCItemStack getHelmet() { - return new BukkitMCItemStack(this.i.getHelmet()); - } + public MCItemStack getChestplate() { + return new BukkitMCItemStack(this.i.getChestplate()); + } + + @Override + public MCItemStack getLeggings() { + return new BukkitMCItemStack(this.i.getLeggings()); + } @Override - public MCItemStack getChestplate() { - return new BukkitMCItemStack(this.i.getChestplate()); - } + public MCItemStack getBoots() { + return new BukkitMCItemStack(this.i.getBoots()); + } @Override - public MCItemStack getLeggings() { - return new BukkitMCItemStack(this.i.getLeggings()); - } + public MCItemStack getItemInMainHand() { + return new BukkitMCItemStack(this.i.getItemInMainHand()); + } @Override - public MCItemStack getBoots() { - return new BukkitMCItemStack(this.i.getBoots()); - } + public MCItemStack getItemInOffHand() { + return new BukkitMCItemStack(this.i.getItemInOffHand()); + } @Override public int getHeldItemSlot() { return i.getHeldItemSlot(); } - + @Override public void setHeldItemSlot(int slot) { i.setHeldItemSlot(slot); diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPlayerProfile.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPlayerProfile.java new file mode 100644 index 0000000000..fa4ca2660a --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPlayerProfile.java @@ -0,0 +1,69 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.destroystokyo.paper.profile.PlayerProfile; +import com.destroystokyo.paper.profile.ProfileProperty; +import com.laytonsmith.abstraction.MCPlayerProfile; +import com.laytonsmith.abstraction.MCProfileProperty; + +import java.util.Set; +import java.util.UUID; + +public class BukkitMCPlayerProfile implements MCPlayerProfile { + + PlayerProfile pp; + + public BukkitMCPlayerProfile(Object pp) { + this.pp = (PlayerProfile) pp; + } + + @Override + public String getName() { + return this.pp.getName(); + } + + @Override + public UUID getId() { + return this.pp.getId(); + } + + @Override + public MCProfileProperty getProperty(String key) { + Set properties = this.pp.getProperties(); + for(ProfileProperty p : properties) { + if(p.getName().equals(key)) { + return new MCProfileProperty(p.getName(), p.getValue(), p.getSignature()); + } + } + return null; + } + + @Override + public void setProperty(MCProfileProperty property) { + this.pp.setProperty(new ProfileProperty(property.getName(), property.getValue(), property.getValue())); + } + + @Override + public boolean removeProperty(String key) { + return this.pp.removeProperty(key); + } + + @Override + public Object getHandle() { + return this.pp; + } + + @Override + public String toString() { + return this.pp.toString(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof BukkitMCPlayerProfile && this.pp.equals(((BukkitMCPlayerProfile) obj).pp); + } + + @Override + public int hashCode() { + return this.pp.hashCode(); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPlugin.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPlugin.java index de230535e0..5e1ec33aaa 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPlugin.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPlugin.java @@ -1,50 +1,44 @@ - - package com.laytonsmith.abstraction.bukkit; import com.laytonsmith.abstraction.AbstractionObject; import com.laytonsmith.abstraction.MCPlugin; import org.bukkit.plugin.Plugin; -/** - * - * - */ public class BukkitMCPlugin implements MCPlugin { - Plugin p; - public BukkitMCPlugin(Plugin plugin) { - this.p = plugin; - } - - public BukkitMCPlugin(AbstractionObject a){ - this((Plugin)null); - if(a instanceof MCPlugin){ - this.p = ((Plugin)a.getHandle()); - } else { - throw new ClassCastException(); - } - } - + Plugin p; + + public BukkitMCPlugin(Plugin plugin) { + this.p = plugin; + } + + public BukkitMCPlugin(AbstractionObject a) { + this((Plugin) null); + if(a instanceof MCPlugin) { + this.p = ((Plugin) a.getHandle()); + } else { + throw new ClassCastException(); + } + } + @Override - public Plugin getHandle(){ - return p; - } + public Plugin getHandle() { + return p; + } @Override - public boolean isEnabled() { - return p.isEnabled(); - } - + public boolean isEnabled() { + return p.isEnabled(); + } + @Override - public boolean isInstanceOf(Class c) { - if (c.isInstance(p)) { - return true; - } - - return false; - } - + public boolean isInstanceOf(Class c) { + if(c.isInstance(p)) { + return true; + } + return false; + } + @Override public String toString() { return p.toString(); @@ -52,7 +46,7 @@ public String toString() { @Override public boolean equals(Object obj) { - return (obj instanceof BukkitMCPlugin?p.equals(((BukkitMCPlugin)obj).p):false); + return obj instanceof BukkitMCPlugin && p.equals(((BukkitMCPlugin) obj).p); } @Override @@ -64,5 +58,5 @@ public int hashCode() { public String getName() { return p.getName(); } - + } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPluginManager.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPluginManager.java index 9829042928..313dc1d4f1 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPluginManager.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPluginManager.java @@ -1,5 +1,3 @@ - - package com.laytonsmith.abstraction.bukkit; import com.laytonsmith.abstraction.AbstractionObject; @@ -10,42 +8,39 @@ import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; -/** - * - * - */ public class BukkitMCPluginManager implements MCPluginManager { - PluginManager p; - public BukkitMCPluginManager(PluginManager pluginManager) { - this.p = pluginManager; - } - - public BukkitMCPluginManager(AbstractionObject a){ - this((PluginManager)null); - if(a instanceof MCPluginManager){ - this.p = ((PluginManager)a.getHandle()); - } else { - throw new ClassCastException(); - } - } - + PluginManager p; + + public BukkitMCPluginManager(PluginManager pluginManager) { + this.p = pluginManager; + } + + public BukkitMCPluginManager(AbstractionObject a) { + this((PluginManager) null); + if(a instanceof MCPluginManager) { + this.p = ((PluginManager) a.getHandle()); + } else { + throw new ClassCastException(); + } + } + @Override - public Object getHandle(){ - return p; - } + public Object getHandle() { + return p; + } @Override - public MCPlugin getPlugin(String name) { - if(p.getPlugin(name) == null){ - return null; - } - return new BukkitMCPlugin(p.getPlugin(name)); - } - - public PluginManager __PluginManager(){ - return p; - } + public MCPlugin getPlugin(String name) { + if(p.getPlugin(name) == null) { + return null; + } + return new BukkitMCPlugin(p.getPlugin(name)); + } + + public PluginManager __PluginManager() { + return p; + } @Override public String toString() { @@ -54,7 +49,7 @@ public String toString() { @Override public boolean equals(Object obj) { - return (obj instanceof BukkitMCPluginManager?p.equals(((BukkitMCPluginManager)obj).p):false); + return obj instanceof BukkitMCPluginManager && p.equals(((BukkitMCPluginManager) obj).p); } @Override @@ -64,14 +59,12 @@ public int hashCode() { @Override public List getPlugins() { - List retn = new ArrayList(); + List retn = new ArrayList<>(); Plugin[] plugs = p.getPlugins(); - - for (Plugin plug : plugs) { + for(Plugin plug : plugs) { retn.add(new BukkitMCPlugin(plug)); } - return retn; } - + } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPluginMeta.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPluginMeta.java index dac7e488b1..23ec44d8a6 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPluginMeta.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPluginMeta.java @@ -2,23 +2,21 @@ import com.laytonsmith.abstraction.MCPlayer; import com.laytonsmith.abstraction.MCPluginMeta; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCPlayer; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.messaging.PluginMessageListener; -/** - * - * - */ public class BukkitMCPluginMeta extends MCPluginMeta implements PluginMessageListener { Plugin plugin; - public BukkitMCPluginMeta(Plugin plugin){ + + public BukkitMCPluginMeta(Plugin plugin) { super(); this.plugin = plugin; } - + @Override public void closeOutgoingChannel0(String channel) { Bukkit.getMessenger().unregisterOutgoingPluginChannel(plugin, channel); @@ -31,7 +29,7 @@ public void openOutgoingChannel0(String channel) { @Override protected void sendIncomingMessage0(MCPlayer player, String channel, byte[] message) { - Player p = ((BukkitMCPlayer)player)._Player(); + Player p = ((BukkitMCPlayer) player)._Player(); Bukkit.getMessenger().dispatchIncomingMessage(p, channel, message); } @@ -49,5 +47,4 @@ public void openIncomingChannel0(String channel) { public void onPluginMessageReceived(String channel, Player player, byte[] message) { triggerOnMessage(new BukkitMCPlayer(player), channel, message); } - } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPotionData.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPotionData.java new file mode 100644 index 0000000000..7656cd5260 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPotionData.java @@ -0,0 +1,50 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.PureUtilities.Common.ReflectionUtils; +import com.laytonsmith.abstraction.MCPotionData; +import com.laytonsmith.abstraction.enums.MCPotionType; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCPotionType; + +public class BukkitMCPotionData implements MCPotionData { + + Object pd; + + public BukkitMCPotionData(Object poda) { + pd = poda; + } + + @Override + public MCPotionType getType() { + return BukkitMCPotionType.valueOfConcrete(ReflectionUtils.invokeMethod(pd, "getType")); + } + + @Override + public boolean isExtended() { + return ReflectionUtils.invokeMethod(pd, "isExtended"); + } + + @Override + public boolean isUpgraded() { + return ReflectionUtils.invokeMethod(pd, "isUpgraded"); + } + + @Override + public Object getHandle() { + return pd; + } + + @Override + public boolean equals(Object obj) { + return pd.equals(obj); + } + + @Override + public int hashCode() { + return pd.hashCode(); + } + + @Override + public String toString() { + return pd.toString(); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPotionMeta.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPotionMeta.java index 1f791d635e..34d06b0f74 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPotionMeta.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPotionMeta.java @@ -1,34 +1,73 @@ package com.laytonsmith.abstraction.bukkit; +import com.laytonsmith.PureUtilities.Common.ReflectionUtils; +import com.laytonsmith.abstraction.MCColor; import com.laytonsmith.abstraction.MCLivingEntity.MCEffect; +import com.laytonsmith.abstraction.MCPotionData; import com.laytonsmith.abstraction.MCPotionMeta; +import com.laytonsmith.abstraction.enums.MCPotionEffectType; +import com.laytonsmith.abstraction.enums.MCPotionType; +import com.laytonsmith.abstraction.enums.MCVersion; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCPotionEffectType; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCPotionType; import com.laytonsmith.core.Static; import com.laytonsmith.core.constructs.Target; -import com.laytonsmith.core.exceptions.ConfigRuntimeException; -import com.laytonsmith.core.functions.Exceptions.ExceptionType; -import java.util.ArrayList; -import java.util.List; import org.bukkit.inventory.meta.PotionMeta; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; +import org.bukkit.potion.PotionType; + +import java.util.ArrayList; +import java.util.List; public class BukkitMCPotionMeta extends BukkitMCItemMeta implements MCPotionMeta { PotionMeta pm; + public BukkitMCPotionMeta(PotionMeta pomet) { super(pomet); pm = pomet; } - + + @Override + public MCPotionData getBasePotionData() { + return new BukkitMCPotionData(ReflectionUtils.invokeMethod(PotionMeta.class, pm, "getBasePotionData")); + } + + @Override + public void setBasePotionData(MCPotionData bpd) { + ReflectionUtils.invokeMethod(pm, "setBasePotionData", bpd.getHandle()); + } + + @Override + public MCPotionType getBasePotionType() { + PotionType type = pm.getBasePotionType(); + if(type == null) { + return null; + } + return BukkitMCPotionType.valueOfConcrete(pm.getBasePotionType()); + } + @Override - public boolean addCustomEffect(int potionID, int strength, int seconds, boolean ambient, boolean overwrite, Target t) { - int maxID = PotionEffectType.values().length; - if (potionID < 1 || potionID > maxID) { - throw new ConfigRuntimeException("Invalid effect ID, must be from 1-" + maxID, ExceptionType.RangeException, t); + public void setBasePotionType(MCPotionType pt) { + if(pt == null) { + pm.setBasePotionType(null); + } else { + pm.setBasePotionType((PotionType) pt.getConcrete()); } - PotionEffect pe = new PotionEffect(PotionEffectType.getById(potionID), - (int) Static.msToTicks(seconds * 1000), strength, ambient); - return pm.addCustomEffect(pe, overwrite); + } + + @Override + public boolean addCustomEffect(MCPotionEffectType type, int strength, int ticks, boolean ambient, boolean particles, boolean icon, boolean force, Target t) { + if(ticks < 0) { + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_19_4)) { + ticks = -1; + } else { + ticks = Integer.MAX_VALUE; + } + } + PotionEffect pe = new PotionEffect((PotionEffectType) type.getConcrete(), ticks, strength, ambient, particles, icon); + return pm.addCustomEffect(pe, force); } @Override @@ -38,17 +77,17 @@ public boolean clearCustomEffects() { @Override public List getCustomEffects() { - List list = new ArrayList(); - for (PotionEffect pe : pm.getCustomEffects()) { - list.add(new MCEffect(pe.getType().getId(), pe.getAmplifier(), - (int)(Static.ticksToMs(pe.getDuration()) / 1000), pe.isAmbient())); + List list = new ArrayList<>(); + for(PotionEffect pe : pm.getCustomEffects()) { + list.add(new MCEffect(BukkitMCPotionEffectType.valueOfConcrete(pe.getType()), pe.getAmplifier(), + pe.getDuration(), pe.isAmbient(), pe.hasParticles(), pe.hasIcon())); } return list; } @Override - public boolean hasCustomEffect(int id) { - return pm.hasCustomEffect(PotionEffectType.getById(id)); + public boolean hasCustomEffect(MCPotionEffectType type) { + return pm.hasCustomEffect((PotionEffectType) type.getConcrete()); } @Override @@ -57,13 +96,20 @@ public boolean hasCustomEffects() { } @Override - public boolean removeCustomEffect(int id) { - return pm.removeCustomEffect(PotionEffectType.getById(id)); + public boolean removeCustomEffect(MCPotionEffectType type) { + return pm.removeCustomEffect((PotionEffectType) type.getConcrete()); } - @Override - public boolean setMainEffect(int id) { - return pm.setMainEffect(PotionEffectType.getById(id)); + public boolean hasColor() { + return pm.hasColor(); + } + + public MCColor getColor() { + return BukkitMCColor.GetMCColor(pm.getColor()); } + @Override + public void setColor(MCColor color) { + pm.setColor(BukkitMCColor.GetColor(color)); + } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCProjectile.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCProjectile.java deleted file mode 100644 index 60978f3da8..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCProjectile.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.laytonsmith.abstraction.bukkit; - -import com.laytonsmith.abstraction.MCEntity; -import com.laytonsmith.abstraction.MCProjectile; -import com.laytonsmith.abstraction.MCProjectileSource; -import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCBlockProjectileSource; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Projectile; -import org.bukkit.projectiles.BlockProjectileSource; -import org.bukkit.projectiles.ProjectileSource; - -public class BukkitMCProjectile extends BukkitMCEntity implements MCProjectile { - - Projectile proj; - public BukkitMCProjectile(Projectile e) { - super(e); - this.proj = e; - } - - @Override - public boolean doesBounce() { - return proj.doesBounce(); - } - - @Override - public MCProjectileSource getShooter() { - ProjectileSource source = proj.getShooter(); - - if (source instanceof BlockProjectileSource) { - return new BukkitMCBlockProjectileSource((BlockProjectileSource) source); - } - - if (source instanceof Entity) { - MCEntity e = BukkitConvertor.BukkitGetCorrectEntity((Entity) source); - if (e instanceof MCProjectileSource) { - return (MCProjectileSource) e; - } - } - - return null; - } - - @Override - public void setBounce(boolean doesBounce) { - proj.setBounce(doesBounce); - } - - @Override - public void setShooter(MCProjectileSource shooter) { - proj.setShooter((ProjectileSource) shooter.getHandle()); - } - - public Projectile asProjectile() { - return proj; - } -} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCRecipe.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCRecipe.java index 86f8dd1dcc..823889821e 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCRecipe.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCRecipe.java @@ -2,32 +2,29 @@ import com.laytonsmith.abstraction.MCItemStack; import com.laytonsmith.abstraction.MCRecipe; -import com.laytonsmith.abstraction.enums.MCRecipeType; import org.bukkit.inventory.Recipe; public abstract class BukkitMCRecipe implements MCRecipe { - Recipe r; + private final Recipe r; + protected BukkitMCRecipe(Recipe rec) { r = rec; } - + @Override public Object getHandle() { return r; } - + @Override public MCItemStack getResult() { return new BukkitMCItemStack(r.getResult()); } - - @Override - public abstract MCRecipeType getRecipeType(); @Override public boolean equals(Object obj) { - return r.equals(obj); + return obj instanceof BukkitMCRecipe && r.equals(((BukkitMCRecipe) obj).r); } @Override @@ -39,5 +36,5 @@ public int hashCode() { public String toString() { return r.toString(); } - + } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCRemoteConsoleCommandSender.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCRemoteConsoleCommandSender.java new file mode 100644 index 0000000000..faa9090646 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCRemoteConsoleCommandSender.java @@ -0,0 +1,17 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCRemoteCommandSender; +import org.bukkit.command.RemoteConsoleCommandSender; + +/** + * + */ +public class BukkitMCRemoteConsoleCommandSender extends BukkitMCCommandSender implements MCRemoteCommandSender { + + RemoteConsoleCommandSender ccs; + + public BukkitMCRemoteConsoleCommandSender(RemoteConsoleCommandSender ccs) { + super(ccs); + this.ccs = ccs; + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCScore.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCScore.java index 9b4990d00f..ed9d98afbd 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCScore.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCScore.java @@ -1,7 +1,6 @@ package com.laytonsmith.abstraction.bukkit; import com.laytonsmith.abstraction.MCObjective; -import com.laytonsmith.abstraction.MCOfflinePlayer; import com.laytonsmith.abstraction.MCScore; import com.laytonsmith.abstraction.MCScoreboard; import org.bukkit.scoreboard.Score; @@ -9,6 +8,7 @@ public class BukkitMCScore implements MCScore { Score s; + public BukkitMCScore(Score score) { s = score; } @@ -18,11 +18,6 @@ public MCObjective getObjective() { return new BukkitMCObjective(s.getObjective()); } - @Override - public MCOfflinePlayer getPlayer() { - return new BukkitMCOfflinePlayer(s.getPlayer()); - } - @Override public int getScore() { return s.getScore(); diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCScoreboard.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCScoreboard.java index 6d572129dc..3d3c5287fe 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCScoreboard.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCScoreboard.java @@ -1,27 +1,31 @@ package com.laytonsmith.abstraction.bukkit; import com.laytonsmith.abstraction.MCObjective; -import com.laytonsmith.abstraction.MCOfflinePlayer; import com.laytonsmith.abstraction.MCScore; import com.laytonsmith.abstraction.MCScoreboard; import com.laytonsmith.abstraction.MCTeam; import com.laytonsmith.abstraction.enums.MCDisplaySlot; import com.laytonsmith.abstraction.enums.bukkit.BukkitMCDisplaySlot; -import java.util.HashSet; -import java.util.Set; -import org.bukkit.OfflinePlayer; import org.bukkit.scoreboard.Objective; import org.bukkit.scoreboard.Score; import org.bukkit.scoreboard.Scoreboard; import org.bukkit.scoreboard.Team; +import java.util.HashSet; +import java.util.Set; + public class BukkitMCScoreboard implements MCScoreboard { Scoreboard s; + public BukkitMCScoreboard(Scoreboard sb) { s = sb; } + public Scoreboard _scoreboard() { + return s; + } + @Override public void clearSlot(MCDisplaySlot slot) { s.clearSlot(BukkitMCDisplaySlot.getConvertor().getConcreteEnum(slot)); @@ -30,7 +34,7 @@ public void clearSlot(MCDisplaySlot slot) { @Override public MCObjective getObjective(MCDisplaySlot slot) { Objective o = s.getObjective(BukkitMCDisplaySlot.getConvertor().getConcreteEnum(slot)); - if (o == null) { + if(o == null) { return null; } return new BukkitMCObjective(o); @@ -39,7 +43,7 @@ public MCObjective getObjective(MCDisplaySlot slot) { @Override public MCObjective getObjective(String name) { Objective o = s.getObjective(name); - if (o == null) { + if(o == null) { return null; } return new BukkitMCObjective(o); @@ -47,8 +51,8 @@ public MCObjective getObjective(String name) { @Override public Set getObjectives() { - Set ret = new HashSet(); - for (Objective o : s.getObjectives()) { + Set ret = new HashSet<>(); + for(Objective o : s.getObjectives()) { ret.add(new BukkitMCObjective(o)); } return ret; @@ -56,8 +60,9 @@ public Set getObjectives() { @Override public Set getObjectivesByCriteria(String criteria) { - Set ret = new HashSet(); - for (Objective o : s.getObjectivesByCriteria(criteria)) { + Set ret = new HashSet<>(); + // deprecated 1.19.2 + for(Objective o : s.getObjectivesByCriteria(criteria)) { ret.add(new BukkitMCObjective(o)); } return ret; @@ -69,8 +74,8 @@ public Set getEntries() { } @Override - public MCTeam getPlayerTeam(MCOfflinePlayer player) { - Team t = s.getPlayerTeam((OfflinePlayer) player.getHandle()); + public MCTeam getPlayerTeam(String entry) { + Team t = s.getEntryTeam(entry); if(t == null) { return null; } @@ -79,8 +84,8 @@ public MCTeam getPlayerTeam(MCOfflinePlayer player) { @Override public Set getScores(String entry) { - Set ret = new HashSet(); - for (Score o : s.getScores(entry)) { + Set ret = new HashSet<>(); + for(Score o : s.getScores(entry)) { ret.add(new BukkitMCScore(o)); } return ret; @@ -97,8 +102,8 @@ public MCTeam getTeam(String teamName) { @Override public Set getTeams() { - Set ret = new HashSet(); - for (Team t : s.getTeams()) { + Set ret = new HashSet<>(); + for(Team t : s.getTeams()) { ret.add(new BukkitMCTeam(t)); } return ret; @@ -106,6 +111,7 @@ public Set getTeams() { @Override public MCObjective registerNewObjective(String name, String criteria) { + // deprecated 1.19.2 return new BukkitMCObjective(s.registerNewObjective(name, criteria)); } @@ -121,10 +127,7 @@ public void resetScores(String entry) { @Override public boolean equals(Object obj) { - if (obj instanceof MCScoreboard) { - return s.equals(((BukkitMCScoreboard) obj).s); - } - return false; + return obj instanceof MCScoreboard && s.equals(((BukkitMCScoreboard) obj).s); } @Override diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCServer.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCServer.java index 00cf21a693..b5abd45622 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCServer.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCServer.java @@ -1,119 +1,146 @@ package com.laytonsmith.abstraction.bukkit; import com.laytonsmith.PureUtilities.Common.ReflectionUtils; +import com.laytonsmith.abstraction.MCBossBar; import com.laytonsmith.abstraction.MCCommandMap; import com.laytonsmith.abstraction.MCCommandSender; import com.laytonsmith.abstraction.MCConsoleCommandSender; -import com.laytonsmith.abstraction.MCHumanEntity; +import com.laytonsmith.abstraction.MCEntity; import com.laytonsmith.abstraction.MCInventory; import com.laytonsmith.abstraction.MCInventoryHolder; import com.laytonsmith.abstraction.MCItemFactory; import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.MCMerchant; import com.laytonsmith.abstraction.MCOfflinePlayer; import com.laytonsmith.abstraction.MCPlayer; +import com.laytonsmith.abstraction.MCPlayerProfile; import com.laytonsmith.abstraction.MCPluginManager; import com.laytonsmith.abstraction.MCRecipe; import com.laytonsmith.abstraction.MCScoreboard; import com.laytonsmith.abstraction.MCServer; import com.laytonsmith.abstraction.MCWorld; +import com.laytonsmith.abstraction.MCWorldBorder; +import com.laytonsmith.abstraction.blocks.MCBlockData; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCBlockData; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCPlayer; import com.laytonsmith.abstraction.bukkit.pluginmessages.BukkitMCMessenger; +import com.laytonsmith.abstraction.enums.MCBarColor; +import com.laytonsmith.abstraction.enums.MCBarStyle; import com.laytonsmith.abstraction.enums.MCInventoryType; +import com.laytonsmith.abstraction.enums.MCVersion; import com.laytonsmith.abstraction.pluginmessages.MCMessenger; import com.laytonsmith.core.Static; -import com.laytonsmith.core.constructs.Target; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; -import net.milkbowl.vault.economy.Economy; +import org.bukkit.BanList; import org.bukkit.Bukkit; +import org.bukkit.NamespacedKey; import org.bukkit.OfflinePlayer; import org.bukkit.Server; import org.bukkit.World; +import org.bukkit.boss.BarColor; +import org.bukkit.boss.BarStyle; import org.bukkit.command.CommandSender; import org.bukkit.command.ConsoleCommandSender; import org.bukkit.command.SimpleCommandMap; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryType; +import org.bukkit.event.server.BroadcastMessageEvent; import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.Recipe; -import org.bukkit.plugin.RegisteredServiceProvider; -/** - * - * - */ +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + public class BukkitMCServer implements MCServer { - Server s; - public BukkitMCServer(){ - this.s = Bukkit.getServer(); - } + private final Server s; + private MCVersion version; + private String craftBukkitPackage; + private boolean isPaper = false; + + public BukkitMCServer() { + this.s = Bukkit.getServer(); + craftBukkitPackage = this.s.getClass().getPackage().getName(); + try { + Class.forName("com.destroystokyo.paper.PaperConfig"); + this.isPaper = true; + } catch (ClassNotFoundException e) {} + } - public BukkitMCServer(Server server) { + public BukkitMCServer(Server server) { this.s = server; } @Override - public Object getHandle(){ - return s; - } + public Object getHandle() { + return s; + } + + public Server __Server() { + return s; + } + + public boolean isPaper() { + return this.isPaper; + } - public Server __Server(){ - return s; - } + public String getCraftBukkitPackage() { + return this.craftBukkitPackage; + } @Override - public String getName() { - return s.getName(); - } + public String getName() { + return s.getName(); + } @Override - public Collection getOnlinePlayers() { - if(s.getOnlinePlayers() == null){ - return null; - } - Collection pa = s.getOnlinePlayers(); + public Collection getOnlinePlayers() { + Collection players = s.getOnlinePlayers(); Set mcpa = new HashSet<>(); - for(Player p : pa){ - mcpa.add(new BukkitMCPlayer(p)); - } - return mcpa; - } + for(Player p : players) { + mcpa.add(new BukkitMCPlayer(p)); + } + return mcpa; + } - public static MCServer Get() { - return new BukkitMCServer(); - } + public static MCServer Get() { + return new BukkitMCServer(); + } @Override - public boolean dispatchCommand(MCCommandSender sender, String command){ + public boolean dispatchCommand(MCCommandSender sender, String command) { CommandSender cs; - if(sender instanceof BukkitMCPlayer){ - cs = ((BukkitMCPlayer)sender).p; + if(sender instanceof MCPlayer) { + cs = (Player) sender.getHandle(); } else { - cs = ((BukkitMCCommandSender)sender).c; + cs = (CommandSender) sender.getHandle(); } - return s.dispatchCommand(cs, command); - } + return s.dispatchCommand(cs, command); + } private class CommandSenderInterceptor implements InvocationHandler { + private final StringBuilder buffer; private final CommandSender sender; - public CommandSenderInterceptor(CommandSender sender){ + public CommandSenderInterceptor(CommandSender sender) { this.buffer = new StringBuilder(); this.sender = sender; } @Override public Object invoke(Object o, Method method, Object[] args) throws Throwable { - if ("sendMessage".equals(method.getName())) { + if("sendMessage".equals(method.getName())) { buffer.append(args[0].toString()); return Void.TYPE; } else { @@ -121,7 +148,7 @@ public Object invoke(Object o, Method method, Object[] args) throws Throwable { } } - public String getBuffer(){ + public String getBuffer() { return buffer.toString(); } } @@ -129,13 +156,13 @@ public String getBuffer(){ @Override public String dispatchAndCaptureCommand(MCCommandSender commandSender, String cmd) { // Grab the CommandSender object from the abstraction layer - CommandSender sender = (CommandSender)commandSender.getHandle(); + CommandSender sender = (CommandSender) commandSender.getHandle(); // Create the interceptor CommandSenderInterceptor interceptor = new CommandSenderInterceptor(sender); // Create a new proxy and abstraction layer wrapper around the proxy - CommandSender newCommandSender = (CommandSender)Proxy.newProxyInstance(BukkitMCServer.class.getClassLoader(), new Class[]{CommandSender.class}, interceptor); + CommandSender newCommandSender = (CommandSender) Proxy.newProxyInstance(BukkitMCServer.class.getClassLoader(), new Class[]{CommandSender.class}, interceptor); BukkitMCCommandSender aCommandSender = new BukkitMCCommandSender(newCommandSender); MCCommandSender oldSender = Static.UninjectPlayer(commandSender); @@ -143,12 +170,14 @@ public String dispatchAndCaptureCommand(MCCommandSender commandSender, String cm Static.InjectPlayer(aCommandSender); // Dispatch the command now - s.dispatchCommand(newCommandSender, cmd); - - // Clean up - Static.UninjectPlayer(aCommandSender); - if(oldSender != null){ - Static.InjectPlayer(oldSender); + try { + s.dispatchCommand(newCommandSender, cmd); + } finally { + // Clean up + Static.UninjectPlayer(aCommandSender); + if(oldSender != null) { + Static.InjectPlayer(oldSender); + } } // Return the buffered text (if any) @@ -156,49 +185,151 @@ public String dispatchAndCaptureCommand(MCCommandSender commandSender, String cm } @Override - public MCPluginManager getPluginManager() { - if(s.getPluginManager() == null){ - return null; - } - return new BukkitMCPluginManager(s.getPluginManager()); - } + public MCPluginManager getPluginManager() { + return new BukkitMCPluginManager(s.getPluginManager()); + } + + /** + * This player Name-to-UUID fallback is used to work around an issue in Minecraft/Spigot where players are + * incorrectly removed from the by-name map in PlayerList (and the list of online players) but not the UUID keyed + * map (which is used to determine if they're considered online by the Bukkit API). + * This occurs during the respawn event or when a player is teleported between worlds during the quit event. + * Versions of Spigot up to at least 1.20.4 are known to be affected by this issue. + * This map should only be updated after the login and quit events in BukkitPlayerListener. + */ + public final Map playerUUIDsByName = new HashMap<>(); + + @Override + public MCPlayer getPlayerExact(String name) { + Player p = s.getPlayerExact(name); + if(p == null) { + UUID id = playerUUIDsByName.get(name); + if(id == null) { + return null; + } + return getPlayer(id); + } + return new BukkitMCPlayer(p); + } + + @Override + public MCPlayer getPlayer(String name) { + Player p = s.getPlayer(name); + if(p == null) { + UUID id = playerUUIDsByName.get(name); + if(id == null) { + return null; + } + return getPlayer(id); + } + return new BukkitMCPlayer(p); + } @Override - public MCPlayer getPlayer(String name) { - if(s.getPlayer(name) == null){ - return null; - } - return new BukkitMCPlayer(s.getPlayer(name)); - } + public MCPlayer getPlayer(UUID uuid) { + Player p = s.getPlayer(uuid); + if(p == null) { + return null; + } + return new BukkitMCPlayer(p); + } @Override - public MCWorld getWorld(String name) { - if(s.getWorld(name) == null){ - return null; - } - return new BukkitMCWorld(s.getWorld(name)); - } + public MCEntity getEntity(UUID uuid) { + return BukkitConvertor.BukkitGetCorrectEntity(s.getEntity(uuid)); + } @Override - public List getWorlds(){ - if(s.getWorlds() == null){ - return null; - } - List list = new ArrayList(); - for(World w : s.getWorlds()){ - list.add(new BukkitMCWorld(w)); - } - return list; - } + public MCWorld getWorld(String name) { + World w = s.getWorld(name); + if(w == null) { + return null; + } + return new BukkitMCWorld(w); + } @Override - public void broadcastMessage(String message) { - s.broadcastMessage(message); - } + public List getWorlds() { + List list = new ArrayList<>(); + for(World w : s.getWorlds()) { + list.add(new BukkitMCWorld(w)); + } + return list; + } + + @Override + public void broadcastMessage(String message) { + + // Get the set of online players and include console. + Set recipients = new HashSet<>(this.s.getOnlinePlayers()); + recipients.add(this.s.getConsoleSender()); + + // Perform the broadcast. + this.bukkitBroadcastMessage(message, recipients); + } @Override public void broadcastMessage(String message, String permission) { - s.broadcast(message, permission); + + // Get the set of online players with the given permission and include console. + Set recipients = new HashSet<>(); + for(Player player : this.s.getOnlinePlayers()) { + if(player.hasPermission(permission)) { + recipients.add(player); + } + } + recipients.add(this.s.getConsoleSender()); + + // Perform the broadcast. + this.bukkitBroadcastMessage(message, recipients); + } + + @Override + public void broadcastMessage(String message, Set recipients) { + + // Convert MCCommandsSender recipients to CommandSender recipients. + Set bukkitRecipients = new HashSet<>(); + if(recipients != null) { + for(MCCommandSender recipient : recipients) { + bukkitRecipients.add((CommandSender) recipient.getHandle()); + } + } + + // Perform the broadcast. + this.bukkitBroadcastMessage(message, bukkitRecipients); + } + + /** + * Broadcasts a message to a list of recipients, firing a {@link BroadcastMessageEvent} before doing so. + * {@link ConsoleCommandSender Console} has to be included in this list to receive the broadcast. + * @param message - The message to broadcast. + * @param recipients - A list of {@link MCCommandSender command senders} to send the message to. + * @return The amount of recipients that received the message. + */ + @SuppressWarnings("deprecation") + private int bukkitBroadcastMessage(String message, Set recipients) { + + // Fire a BroadcastMessageEvent for this broadcast. + BroadcastMessageEvent broadcastMessageEvent = new BroadcastMessageEvent(!Bukkit.isPrimaryThread(), message, + recipients); + this.s.getPluginManager().callEvent(broadcastMessageEvent); + + // Return if the event was cancelled. + if(broadcastMessageEvent.isCancelled()) { + return 0; + } + + // Get the possibly modified message and recipients. + message = broadcastMessageEvent.getMessage(); + recipients = broadcastMessageEvent.getRecipients(); // This returns the same reference, but breaks less likely. + + // Perform the actual broadcast to all remaining recipients. + for(CommandSender recipient : recipients) { + recipient.sendMessage(message); + } + + // Return the amount of recipients that received the message. + return recipients.size(); } @Override @@ -217,130 +348,141 @@ public MCCommandMap getCommandMap() { } @Override - public MCOfflinePlayer getOfflinePlayer(String player) { - return new BukkitMCOfflinePlayer(s.getOfflinePlayer(player)); - } + public MCPlayerProfile getPlayerProfile(UUID id, String name) { + if(isPaper()) { + return new BukkitMCPlayerProfile(this.s.createProfile(id, name)); + } + return null; + } @Override - public MCOfflinePlayer[] getOfflinePlayers() { - if (s.getOfflinePlayers() == null) { + public MCOfflinePlayer getOfflinePlayer(String player) { + OfflinePlayer ofp = s.getOfflinePlayer(player); + if(s.getOnlineMode() && ofp.getUniqueId().version() == 3) { + // Not a Mojang provided UUID. + // Bukkit will return a version 3 name-based UUID when it can't find an offline player by that name. + // This is fine for offline mode, but in online mode this means the player can't be found. return null; } + return new BukkitMCOfflinePlayer(ofp); + } + + @Override + public MCOfflinePlayer getOfflinePlayer(UUID uuid) { + return new BukkitMCOfflinePlayer(s.getOfflinePlayer(uuid)); + } + + @Override + public MCOfflinePlayer[] getOfflinePlayers() { OfflinePlayer[] offp = s.getOfflinePlayers(); MCOfflinePlayer[] mcoff = new MCOfflinePlayer[offp.length]; - for (int i = 0; i < offp.length; i++) { + for(int i = 0; i < offp.length; i++) { mcoff[i] = new BukkitMCOfflinePlayer(offp[i]); } return mcoff; } - /* Boring information get methods -.- */ + /* Boring information get methods -.- */ @Override - public String getModVersion() { - return s.getBukkitVersion(); - } + public String getAPIVersion() { + return s.getBukkitVersion(); + } @Override - public String getVersion() { - return s.getVersion(); - } + public String getServerVersion() { + return s.getVersion(); + } + + @Override + public MCVersion getMinecraftVersion() { + if(version == null) { + int temp = s.getBukkitVersion().indexOf('-'); + version = MCVersion.match(s.getBukkitVersion().substring(0, temp).split("\\.")); + } + return version; + } + + @Override + public int getPort() { + return s.getPort(); + } @Override - public int getPort() { - return s.getPort(); - } + public String getIp() { + return s.getIp(); + } @Override - public Boolean getAllowEnd() { - return s.getAllowEnd(); - } + public boolean getAllowEnd() { + return s.getAllowEnd(); + } @Override - public Boolean getAllowFlight() { - return s.getAllowFlight(); - } + public boolean getAllowFlight() { + return s.getAllowFlight(); + } @Override - public Boolean getAllowNether() { - return s.getAllowNether(); - } + public boolean getAllowNether() { + return s.getAllowNether(); + } @Override - public Boolean getOnlineMode() { - return s.getOnlineMode(); - } + public boolean getOnlineMode() { + return s.getOnlineMode(); + } @Override - public String getWorldContainer() { - return s.getWorldContainer().getPath(); - } + public int getViewDistance() { + return s.getViewDistance(); + } @Override - public String getServerName() { - return s.getServerName(); - } + public String getWorldContainer() { + return s.getWorldContainer().getPath(); + } @Override - public int getMaxPlayers() { - return s.getMaxPlayers(); - } + public String getMotd() { + return s.getMotd(); + } @Override - public List getBannedPlayers() { - if(s.getBannedPlayers() == null){ - return null; - } - List list = new ArrayList(); - for(OfflinePlayer p : s.getBannedPlayers()){ - list.add(getOfflinePlayer(p.getName())); - } - return list; - } + public int getMaxPlayers() { + return s.getMaxPlayers(); + } @Override - public List getWhitelistedPlayers() { - if(s.getBannedPlayers() == null){ - return null; - } - List list = new ArrayList(); - for(OfflinePlayer p : s.getWhitelistedPlayers()){ - list.add(getOfflinePlayer(p.getName())); - } - return list; - } + public List getBannedPlayers() { + List list = new ArrayList<>(); + for(OfflinePlayer p : s.getBannedPlayers()) { + list.add(new BukkitMCOfflinePlayer(p)); + } + return list; + } @Override - public List getOperators() { - if(s.getOperators() == null){ - return null; - } - List list = new ArrayList(); - for(OfflinePlayer p : s.getOperators()){ - list.add(getOfflinePlayer(p.getName())); - } - return list; - } + public List getWhitelistedPlayers() { + List list = new ArrayList<>(); + for(OfflinePlayer p : s.getWhitelistedPlayers()) { + list.add(new BukkitMCOfflinePlayer(p)); + } + return list; + } @Override - public Economy getEconomy() { - try{ - @SuppressWarnings("unchecked") - RegisteredServiceProvider economyProvider = (RegisteredServiceProvider) - s.getServicesManager().getRegistration(Class.forName("net.milkbowl.vault.economy.Economy")); - if (economyProvider != null) { - return economyProvider.getProvider(); - } - } catch(ClassNotFoundException e){ - //Ignored, it means they don't have Vault installed. - } - return null; - } + public List getOperators() { + List list = new ArrayList<>(); + for(OfflinePlayer p : s.getOperators()) { + list.add(new BukkitMCOfflinePlayer(p)); + } + return list; + } @Override - public void runasConsole(String cmd) { - CommandSender sender = (CommandSender)Static.GetCommandSender("~console", Target.UNKNOWN).getHandle(); - s.dispatchCommand(sender, cmd); - } + public void runasConsole(String cmd) { + s.dispatchCommand(s.getConsoleSender(), cmd); + } @Override public String toString() { @@ -349,7 +491,7 @@ public String toString() { @Override public boolean equals(Object obj) { - return (obj instanceof BukkitMCServer?s.equals(((BukkitMCServer)obj).s):false); + return (obj instanceof BukkitMCServer && s.equals(((BukkitMCServer) obj).s)); } @Override @@ -358,48 +500,41 @@ public int hashCode() { } @Override - public MCInventory createInventory(MCInventoryHolder holder, MCInventoryType type) { - InventoryHolder ih = null; - - if (holder instanceof MCPlayer) { - ih = ((BukkitMCPlayer)holder)._Player(); - } else if (holder instanceof MCHumanEntity) { - ih = ((BukkitMCHumanEntity)holder).asHumanEntity(); - } else if (holder.getHandle() instanceof InventoryHolder) { - ih = (InventoryHolder)holder.getHandle(); + public MCInventory createInventory(MCInventoryHolder holder, MCInventoryType type, String title) { + InventoryHolder ih; + if(holder == null) { + ih = null; + } else { + ih = (InventoryHolder) holder.getHandle(); } - - return new BukkitMCInventory(Bukkit.createInventory(ih, InventoryType.valueOf(type.name()))); + if(title == null) { + return new BukkitMCInventory(Bukkit.createInventory(ih, InventoryType.valueOf(type.name()))); + } + return new BukkitMCInventory(Bukkit.createInventory(ih, InventoryType.valueOf(type.name()), title)); } @Override - public MCInventory createInventory(MCInventoryHolder holder, int size) { - InventoryHolder ih = null; - - if (holder instanceof MCPlayer) { - ih = ((BukkitMCPlayer)holder)._Player(); - } else if (holder instanceof MCHumanEntity) { - ih = ((BukkitMCHumanEntity)holder).asHumanEntity(); - } else if (holder.getHandle() instanceof InventoryHolder) { - ih = (InventoryHolder)holder.getHandle(); + public MCInventory createInventory(MCInventoryHolder holder, int size, String title) { + InventoryHolder ih; + if(holder == null) { + ih = null; + } else { + ih = (InventoryHolder) holder.getHandle(); } - - return new BukkitMCInventory(Bukkit.createInventory(ih, size)); + if(title == null) { + return new BukkitMCInventory(Bukkit.createInventory(ih, size)); + } + return new BukkitMCInventory(Bukkit.createInventory(ih, size, title)); } @Override - public MCInventory createInventory(MCInventoryHolder holder, int size, String title) { - InventoryHolder ih = null; - - if (holder instanceof MCPlayer) { - ih = ((BukkitMCPlayer)holder)._Player(); - } else if (holder instanceof MCHumanEntity) { - ih = ((BukkitMCHumanEntity)holder).asHumanEntity(); - } else if (holder.getHandle() instanceof InventoryHolder) { - ih = (InventoryHolder)holder.getHandle(); - } + public void banName(String name, String reason, String source) { + s.getBanList(BanList.Type.NAME).addBan(name, reason, null, source); + } - return new BukkitMCInventory(Bukkit.createInventory(ih, size, title)); + @Override + public void unbanName(String name) { + s.getBanList(BanList.Type.NAME).pardon(name); } @Override @@ -437,6 +572,11 @@ public boolean unloadWorld(MCWorld world, boolean save) { return s.unloadWorld(((BukkitMCWorld) world).__World(), save); } + @Override + public void savePlayers() { + s.savePlayers(); + } + @Override public void shutdown() { s.shutdown(); @@ -444,14 +584,27 @@ public void shutdown() { @Override public boolean addRecipe(MCRecipe recipe) { - return s.addRecipe(((BukkitMCRecipe) recipe).r); + return s.addRecipe((Recipe) recipe.getHandle()); + } + + @Override + public boolean removeRecipe(String key) { + if(key.indexOf(':') > -1) { + NamespacedKey nsKey = NamespacedKey.fromString(key); + if(nsKey == null) { + return false; + } + return s.removeRecipe(nsKey); + } else { + return s.removeRecipe(NamespacedKey.minecraft(key)); + } } @Override public List getRecipesFor(MCItemStack result) { - List ret = new ArrayList(); + List ret = new ArrayList<>(); List recipes = s.getRecipesFor(((BukkitMCItemStack) result).__ItemStack()); - for (Recipe recipe : recipes) { + for(Recipe recipe : recipes) { ret.add(BukkitConvertor.BukkitGetRecipe(recipe)); } return ret; @@ -459,18 +612,14 @@ public List getRecipesFor(MCItemStack result) { @Override public List allRecipes() { - List ret = new ArrayList(); - for (Iterator recipes = s.recipeIterator(); recipes.hasNext();) { - Recipe recipe = (Recipe) recipes.next(); + List ret = new ArrayList<>(); + for(Iterator recipes = s.recipeIterator(); recipes.hasNext();) { + Recipe recipe = recipes.next(); ret.add(BukkitConvertor.BukkitGetRecipe(recipe)); } return ret; } -// public Iterator recipe iterator() { -// Iterator ret = //create iterator; -// } - @Override public void clearRecipes() { s.clearRecipes(); @@ -480,4 +629,34 @@ public void clearRecipes() { public void resetRecipes() { s.resetRecipes(); } + + @Override + public MCBossBar createBossBar(String title, MCBarColor color, MCBarStyle style) { + return new BukkitMCBossBar(s.createBossBar(title, BarColor.valueOf(color.name()), BarStyle.valueOf(style.name()))); + } + + @Override + public MCBlockData createBlockData(String data) { + return new BukkitMCBlockData(s.createBlockData(data)); + } + + @Override + public MCMerchant createMerchant(String title) { + return new BukkitMCMerchant(__Server().createMerchant(title), title); + } + + @Override + public MCWorldBorder createWorldBorder() { + return new BukkitMCWorldBorder(s.createWorldBorder()); + } + + @Override + public List selectEntites(MCCommandSender sender, String selector) { + List selectedEntities = s.selectEntities((CommandSender) sender.getHandle(), selector); + List result = new ArrayList<>(selectedEntities.size()); + for(Entity e : selectedEntities) { + result.add(e.getUniqueId()); + } + return result; + } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCShapedRecipe.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCShapedRecipe.java index e7f0cf20ec..edf70c9d1a 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCShapedRecipe.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCShapedRecipe.java @@ -1,92 +1,120 @@ package com.laytonsmith.abstraction.bukkit; import com.laytonsmith.abstraction.MCItemStack; -import com.laytonsmith.abstraction.MCMaterialData; +import com.laytonsmith.abstraction.MCRecipeChoice; +import com.laytonsmith.abstraction.MCRecipeChoice.ExactChoice; +import com.laytonsmith.abstraction.MCRecipeChoice.MaterialChoice; import com.laytonsmith.abstraction.MCShapedRecipe; -import com.laytonsmith.abstraction.StaticLayer; +import com.laytonsmith.abstraction.blocks.MCMaterial; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCMaterial; import com.laytonsmith.abstraction.enums.MCRecipeType; + +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; + +import org.bukkit.Material; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.RecipeChoice; import org.bukkit.inventory.ShapedRecipe; public class BukkitMCShapedRecipe extends BukkitMCRecipe implements MCShapedRecipe { - ShapedRecipe r; + private final ShapedRecipe recipe; + public BukkitMCShapedRecipe(ShapedRecipe recipe) { super(recipe); - r = recipe; + this.recipe = recipe; } - - public BukkitMCShapedRecipe(MCItemStack result) { - this(new ShapedRecipe(((BukkitMCItemStack) result).asItemStack())); + + @Override + public String getKey() { + return recipe.getKey().getKey(); } - + @Override public MCRecipeType getRecipeType() { return MCRecipeType.SHAPED; } - + + @Override + public String getGroup() { + return recipe.getGroup(); + } + + @Override + public void setGroup(String group) { + recipe.setGroup(group); + } + @Override public Object getHandle() { - return r; + return recipe; } @Override - public Map getIngredientMap() { - Map ret = new HashMap(); - for (Map.Entry e : r.getIngredientMap().entrySet()) { - ret.put(e.getKey(), new BukkitMCItemStack(e.getValue())); + public Map getIngredientMap() { + Map ret = new HashMap<>(); + for(Map.Entry entry : recipe.getChoiceMap().entrySet()) { + if(entry.getValue() == null) { + ret.put(entry.getKey(), null); + } else if(entry.getValue() instanceof RecipeChoice.MaterialChoice materialChoice) { + MCRecipeChoice.MaterialChoice choice = new MCRecipeChoice.MaterialChoice(); + for(Material material : materialChoice.getChoices()) { + choice.addMaterial(BukkitMCMaterial.valueOfConcrete(material)); + } + ret.put(entry.getKey(), choice); + } else if(entry.getValue() instanceof RecipeChoice.ExactChoice exactChoice) { + MCRecipeChoice.ExactChoice choice = new MCRecipeChoice.ExactChoice(); + for(ItemStack itemStack : exactChoice.getChoices()) { + choice.addItem(new BukkitMCItemStack(itemStack)); + } + ret.put(entry.getKey(), choice); + } } return ret; } @Override public MCItemStack getResult() { - return new BukkitMCItemStack(r.getResult()); + return new BukkitMCItemStack(recipe.getResult()); } @Override public String[] getShape() { - return r.getShape(); + return recipe.getShape(); } @Override - public MCShapedRecipe setIngredient(char key, MCItemStack ingredient) { -// int type = ingredient.getTypeId(); -// int data = 0; -// if (type < 256) { -// data = ingredient.getData() != null ? ingredient.getData().getData() : 0; -// } else { -// data = ingredient.getDurability(); -// } - MCMaterialData data = null; - if (ingredient.getTypeId() != 0) { - data = ingredient.getData(); - } - return this.setIngredient(key, data); + public void setIngredient(char key, MCItemStack ingredient) { + recipe.setIngredient(key, new RecipeChoice.ExactChoice((ItemStack) ingredient.getHandle())); } @Override - public MCShapedRecipe setIngredient(char key, int type, int data) { - MCMaterialData md = null; - if (type != 0) { - md = StaticLayer.GetItemStack(type, data, 1).getData(); + public void setIngredient(char key, MCRecipeChoice ingredients) { + if(ingredients instanceof MCRecipeChoice.ExactChoice) { + List choice = new ArrayList<>(); + for(MCItemStack itemStack : ((ExactChoice) ingredients).getItems()) { + choice.add((ItemStack) itemStack.getHandle()); + } + recipe.setIngredient(key, new RecipeChoice.ExactChoice(choice)); + } else if(ingredients instanceof MCRecipeChoice.MaterialChoice) { + List choice = new ArrayList<>(); + for(MCMaterial material : ((MaterialChoice) ingredients).getMaterials()) { + choice.add((Material) material.getHandle()); + } + recipe.setIngredient(key, new RecipeChoice.MaterialChoice(choice)); } - return this.setIngredient(key, md); } - + @Override - public MCShapedRecipe setIngredient(char key, MCMaterialData data) { - if (data != null) { - r.setIngredient(key, ((BukkitMCMaterialData)data).md); - } - return this; + public void setIngredient(char key, MCMaterial mat) { + recipe.setIngredient(key, (Material) mat.getHandle()); } @Override - public MCShapedRecipe setShape(String[] shape) { - r.shape(shape); - return this; + public void setShape(String[] shape) { + recipe.shape(shape); } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCShapelessRecipe.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCShapelessRecipe.java index 33b6bf2081..2e5bb83f7b 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCShapelessRecipe.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCShapelessRecipe.java @@ -1,87 +1,106 @@ package com.laytonsmith.abstraction.bukkit; import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.MCRecipeChoice; +import com.laytonsmith.abstraction.MCRecipeChoice.ExactChoice; +import com.laytonsmith.abstraction.MCRecipeChoice.MaterialChoice; import com.laytonsmith.abstraction.MCShapelessRecipe; +import com.laytonsmith.abstraction.blocks.MCMaterial; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCMaterial; import com.laytonsmith.abstraction.enums.MCRecipeType; import java.util.ArrayList; import java.util.List; + import org.bukkit.Material; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.RecipeChoice; import org.bukkit.inventory.ShapelessRecipe; -/** - * - * @author jb_aero - */ public class BukkitMCShapelessRecipe extends BukkitMCRecipe implements MCShapelessRecipe { - ShapelessRecipe r; + private final ShapelessRecipe recipe; + public BukkitMCShapelessRecipe(ShapelessRecipe recipe) { super(recipe); - r = recipe; + this.recipe = recipe; } - - public BukkitMCShapelessRecipe(MCItemStack result) { - this(new ShapelessRecipe(((BukkitMCItemStack) result).asItemStack())); + + @Override + public String getKey() { + return recipe.getKey().getKey(); } - + @Override public MCRecipeType getRecipeType() { return MCRecipeType.SHAPELESS; } - + + @Override + public String getGroup() { + return recipe.getGroup(); + } + + @Override + public void setGroup(String group) { + recipe.setGroup(group); + } + @Override public Object getHandle() { - return r; + return recipe; } @Override public MCItemStack getResult() { - return new BukkitMCItemStack(r.getResult()); + return new BukkitMCItemStack(recipe.getResult()); } @Override - public List getIngredients() { - List ret = new ArrayList(); - for (ItemStack is : r.getIngredientList()) { - ret.add(new BukkitMCItemStack(is)); + public List getIngredients() { + List choiceList = recipe.getChoiceList(); + List ret = new ArrayList<>(choiceList.size()); + for(RecipeChoice recipeChoice : choiceList) { + if(recipeChoice instanceof RecipeChoice.MaterialChoice materialChoice) { + MCRecipeChoice.MaterialChoice choice = new MCRecipeChoice.MaterialChoice(); + for(Material material : materialChoice.getChoices()) { + choice.addMaterial(BukkitMCMaterial.valueOfConcrete(material)); + } + ret.add(choice); + } else if(recipeChoice instanceof RecipeChoice.ExactChoice exactChoice) { + MCRecipeChoice.ExactChoice choice = new MCRecipeChoice.ExactChoice(); + for(ItemStack itemStack : exactChoice.getChoices()) { + choice.addItem(new BukkitMCItemStack(itemStack)); + } + ret.add(choice); + } } return ret; } @Override - public MCShapelessRecipe addIngredient(MCItemStack ingredient) { - int type = ingredient.getTypeId(); - int data = 0; - if (type < 256) { - data = ingredient.getData() != null ? ingredient.getData().getData() : 0; - } else { - data = ingredient.getDurability(); - } - return this.addIngredient(type, data, ingredient.getAmount()); + public void addIngredient(MCMaterial ingredient, int amount) { + recipe.addIngredient(amount, (Material) ingredient.getHandle()); } @Override - public MCShapelessRecipe addIngredient(int type, int data, int amount) { - r.addIngredient(amount, Material.getMaterial(type), data); - return this; + public void addIngredient(MCMaterial ingredient) { + recipe.addIngredient((Material) ingredient.getHandle()); } @Override - public MCShapelessRecipe removeIngredient(MCItemStack ingredient) { - int type = ingredient.getTypeId(); - int data = 0; - if (type < 256) { - data = ingredient.getData() != null ? ingredient.getData().getData() : 0; - } else { - data = ingredient.getDurability(); + public void addIngredient(MCRecipeChoice choice) { + if(choice instanceof MCRecipeChoice.ExactChoice) { + List itemChoice = new ArrayList<>(); + for(MCItemStack itemStack : ((ExactChoice) choice).getItems()) { + itemChoice.add((ItemStack) itemStack.getHandle()); + } + recipe.addIngredient(new RecipeChoice.ExactChoice(itemChoice)); + } else if(choice instanceof MCRecipeChoice.MaterialChoice) { + List materialChoice = new ArrayList<>(); + for(MCMaterial material : ((MaterialChoice) choice).getMaterials()) { + materialChoice.add((Material) material.getHandle()); + } + recipe.addIngredient(new RecipeChoice.MaterialChoice(materialChoice)); } - return this.removeIngredient(type, data, ingredient.getAmount()); - } - - @Override - public MCShapelessRecipe removeIngredient(int type, int data, int amount) { - r.removeIngredient(amount, Material.getMaterial(type), data); - return this; } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCSkullMeta.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCSkullMeta.java index da276577cd..43f2f4bd0f 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCSkullMeta.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCSkullMeta.java @@ -1,12 +1,19 @@ package com.laytonsmith.abstraction.bukkit; +import com.destroystokyo.paper.profile.PlayerProfile; import com.laytonsmith.abstraction.AbstractionObject; +import com.laytonsmith.abstraction.MCOfflinePlayer; +import com.laytonsmith.abstraction.MCPlayerProfile; import com.laytonsmith.abstraction.MCSkullMeta; +import com.laytonsmith.core.Static; +import org.bukkit.NamespacedKey; +import org.bukkit.OfflinePlayer; import org.bukkit.inventory.meta.SkullMeta; public class BukkitMCSkullMeta extends BukkitMCItemMeta implements MCSkullMeta { SkullMeta sm; + public BukkitMCSkullMeta(SkullMeta im) { super(im); this.sm = im; @@ -19,6 +26,7 @@ public BukkitMCSkullMeta(AbstractionObject o) { @Override public boolean hasOwner() { + // Spigot only returns true of profile has a name, ignoring UUID. return sm.hasOwner(); } @@ -27,9 +35,55 @@ public String getOwner() { return sm.getOwner(); } + @Override + public MCOfflinePlayer getOwningPlayer() { + // Spigot will return null if profile doesn't have a name. + // This might be a bug. + OfflinePlayer ofp = sm.getOwningPlayer(); + if(ofp != null) { + return new BukkitMCOfflinePlayer(sm.getOwningPlayer()); + } + return null; + } + @Override public boolean setOwner(String owner) { return sm.setOwner(owner); } + @Override + public void setOwningPlayer(MCOfflinePlayer player) { + sm.setOwningPlayer((OfflinePlayer) player.getHandle()); + } + + @Override + public MCPlayerProfile getProfile() { + if(((BukkitMCServer) Static.getServer()).isPaper()) { + PlayerProfile profile = this.sm.getPlayerProfile(); + if(profile != null) { + return new BukkitMCPlayerProfile(profile); + } + } + return null; + } + + @Override + public void setProfile(MCPlayerProfile profile) { + // Completes the profile from user cache. + this.sm.setPlayerProfile((PlayerProfile) profile.getHandle()); + } + + @Override + public String getNoteBlockSound() { + NamespacedKey sound = this.sm.getNoteBlockSound(); + if(sound == null) { + return null; + } + return sound.toString(); + } + + @Override + public void setNoteBlockSound(String noteBlockSound) { + this.sm.setNoteBlockSound(NamespacedKey.fromString(noteBlockSound)); + } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCSmithingInventory.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCSmithingInventory.java new file mode 100644 index 0000000000..7f464b76cd --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCSmithingInventory.java @@ -0,0 +1,66 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.MCRecipe; +import com.laytonsmith.abstraction.MCSmithingInventory; +import org.bukkit.inventory.SmithingInventory; +import org.bukkit.inventory.SmithingRecipe; + +public class BukkitMCSmithingInventory extends BukkitMCInventory implements MCSmithingInventory { + + SmithingInventory si; + + public BukkitMCSmithingInventory(SmithingInventory inventory) { + super(inventory); + si = inventory; + } + + @Override + public MCItemStack getInputEquipment() { + return new BukkitMCItemStack(si.getItem(1)); + } + + @Override + public MCItemStack getInputMaterial() { + return new BukkitMCItemStack(si.getItem(2)); + } + + @Override + public MCItemStack getInputTemplate() { + return new BukkitMCItemStack(si.getItem(0)); + } + + @Override + public MCRecipe getRecipe() { + if(si.getRecipe() == null) { + return null; + } else { + return new BukkitMCSmithingRecipe((SmithingRecipe) si.getRecipe()); + } + } + + @Override + public MCItemStack getResult() { + return new BukkitMCItemStack(si.getResult()); + } + + @Override + public void setInputEquipment(MCItemStack stack) { + si.setItem(1, ((BukkitMCItemStack) stack).asItemStack()); + } + + @Override + public void setInputMaterial(MCItemStack stack) { + si.setItem(2, ((BukkitMCItemStack) stack).asItemStack()); + } + + @Override + public void setInputTemplate(MCItemStack stack) { + si.setItem(0, ((BukkitMCItemStack) stack).asItemStack()); + } + + @Override + public void setResult(MCItemStack stack) { + si.setResult(((BukkitMCItemStack) stack).asItemStack()); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCSmithingRecipe.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCSmithingRecipe.java new file mode 100644 index 0000000000..630bf2550f --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCSmithingRecipe.java @@ -0,0 +1,70 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.MCSmithingRecipe; +import com.laytonsmith.abstraction.blocks.MCMaterial; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCMaterial; +import com.laytonsmith.abstraction.enums.MCRecipeType; +import org.bukkit.Material; +import org.bukkit.inventory.RecipeChoice; +import org.bukkit.inventory.SmithingRecipe; + +import java.util.List; + +public class BukkitMCSmithingRecipe extends BukkitMCRecipe implements MCSmithingRecipe { + + private final SmithingRecipe recipe; + + public BukkitMCSmithingRecipe(SmithingRecipe recipe) { + super(recipe); + this.recipe = recipe; + } + + @Override + public String getKey() { + return recipe.getKey().getKey(); + } + + @Override + public MCRecipeType getRecipeType() { + return MCRecipeType.SMITHING; + } + + @Override + public String getGroup() { + return ""; + } + + @Override + public void setGroup(String group) {} + + @Override + public MCItemStack getResult() { + return new BukkitMCItemStack(recipe.getResult()); + } + + @Override + public Object getHandle() { + return recipe; + } + + @Override + public MCMaterial[] getBase() { + List choices = ((RecipeChoice.MaterialChoice) recipe.getBase()).getChoices(); + MCMaterial[] ret = new MCMaterial[choices.size()]; + for(int i = 0; i < choices.size(); i++) { + ret[i] = BukkitMCMaterial.valueOfConcrete(choices.get(i)); + } + return ret; + } + + @Override + public MCMaterial[] getAddition() { + List choices = ((RecipeChoice.MaterialChoice) recipe.getAddition()).getChoices(); + MCMaterial[] ret = new MCMaterial[choices.size()]; + for(int i = 0; i < choices.size(); i++) { + ret[i] = BukkitMCMaterial.valueOfConcrete(choices.get(i)); + } + return ret; + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCStonecuttingRecipe.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCStonecuttingRecipe.java new file mode 100644 index 0000000000..8fab3050f8 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCStonecuttingRecipe.java @@ -0,0 +1,103 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.MCRecipeChoice; +import com.laytonsmith.abstraction.MCRecipeChoice.ExactChoice; +import com.laytonsmith.abstraction.MCRecipeChoice.MaterialChoice; +import com.laytonsmith.abstraction.MCStonecuttingRecipe; +import com.laytonsmith.abstraction.blocks.MCMaterial; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCMaterial; +import com.laytonsmith.abstraction.enums.MCRecipeType; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.RecipeChoice; +import org.bukkit.inventory.StonecuttingRecipe; + +import java.util.ArrayList; +import java.util.List; + +public class BukkitMCStonecuttingRecipe extends BukkitMCRecipe implements MCStonecuttingRecipe { + + private final StonecuttingRecipe recipe; + + public BukkitMCStonecuttingRecipe(StonecuttingRecipe recipe) { + super(recipe); + this.recipe = recipe; + } + + @Override + public String getKey() { + return recipe.getKey().getKey(); + } + + @Override + public MCRecipeType getRecipeType() { + return MCRecipeType.STONECUTTING; + } + + @Override + public String getGroup() { + return recipe.getGroup(); + } + + @Override + public void setGroup(String group) { + recipe.setGroup(group); + } + + @Override + public MCItemStack getResult() { + return new BukkitMCItemStack(recipe.getResult()); + } + + @Override + public Object getHandle() { + return recipe; + } + + @Override + public MCRecipeChoice getInput() { + RecipeChoice recipeChoice = recipe.getInputChoice(); + if(recipeChoice instanceof RecipeChoice.MaterialChoice materialChoice) { + MCRecipeChoice.MaterialChoice choice = new MCRecipeChoice.MaterialChoice(); + for(Material material : materialChoice.getChoices()) { + choice.addMaterial(BukkitMCMaterial.valueOfConcrete(material)); + } + return choice; + } else if(recipeChoice instanceof RecipeChoice.ExactChoice exactChoice) { + MCRecipeChoice.ExactChoice choice = new MCRecipeChoice.ExactChoice(); + for(ItemStack itemStack : exactChoice.getChoices()) { + choice.addItem(new BukkitMCItemStack(itemStack)); + } + return choice; + } + throw new UnsupportedOperationException("Unsupported recipe choice"); + } + + @Override + public void setInput(MCItemStack input) { + recipe.setInputChoice(new RecipeChoice.ExactChoice((ItemStack) input.getHandle())); + } + + @Override + public void setInput(MCMaterial mat) { + recipe.setInput((Material) mat.getHandle()); + } + + @Override + public void setInput(MCRecipeChoice choice) { + if(choice instanceof MCRecipeChoice.ExactChoice) { + List itemChoice = new ArrayList<>(); + for(MCItemStack itemStack : ((ExactChoice) choice).getItems()) { + itemChoice.add((ItemStack) itemStack.getHandle()); + } + recipe.setInputChoice(new RecipeChoice.ExactChoice(itemChoice)); + } else if(choice instanceof MCRecipeChoice.MaterialChoice) { + List materialChoice = new ArrayList<>(); + for(MCMaterial material : ((MaterialChoice) choice).getMaterials()) { + materialChoice.add((Material) material.getHandle()); + } + recipe.setInputChoice(new RecipeChoice.MaterialChoice(materialChoice)); + } + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCSuspiciousStewMeta.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCSuspiciousStewMeta.java new file mode 100644 index 0000000000..aa629a898a --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCSuspiciousStewMeta.java @@ -0,0 +1,48 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCLivingEntity.MCEffect; +import com.laytonsmith.abstraction.MCSuspiciousStewMeta; +import com.laytonsmith.abstraction.enums.MCPotionEffectType; +import com.laytonsmith.abstraction.enums.MCVersion; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCPotionEffectType; +import com.laytonsmith.core.Static; +import com.laytonsmith.core.constructs.Target; +import org.bukkit.inventory.meta.SuspiciousStewMeta; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.ArrayList; +import java.util.List; + +public class BukkitMCSuspiciousStewMeta extends BukkitMCItemMeta implements MCSuspiciousStewMeta { + + SuspiciousStewMeta ssm; + + public BukkitMCSuspiciousStewMeta(SuspiciousStewMeta ssm) { + super(ssm); + this.ssm = ssm; + } + + @Override + public boolean addCustomEffect(MCPotionEffectType type, int strength, int ticks, boolean ambient, boolean particles, boolean icon, boolean force, Target t) { + if(ticks < 0) { + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_19_4)) { + ticks = -1; + } else { + ticks = Integer.MAX_VALUE; + } + } + PotionEffect pe = new PotionEffect((PotionEffectType) type.getConcrete(), ticks, strength, ambient, particles, icon); + return this.ssm.addCustomEffect(pe, force); + } + + @Override + public List getCustomEffects() { + List list = new ArrayList<>(); + for(PotionEffect pe : this.ssm.getCustomEffects()) { + list.add(new MCEffect(BukkitMCPotionEffectType.valueOfConcrete(pe.getType()), pe.getAmplifier(), + pe.getDuration(), pe.isAmbient(), pe.hasParticles(), pe.hasIcon())); + } + return list; + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCTagContainer.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCTagContainer.java new file mode 100644 index 0000000000..495005322a --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCTagContainer.java @@ -0,0 +1,157 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCNamespacedKey; +import com.laytonsmith.abstraction.MCTagContainer; +import com.laytonsmith.abstraction.enums.MCTagType; +import org.bukkit.NamespacedKey; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; + +import java.util.HashSet; +import java.util.Set; + +public class BukkitMCTagContainer implements MCTagContainer { + + PersistentDataContainer pdc; + + public BukkitMCTagContainer(PersistentDataContainer pdc) { + this.pdc = pdc; + } + + @Override + public MCTagContainer newContainer() { + return new BukkitMCTagContainer(this.pdc.getAdapterContext().newPersistentDataContainer()); + } + + @Override + public boolean isEmpty() { + return this.pdc.isEmpty(); + } + + @Override + public Set getKeys() { + Set keys = new HashSet<>(); + for(NamespacedKey key : this.pdc.getKeys()) { + keys.add(new BukkitMCNamespacedKey(key)); + } + return keys; + } + + @Override + public MCTagType getType(MCNamespacedKey key) { + NamespacedKey namespacedKey = (NamespacedKey) key.getHandle(); + // Check tag types in order of most frequently used + if(this.pdc.has(namespacedKey, PersistentDataType.STRING)) { + return MCTagType.STRING; + } else if(this.pdc.has(namespacedKey, PersistentDataType.INTEGER)) { + return MCTagType.INTEGER; + } else if(this.pdc.has(namespacedKey, PersistentDataType.BYTE)) { + return MCTagType.BYTE; + } else if(this.pdc.has(namespacedKey, PersistentDataType.DOUBLE)) { + return MCTagType.DOUBLE; + } else if(this.pdc.has(namespacedKey, PersistentDataType.LONG)) { + return MCTagType.LONG; + } else if(this.pdc.has(namespacedKey, PersistentDataType.FLOAT)) { + return MCTagType.FLOAT; + } else if(this.pdc.has(namespacedKey, PersistentDataType.TAG_CONTAINER)) { + return MCTagType.TAG_CONTAINER; + } else if(this.pdc.has(namespacedKey, PersistentDataType.BYTE_ARRAY)) { + return MCTagType.BYTE_ARRAY; + } else if(this.pdc.has(namespacedKey, PersistentDataType.SHORT)) { + return MCTagType.SHORT; + } else if(this.pdc.has(namespacedKey, PersistentDataType.INTEGER_ARRAY)) { + return MCTagType.INTEGER_ARRAY; + } else if(this.pdc.has(namespacedKey, PersistentDataType.LONG_ARRAY)) { + return MCTagType.LONG_ARRAY; + } else if(this.pdc.has(namespacedKey, PersistentDataType.TAG_CONTAINER_ARRAY)) { + return MCTagType.TAG_CONTAINER_ARRAY; + } + return null; + } + + @Override + public Object get(MCNamespacedKey key, MCTagType type) { + PersistentDataType bukkitType = GetPersistentDataType(type); + Object value = this.pdc.get((NamespacedKey) key.getHandle(), bukkitType); + if(value instanceof PersistentDataContainer) { + return new BukkitMCTagContainer((PersistentDataContainer) value); + } else if(value instanceof PersistentDataContainer[] concreteContainers) { + MCTagContainer[] abstractContainers = new MCTagContainer[concreteContainers.length]; + for(int i = 0; i < concreteContainers.length; i++) { + abstractContainers[i] = new BukkitMCTagContainer(concreteContainers[i]); + } + return abstractContainers; + } + return value; + } + + @Override + public void set(MCNamespacedKey key, MCTagType type, Object value) { + PersistentDataType bukkitType = GetPersistentDataType(type); + if(value instanceof MCTagContainer) { + value = ((MCTagContainer) value).getHandle(); + } else if(value instanceof MCTagContainer[] abstractContainers) { + PersistentDataContainer[] concreteContainers = new PersistentDataContainer[abstractContainers.length]; + for(int i = 0; i < abstractContainers.length; i++) { + concreteContainers[i] = (PersistentDataContainer) abstractContainers[i].getHandle(); + } + value = concreteContainers; + } + this.pdc.set((NamespacedKey) key.getHandle(), bukkitType, value); + } + + @Override + public void remove(MCNamespacedKey key) { + this.pdc.remove((NamespacedKey) key.getHandle()); + } + + private static PersistentDataType GetPersistentDataType(MCTagType type) { + switch(type) { + case BYTE: + return PersistentDataType.BYTE; + case BYTE_ARRAY: + return PersistentDataType.BYTE_ARRAY; + case DOUBLE: + return PersistentDataType.DOUBLE; + case FLOAT: + return PersistentDataType.FLOAT; + case INTEGER: + return PersistentDataType.INTEGER; + case INTEGER_ARRAY: + return PersistentDataType.INTEGER_ARRAY; + case LONG: + return PersistentDataType.LONG; + case LONG_ARRAY: + return PersistentDataType.LONG_ARRAY; + case SHORT: + return PersistentDataType.SHORT; + case STRING: + return PersistentDataType.STRING; + case TAG_CONTAINER: + return PersistentDataType.TAG_CONTAINER; + case TAG_CONTAINER_ARRAY: + return PersistentDataType.TAG_CONTAINER_ARRAY; + } + throw new IllegalArgumentException("Invalid persistent data type: " + type.name()); + } + + @Override + public Object getHandle() { + return this.pdc; + } + + @Override + public boolean equals(Object o) { + return o instanceof MCTagContainer && this.pdc.equals(((MCTagContainer) o).getHandle()); + } + + @Override + public int hashCode() { + return this.pdc.hashCode(); + } + + @Override + public String toString() { + return this.pdc.toString(); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCTameable.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCTameable.java deleted file mode 100644 index c3f3f3c9aa..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCTameable.java +++ /dev/null @@ -1,51 +0,0 @@ - - -package com.laytonsmith.abstraction.bukkit; - -import com.laytonsmith.abstraction.AbstractionObject; -import com.laytonsmith.abstraction.MCAnimalTamer; -import com.laytonsmith.abstraction.MCTameable; -import org.bukkit.entity.Entity; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Tameable; - -/** - * - * - */ -public class BukkitMCTameable extends BukkitMCAgeable implements MCTameable{ - - Tameable t; - public BukkitMCTameable(Entity t){ - super((LivingEntity) t); - this.t = (Tameable)t; - } - - public BukkitMCTameable(AbstractionObject a){ - super((LivingEntity)a.getHandle()); - this.t = ((Tameable)a.getHandle()); - } - - @Override - public boolean isTamed() { - return t.isTamed(); - } - - @Override - public void setTamed(boolean bln) { - t.setTamed(bln); - } - - @Override - public MCAnimalTamer getOwner() { - if(t.getOwner() == null){ - return null; - } - return new BukkitMCAnimalTamer(t.getOwner()); - } - - @Override - public void setOwner(MCAnimalTamer at) { - t.setOwner(((BukkitMCAnimalTamer)at).at); - } -} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCTeam.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCTeam.java index 547819f8ce..6f55d59567 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCTeam.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCTeam.java @@ -1,23 +1,29 @@ package com.laytonsmith.abstraction.bukkit; -import com.laytonsmith.abstraction.MCOfflinePlayer; import com.laytonsmith.abstraction.MCScoreboard; import com.laytonsmith.abstraction.MCTeam; -import java.util.HashSet; -import java.util.Set; -import org.bukkit.OfflinePlayer; +import com.laytonsmith.abstraction.enums.MCChatColor; +import com.laytonsmith.abstraction.enums.MCOption; +import com.laytonsmith.abstraction.enums.MCOptionStatus; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCChatColor; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCOption; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCOptionStatus; import org.bukkit.scoreboard.Team; +import org.bukkit.scoreboard.Team.OptionStatus; + +import java.util.Set; public class BukkitMCTeam implements MCTeam { Team t; + public BukkitMCTeam(Team team) { t = team; } @Override - public void addPlayer(MCOfflinePlayer player) { - t.addPlayer((OfflinePlayer) player.getHandle()); + public void addEntry(String entry) { + t.addEntry(entry); } @Override @@ -41,12 +47,14 @@ public String getName() { } @Override - public Set getPlayers() { - Set ret = new HashSet(); - for (OfflinePlayer o : t.getPlayers()) { - ret.add(new BukkitMCOfflinePlayer(o)); - } - return ret; + public MCOptionStatus getOption(MCOption option) { + OptionStatus os = t.getOption(BukkitMCOption.getConvertor().getConcreteEnum(option)); + return MCOptionStatus.valueOf(os.name()); + } + + @Override + public Set getEntries() { + return t.getEntries(); } @Override @@ -70,13 +78,18 @@ public String getSuffix() { } @Override - public boolean hasPlayer(MCOfflinePlayer player) { - return t.hasPlayer((OfflinePlayer) player.getHandle()); + public MCChatColor getColor() { + return BukkitMCChatColor.getConvertor().getAbstractedEnum(t.getColor()); + } + + @Override + public boolean hasEntry(String entry) { + return t.hasEntry(entry); } @Override - public boolean removePlayer(MCOfflinePlayer player) { - return t.removePlayer((OfflinePlayer) player.getHandle()); + public boolean removeEntry(String entry) { + return t.removeEntry(entry); } @Override @@ -94,6 +107,11 @@ public void setDisplayName(String displayName) { t.setDisplayName(displayName); } + @Override + public void setOption(MCOption option, MCOptionStatus status) { + t.setOption(BukkitMCOption.getConvertor().getConcreteEnum(option), BukkitMCOptionStatus.getConvertor().getConcreteEnum(status)); + } + @Override public void setPrefix(String prefix) { t.setPrefix(prefix); @@ -104,6 +122,11 @@ public void setSuffix(String suffix) { t.setSuffix(suffix); } + @Override + public void setColor(MCChatColor color) { + t.setColor(BukkitMCChatColor.getConvertor().getConcreteEnum(color)); + } + @Override public void unregister() { t.unregister(); diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCTravelAgent.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCTravelAgent.java deleted file mode 100644 index 45d7e09f52..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCTravelAgent.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.laytonsmith.abstraction.bukkit; - -import com.laytonsmith.abstraction.AbstractionObject; -import com.laytonsmith.abstraction.MCLocation; -import com.laytonsmith.abstraction.MCTravelAgent; -import org.bukkit.TravelAgent; - -/** - * - * @author MariuszT - */ -public class BukkitMCTravelAgent implements MCTravelAgent { - - TravelAgent a; - public BukkitMCTravelAgent(TravelAgent a) { - this.a = a; - } - - public BukkitMCTravelAgent(AbstractionObject o) { - a = (TravelAgent)o; - } - - @Override - public String toString() { - return a.toString(); - } - - @Override - @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") - public boolean equals(Object obj) { - return a.equals(obj); - } - - @Override - public int hashCode() { - return a.hashCode(); - } - - @Override - public boolean createPortal(MCLocation location) { - return a.createPortal(((BukkitMCLocation)location).asLocation()); - } - - @Override - public MCLocation findOrCreate(MCLocation location) { - return new BukkitMCLocation(a.findOrCreate(((BukkitMCLocation)location).asLocation())); - } - - @Override - public MCLocation findPortal(MCLocation location) { - return new BukkitMCLocation(a.findPortal(((BukkitMCLocation)location).asLocation())); - } - - @Override - public boolean getCanCreatePortal() { - return a.getCanCreatePortal(); - } - - @Override - public void setCanCreatePortal(boolean create) { - a.setCanCreatePortal(create); - } - - @Override - public int getCreationRadius() { - return a.getCreationRadius(); - } - - @Override - public MCTravelAgent setCreationRadius(int radius) { - return new BukkitMCTravelAgent(a.setCreationRadius(radius)); - } - - @Override - public int getSearchRadius() { - return a.getSearchRadius(); - } - - @Override - public MCTravelAgent setSearchRadius(int radius) { - return new BukkitMCTravelAgent(a.setSearchRadius(radius)); - } - - @Override - public Object getHandle() { - return a; - } -} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCTropicalFishBucketMeta.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCTropicalFishBucketMeta.java new file mode 100644 index 0000000000..39dd4d4606 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCTropicalFishBucketMeta.java @@ -0,0 +1,53 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCTropicalFishBucketMeta; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCTropicalFish; +import com.laytonsmith.abstraction.entities.MCTropicalFish; +import com.laytonsmith.abstraction.enums.MCDyeColor; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCDyeColor; +import org.bukkit.inventory.meta.TropicalFishBucketMeta; + +public class BukkitMCTropicalFishBucketMeta extends BukkitMCItemMeta implements MCTropicalFishBucketMeta { + + TropicalFishBucketMeta meta; + + public BukkitMCTropicalFishBucketMeta(TropicalFishBucketMeta im) { + super(im); + meta = im; + } + + @Override + public MCDyeColor getPatternColor() { + return BukkitMCDyeColor.getConvertor().getAbstractedEnum(meta.getPatternColor()); + } + + @Override + public void setPatternColor(MCDyeColor color) { + meta.setPatternColor(BukkitMCDyeColor.getConvertor().getConcreteEnum(color)); + } + + @Override + public MCDyeColor getBodyColor() { + return BukkitMCDyeColor.getConvertor().getAbstractedEnum(meta.getBodyColor()); + } + + @Override + public void setBodyColor(MCDyeColor color) { + meta.setBodyColor(BukkitMCDyeColor.getConvertor().getConcreteEnum(color)); + } + + @Override + public MCTropicalFish.MCPattern getPattern() { + return BukkitMCTropicalFish.BukkitMCPattern.getConvertor().getAbstractedEnum(meta.getPattern()); + } + + @Override + public void setPattern(MCTropicalFish.MCPattern pattern) { + meta.setPattern(BukkitMCTropicalFish.BukkitMCPattern.getConvertor().getConcreteEnum(pattern)); + } + + @Override + public boolean hasVariant() { + return meta.hasVariant(); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCVibration.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCVibration.java new file mode 100644 index 0000000000..8cdf4be6c6 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCVibration.java @@ -0,0 +1,28 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.AbstractionObject; +import com.laytonsmith.abstraction.MCEntity; +import com.laytonsmith.abstraction.MCLocation; +import org.bukkit.Location; +import org.bukkit.Vibration; +import org.bukkit.entity.Entity; + +public class BukkitMCVibration implements AbstractionObject { + + Vibration vibration; + + public BukkitMCVibration(MCLocation origin, MCEntity entity, int arrivalTime) { + this.vibration = new Vibration((Location) origin.getHandle(), + new Vibration.Destination.EntityDestination((Entity) entity.getHandle()), arrivalTime); + } + + public BukkitMCVibration(MCLocation origin, MCLocation location, int arrivalTime) { + this.vibration = new Vibration((Location) origin.getHandle(), + new Vibration.Destination.BlockDestination((Location) location.getHandle()), arrivalTime); + } + + @Override + public Vibration getHandle() { + return this.vibration; + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCVirtualInventoryHolder.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCVirtualInventoryHolder.java new file mode 100644 index 0000000000..d709a9fe64 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCVirtualInventoryHolder.java @@ -0,0 +1,55 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCInventory; +import com.laytonsmith.abstraction.MCVirtualInventoryHolder; +import com.laytonsmith.core.functions.InventoryManagement; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.jetbrains.annotations.NotNull; + +public class BukkitMCVirtualInventoryHolder implements MCVirtualInventoryHolder { + + private final VirtualHolder vholder; + + public BukkitMCVirtualInventoryHolder(String id, String title) { + this.vholder = new VirtualHolder(id, title); + } + + public BukkitMCVirtualInventoryHolder(InventoryHolder ih) { + this.vholder = (VirtualHolder) ih; + } + + @Override + public MCInventory getInventory() { + return new BukkitMCInventory(this.vholder.getInventory()); + } + + @Override + public String getID() { + return this.vholder.id; + } + + @Override + public VirtualHolder getHandle() { + return this.vholder; + } + + public static class VirtualHolder implements InventoryHolder { + private final String id; + private final String title; + + VirtualHolder(String id, String title) { + this.id = id; + this.title = title; + } + + @Override + public @NotNull Inventory getInventory() { + return (Inventory) InventoryManagement.VIRTUAL_INVENTORIES.get(this.id).getHandle(); + } + + public String getTitle() { + return this.title; + } + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCWorld.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCWorld.java index d0c45deb8c..4710503593 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCWorld.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCWorld.java @@ -1,118 +1,98 @@ - - package com.laytonsmith.abstraction.bukkit; import com.laytonsmith.abstraction.AbstractionObject; import com.laytonsmith.abstraction.MCChunk; import com.laytonsmith.abstraction.MCEntity; -import com.laytonsmith.abstraction.MCItem; +import com.laytonsmith.abstraction.MCFireworkEffect; +import com.laytonsmith.abstraction.blocks.MCBlockFace; +import com.laytonsmith.abstraction.blocks.MCMaterial; +import com.laytonsmith.abstraction.entities.MCItem; import com.laytonsmith.abstraction.MCItemStack; -import com.laytonsmith.abstraction.MCLightningStrike; +import com.laytonsmith.abstraction.entities.MCLightningStrike; import com.laytonsmith.abstraction.MCLivingEntity; import com.laytonsmith.abstraction.MCLocation; import com.laytonsmith.abstraction.MCPlayer; import com.laytonsmith.abstraction.MCWorld; +import com.laytonsmith.abstraction.MCWorldBorder; import com.laytonsmith.abstraction.blocks.MCBlock; -import com.laytonsmith.abstraction.blocks.MCFallingBlock; +import com.laytonsmith.abstraction.blocks.MCBlockData; import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCBlock; -import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCFallingBlock; -import com.laytonsmith.abstraction.bukkit.entities.BukkitMCHorse; -import com.laytonsmith.abstraction.entities.MCHorse; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCEntity; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCFallingBlock; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCFirework; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCItem; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCLightningStrike; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCLivingEntity; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCPlayer; +import com.laytonsmith.abstraction.entities.MCFallingBlock; +import com.laytonsmith.abstraction.entities.MCFirework; import com.laytonsmith.abstraction.enums.MCBiomeType; -import com.laytonsmith.abstraction.enums.MCCreeperType; import com.laytonsmith.abstraction.enums.MCDifficulty; -import com.laytonsmith.abstraction.enums.MCDyeColor; import com.laytonsmith.abstraction.enums.MCEffect; import com.laytonsmith.abstraction.enums.MCEntityType; -import com.laytonsmith.abstraction.enums.MCGameRule; -import com.laytonsmith.abstraction.enums.MCMobs; -import com.laytonsmith.abstraction.enums.MCOcelotType; -import com.laytonsmith.abstraction.enums.MCPigType; -import com.laytonsmith.abstraction.enums.MCProfession; -import com.laytonsmith.abstraction.enums.MCSkeletonType; +import com.laytonsmith.abstraction.enums.MCParticle; import com.laytonsmith.abstraction.enums.MCSound; +import com.laytonsmith.abstraction.enums.MCSoundCategory; import com.laytonsmith.abstraction.enums.MCTreeType; -import com.laytonsmith.abstraction.enums.MCWolfType; +import com.laytonsmith.abstraction.enums.MCVersion; import com.laytonsmith.abstraction.enums.MCWorldEnvironment; import com.laytonsmith.abstraction.enums.MCWorldType; -import com.laytonsmith.abstraction.enums.MCZombieType; import com.laytonsmith.abstraction.enums.bukkit.BukkitMCBiomeType; import com.laytonsmith.abstraction.enums.bukkit.BukkitMCDifficulty; -import com.laytonsmith.abstraction.enums.bukkit.BukkitMCDyeColor; import com.laytonsmith.abstraction.enums.bukkit.BukkitMCEntityType; -import com.laytonsmith.abstraction.enums.bukkit.BukkitMCOcelotType; -import com.laytonsmith.abstraction.enums.bukkit.BukkitMCProfession; -import com.laytonsmith.abstraction.enums.bukkit.BukkitMCSkeletonType; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCParticle; import com.laytonsmith.abstraction.enums.bukkit.BukkitMCSound; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCSoundCategory; import com.laytonsmith.abstraction.enums.bukkit.BukkitMCTreeType; import com.laytonsmith.abstraction.enums.bukkit.BukkitMCWorldEnvironment; import com.laytonsmith.abstraction.enums.bukkit.BukkitMCWorldType; -import com.laytonsmith.core.constructs.CArray; -import com.laytonsmith.core.constructs.CInt; +import com.laytonsmith.core.Static; +import com.laytonsmith.core.constructs.CClosure; +import com.laytonsmith.core.constructs.CString; import com.laytonsmith.core.constructs.Target; -import com.laytonsmith.core.exceptions.ConfigRuntimeException; -import com.laytonsmith.core.functions.Exceptions.ExceptionType; -import java.util.ArrayList; -import java.util.List; +import com.laytonsmith.core.exceptions.CRE.CREPluginInternalException; import org.bukkit.Chunk; import org.bukkit.Effect; +import org.bukkit.FireworkEffect; +import org.bukkit.GameRule; +import org.bukkit.HeightMap; import org.bukkit.Location; -import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.Particle; +import org.bukkit.Registry; +import org.bukkit.SoundCategory; import org.bukkit.World; -import org.bukkit.block.Biome; -import org.bukkit.block.Block; import org.bukkit.block.BlockFace; -import org.bukkit.entity.Bat; -import org.bukkit.entity.Blaze; -import org.bukkit.entity.CaveSpider; -import org.bukkit.entity.Chicken; -import org.bukkit.entity.Cow; -import org.bukkit.entity.Creeper; -import org.bukkit.entity.EnderDragon; -import org.bukkit.entity.Enderman; +import org.bukkit.block.data.BlockData; import org.bukkit.entity.Entity; -import org.bukkit.entity.Ghast; -import org.bukkit.entity.Giant; -import org.bukkit.entity.Horse; -import org.bukkit.entity.IronGolem; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Firework; import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.MagmaCube; -import org.bukkit.entity.MushroomCow; -import org.bukkit.entity.Ocelot; -import org.bukkit.entity.Pig; -import org.bukkit.entity.PigZombie; import org.bukkit.entity.Player; -import org.bukkit.entity.Sheep; -import org.bukkit.entity.Silverfish; -import org.bukkit.entity.Skeleton; -import org.bukkit.entity.Slime; -import org.bukkit.entity.Snowman; -import org.bukkit.entity.Spider; -import org.bukkit.entity.Squid; -import org.bukkit.entity.Villager; -import org.bukkit.entity.Witch; -import org.bukkit.entity.Wither; -import org.bukkit.entity.WitherSkull; -import org.bukkit.entity.Wolf; -import org.bukkit.entity.Zombie; -import org.bukkit.material.MaterialData; - -/** - * - * - */ +import org.bukkit.generator.ChunkGenerator; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.FireworkMeta; +import org.bukkit.util.Consumer; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.UUID; + public class BukkitMCWorld extends BukkitMCMetadatable implements MCWorld { - World w; + World w; - public BukkitMCWorld(World w) { + public BukkitMCWorld(World w) { super(w); - this.w = w; - } + this.w = w; + } @Override public boolean equals(Object o) { - return o instanceof MCWorld ? this.w.equals(((BukkitMCWorld)o).w) : false; + return o instanceof MCWorld && this.w.equals(((BukkitMCWorld) o).w); } @Override @@ -125,31 +105,28 @@ public String toString() { return this.w.toString(); } - public BukkitMCWorld(AbstractionObject a){ - this((World)null); - if(a instanceof MCWorld){ - this.w = ((World)a.getHandle()); - } else { - throw new ClassCastException(); - } - } + public BukkitMCWorld(AbstractionObject a) { + this((World) null); + if(a instanceof MCWorld) { + this.w = ((World) a.getHandle()); + } else { + throw new ClassCastException(); + } + } @Override - public World getHandle(){ - return w; - } + public World getHandle() { + return w; + } - public World __World() { - return w; - } + public World __World() { + return w; + } @Override public List getPlayers() { - if (w.getPlayers() == null) { - return null; - } - List list = new ArrayList(); - for (Player p : w.getPlayers()) { + List list = new ArrayList<>(); + for(Player p : w.getPlayers()) { list.add(new BukkitMCPlayer(p)); } return list; @@ -157,32 +134,31 @@ public List getPlayers() { @Override public List getEntities() { - if (w.getEntities() == null) { - return null; - } - List list = new ArrayList(); - for (Entity e : w.getEntities()) { + List list = new ArrayList<>(); + for(Entity e : w.getEntities()) { list.add(new BukkitMCEntity(e)); } return list; } @Override - public List getLivingEntities() { - if (w.getLivingEntities() == null) { - return null; - } - List list = new ArrayList(); - for (LivingEntity e : w.getLivingEntities()) { - list.add(new BukkitMCLivingEntity(e)); - } - return list; - } + public List getLivingEntities() { + List list = new ArrayList<>(); + for(LivingEntity e : w.getLivingEntities()) { + list.add(new BukkitMCLivingEntity(e)); + } + return list; + } @Override - public String getName() { - return w.getName(); - } + public String getName() { + return w.getName(); + } + + @Override + public UUID getUniqueID() { + return w.getUID(); + } @Override public long getSeed() { @@ -210,13 +186,45 @@ public void setPVP(boolean pvp) { } @Override - public boolean getGameRuleValue(MCGameRule gameRule) { - return Boolean.valueOf(w.getGameRuleValue(gameRule.getGameRule())); + public String[] getGameRules() { + return w.getGameRules(); + } + + @Override + public boolean isGameRule(String gameRule) { + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_21_11)) { + // Paper and Spigot differ in GameRule interfaces/implementation + // This forces Spigot to be as case-sensitive as Paper for new rule names + NamespacedKey key = NamespacedKey.fromString(gameRule); + if(key == null) { + return false; + } + return Registry.GAME_RULE.get(key) != null; + } + return w.isGameRule(gameRule); + } + + @Override + public Object getGameRuleValue(String gameRule) { + GameRule gr = GameRule.getByName(gameRule); + if(gr == null) { + return null; + } + return w.getGameRuleValue(gr); + } + + @Override + public boolean setGameRuleValue(String gameRule, Object value) { + GameRule gr = GameRule.getByName(gameRule); + if(gr == null) { + return false; + } + return w.setGameRule(gr, value); } @Override - public void setGameRuleValue(MCGameRule gameRule, boolean value) { - w.setGameRuleValue(gameRule.getGameRule(), String.valueOf(value)); + public MCWorldBorder getWorldBorder() { + return new BukkitMCWorldBorder(w.getWorldBorder()); } @Override @@ -226,11 +234,11 @@ public MCWorldEnvironment getEnvironment() { @Override public String getGenerator() { - try { - return w.getGenerator().toString(); - } catch (NullPointerException npe) { + ChunkGenerator generator = w.getGenerator(); + if(generator == null) { return "default"; } + return generator.toString(); } @Override @@ -239,23 +247,70 @@ public MCWorldType getWorldType() { } @Override - public MCBlock getBlockAt(int x, int y, int z) { - if (w.getBlockAt(x, y, z) == null) { - return null; - } - return new BukkitMCBlock(w.getBlockAt(x, y, z)); - } + public int getSeaLevel() { + return getHandle().getSeaLevel(); + } + + @Override + public int getMaxHeight() { + return getHandle().getMaxHeight(); + } @Override - public MCEntity spawn(MCLocation l, Class mobType) { - return BukkitConvertor.BukkitGetCorrectEntity(w.spawn(((BukkitMCLocation) l).l, mobType)); - } + public int getMinHeight() { + return w.getMinHeight(); + } + + @Override + public MCBlock getBlockAt(int x, int y, int z) { + return new BukkitMCBlock(w.getBlockAt(x, y, z)); + } + + @Override + public MCEntity spawn(MCLocation l, Class mobType) { + return BukkitConvertor.BukkitGetCorrectEntity(w.spawn(((BukkitMCLocation) l).l, mobType)); + } @Override public MCEntity spawn(MCLocation l, MCEntityType entType) { return BukkitConvertor.BukkitGetCorrectEntity(w.spawnEntity( ((BukkitMCLocation) l).asLocation(), - BukkitMCEntityType.getConvertor().getConcreteEnum(MCEntityType.valueOf(entType.name())))); + ((BukkitMCEntityType) entType).getConcrete())); + } + + @Override + public MCEntity spawn(MCLocation l, MCEntityType.MCVanillaEntityType entityType) { + return BukkitConvertor.BukkitGetCorrectEntity(w.spawnEntity( + ((BukkitMCLocation) l).asLocation(), + (EntityType) MCEntityType.valueOfVanillaType(entityType).getConcrete())); + } + + @Override + public MCEntity spawn(MCLocation l, MCEntityType entType, final CClosure closure) { + Location location = (Location) l.getHandle(); + Class entityClass = ((EntityType) entType.getConcrete()).getEntityClass(); + Entity entity; + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_20_2)) { + entity = w.spawn(location, entityClass, e -> beforeSpawn(e, closure)); + } else { + try { + Method m = w.getClass().getMethod("spawn", Location.class, Class.class, Consumer.class); + entity = (Entity) m.invoke(w, location, entityClass, (Consumer) e -> beforeSpawn(e, closure)); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + throw new CREPluginInternalException(e.getMessage(), Target.UNKNOWN, e); + } + } + return BukkitConvertor.BukkitGetCorrectEntity(entity); + } + + private void beforeSpawn(Entity entity, CClosure closure) { + MCEntity temp = BukkitConvertor.BukkitGetCorrectEntity(entity); + Static.InjectEntity(temp); + try { + closure.executeCallable(new CString(entity.getUniqueId().toString(), Target.UNKNOWN)); + } finally { + Static.UninjectEntity(temp); + } } @Override @@ -264,427 +319,257 @@ public boolean generateTree(MCLocation l, MCTreeType treeType) { } @Override - public void playEffect(MCLocation l, MCEffect mCEffect, int e, int data) { - w.playEffect(((BukkitMCLocation) l).l, Effect.valueOf(mCEffect.name()), e, data); - } + public void playEffect(MCLocation l, MCEffect mCEffect, int data, int radius) { + w.playEffect(((BukkitMCLocation) l).l, Effect.valueOf(mCEffect.name()), data, radius); + } + + @Override + public void playEffect(MCLocation l, MCEffect mcEffect, Object data, int radius) { + Effect effect = Effect.valueOf(mcEffect.name()); + switch(mcEffect) { + case RECORD_PLAY: + case STEP_SOUND: + w.playEffect((Location) l.getHandle(), effect, ((MCMaterial) data).getHandle(), radius); + return; + case SMOKE: + case SHOOT_WHITE_SMOKE: + w.playEffect((Location) l.getHandle(), effect, BlockFace.valueOf(((MCBlockFace) data).name()), radius); + return; + case PARTICLES_AND_SOUND_BRUSH_BLOCK_COMPLETE: + w.playEffect((Location) l.getHandle(), effect, ((MCBlockData) data).getHandle(), radius); + return; + } + w.playEffect((Location) l.getHandle(), effect, data, radius); + } + + @Override + public void spawnParticle(MCLocation l, MCParticle pa, int count, double offsetX, double offsetY, double offsetZ, double velocity, boolean force, Object data) { + w.spawnParticle((Particle) pa.getConcrete(), (Location) l.getHandle(), count, offsetX, offsetY, offsetZ, + velocity, ((BukkitMCParticle) pa).getParticleData(l, data), force); + } + + @Override + public void playSound(MCLocation l, MCSound sound, MCSoundCategory category, float volume, float pitch, Long seed) { + SoundCategory cat = BukkitMCSoundCategory.getConvertor().getConcreteEnum(category); + if(cat == null) { + cat = SoundCategory.MASTER; + } + if(seed == null) { + w.playSound((Location) l.getHandle(), ((BukkitMCSound) sound).getConcrete(), cat, volume, pitch); + } else { + w.playSound((Location) l.getHandle(), ((BukkitMCSound) sound).getConcrete(), cat, volume, pitch, seed); + } + } + + @Override + public void playSound(MCEntity ent, MCSound sound, MCSoundCategory category, float volume, float pitch, Long seed) { + SoundCategory cat = BukkitMCSoundCategory.getConvertor().getConcreteEnum(category); + if(cat == null) { + cat = SoundCategory.MASTER; + } + if(seed == null) { + w.playSound((Entity) ent.getHandle(), ((BukkitMCSound) sound).getConcrete(), cat, volume, pitch); + } else { + w.playSound((Entity) ent.getHandle(), ((BukkitMCSound) sound).getConcrete(), cat, volume, pitch, seed); + } + } @Override - public void playSound(MCLocation l, MCSound sound, float volume, float pitch) { - w.playSound(((BukkitMCLocation) l).asLocation(), - BukkitMCSound.getConvertor().getConcreteEnum(sound), volume, pitch); + public void playSound(MCEntity ent, String sound, MCSoundCategory category, float volume, float pitch, Long seed) { + SoundCategory cat = BukkitMCSoundCategory.getConvertor().getConcreteEnum(category); + if(cat == null) { + cat = SoundCategory.MASTER; + } + if(seed == null) { + w.playSound((Entity) ent.getHandle(), sound, cat, volume, pitch); + } else { + w.playSound((Entity) ent.getHandle(), sound, cat, volume, pitch, seed); + } } @Override - public void playSound(MCLocation l, String sound, float volume, float pitch) { - for(Player p: w.getPlayers()) - p.playSound(((BukkitMCLocation)l).asLocation(), sound, volume, pitch); + public void playSound(MCLocation l, String sound, MCSoundCategory category, float volume, float pitch, Long seed) { + SoundCategory cat = BukkitMCSoundCategory.getConvertor().getConcreteEnum(category); + if(cat == null) { + cat = SoundCategory.MASTER; + } + if(seed == null) { + w.playSound((Location) l.getHandle(), sound, cat, volume, pitch); + } else { + w.playSound((Location) l.getHandle(), sound, cat, volume, pitch, seed); + } } @Override - public MCItem dropItemNaturally(MCLocation l, MCItemStack is) { - return new BukkitMCItem(w.dropItemNaturally(((BukkitMCLocation) l).l, ((BukkitMCItemStack) is).is)); - } + public MCItem dropItemNaturally(MCLocation l, MCItemStack is) { + return new BukkitMCItem(w.dropItemNaturally(((BukkitMCLocation) l).l, (ItemStack) is.getHandle())); + } @Override - public MCItem dropItem(MCLocation l, MCItemStack is) { - return new BukkitMCItem(w.dropItem(((BukkitMCLocation) l).l, ((BukkitMCItemStack) is).is)); - } + public MCItem dropItem(MCLocation l, MCItemStack is) { + return new BukkitMCItem(w.dropItem(((BukkitMCLocation) l).l, (ItemStack) is.getHandle())); + } @Override - public MCLightningStrike strikeLightning(MCLocation GetLocation) { + public MCLightningStrike strikeLightning(MCLocation location) { return new BukkitMCLightningStrike( - w.strikeLightning(((BukkitMCLocation) GetLocation).l)); + w.strikeLightning(((BukkitMCLocation) location).l)); } @Override - public MCLightningStrike strikeLightningEffect(MCLocation GetLocation) { + public MCLightningStrike strikeLightningEffect(MCLocation location) { return new BukkitMCLightningStrike( - w.strikeLightningEffect(((BukkitMCLocation) GetLocation).l)); + w.strikeLightningEffect(((BukkitMCLocation) location).l)); } @Override - public void setStorm(boolean b) { - w.setStorm(b); - } + public void setStorm(boolean b) { + w.setStorm(b); + } @Override - public MCLocation getSpawnLocation() { - return new BukkitMCLocation(w.getSpawnLocation()); - } + public MCLocation getSpawnLocation() { + return new BukkitMCLocation(w.getSpawnLocation()); + } @Override - public void refreshChunk(int x, int z) { - w.refreshChunk(x, z); - } + public void refreshChunk(int x, int z) { + // deprecated in 1.8 due to inconsistency + w.refreshChunk(x, z); + } @Override public void loadChunk(int x, int z) { - w.loadChunk(x, z); - } + w.loadChunk(x, z); + } @Override - public void unloadChunk(int x, int z) { - w.unloadChunk(x, z); - } + public void unloadChunk(int x, int z) { + w.unloadChunk(x, z); + } + @Override + public boolean isChunkForceLoaded(int x, int z) { + return w.isChunkForceLoaded(x, z); + } @Override - public void setTime(long time) { - w.setTime(time); - } + public void setChunkForceLoaded(int x, int z, boolean forced) { + w.setChunkForceLoaded(x, z, forced); + } + + @Override + public MCChunk[] getForceLoadedChunks() { + Collection chunks = w.getForceLoadedChunks(); + MCChunk[] mcChunks = new MCChunk[chunks.size()]; + int i = 0; + for(Chunk c : chunks) { + mcChunks[i++] = new BukkitMCChunk(c); + } + return mcChunks; + } + + @Override + public void setTime(long time) { + w.setTime(time); + } + + @Override + public long getTime() { + return w.getTime(); + } + + @Override + public void setFullTime(long time) { + w.setFullTime(time); + } @Override - public long getTime() { - return w.getTime(); - } + public long getFullTime() { + return w.getFullTime(); + } + + @Override + public MCBiomeType getBiome(MCLocation location) { + return BukkitMCBiomeType.valueOfConcrete(w.getBiome((Location) location.getHandle())); + } @Override - public MCBiomeType getBiome(int x, int z) { - return BukkitMCBiomeType.getConvertor().getAbstractedEnum(w.getBiome(x, z)); - } + public void setBiome(int x, int z, MCBiomeType type) { + w.setBiome(x, z, ((BukkitMCBiomeType) type).getConcrete()); + } @Override - public void setBiome(int x, int z, MCBiomeType type) { - w.setBiome(x, z, Biome.valueOf(type.name())); - } + public void setBiome(MCLocation location, MCBiomeType type) { + w.setBiome(((Location) location.getHandle()), ((BukkitMCBiomeType) type).getConcrete()); + } @Override public MCBlock getHighestBlockAt(int x, int z) { - //Workaround for getHighestBlockAt, since it doesn't like transparent - //blocks. - Block b = w.getBlockAt(x, w.getMaxHeight() - 1, z); - while(b.getType() == Material.AIR && b.getY() > 0){ - b = b.getRelative(BlockFace.DOWN); + return new BukkitMCBlock(w.getHighestBlockAt(x, z, HeightMap.WORLD_SURFACE)); + } + + @Override + public boolean explosion(MCLocation location, float size, boolean safe, boolean fire, MCEntity source) { + Location loc = (Location) location.getHandle(); + Entity src = null; + if(source != null) { + src = (Entity) source.getHandle(); } - return new BukkitMCBlock(b); - } - - @Override - public void explosion(double x, double y, double z, float size, boolean safe) { - w.createExplosion(x, y, z, size, !safe, !safe); - } - - @Override - public void setSpawnLocation(int x, int y, int z) { - w.setSpawnLocation(x, y, z); - } - - @Override - public CArray spawnMob(MCMobs name, String subClass, int qty, MCLocation l, Target t) { - Class mobType = null; - CArray ids = new CArray(Target.UNKNOWN); - try { - switch (name) { - case CHICKEN: - mobType = Chicken.class; - break; - case COW: - mobType = Cow.class; - break; - case CREEPER: - mobType = Creeper.class; - break; - case GHAST: - mobType = Ghast.class; - break; - case PIG: - mobType = Pig.class; - break; - case PIGZOMBIE: - mobType = PigZombie.class; - break; - case SHEEP: - mobType = Sheep.class; - break; - case SKELETON: - mobType = Skeleton.class; - break; - case SLIME: - mobType = Slime.class; - break; - case SPIDER: - mobType = Spider.class; - break; - case SQUID: - mobType = Squid.class; - break; - case WOLF: - mobType = Wolf.class; - break; - case ZOMBIE: - mobType = Zombie.class; - break; - case CAVESPIDER: - mobType = CaveSpider.class; - break; - case ENDERMAN: - mobType = Enderman.class; - break; - case SILVERFISH: - mobType = Silverfish.class; - break; - case BLAZE: - mobType = Blaze.class; - break; - case VILLAGER: - mobType = Villager.class; - break; - case ENDERDRAGON: - mobType = EnderDragon.class; - break; - case MAGMACUBE: - mobType = MagmaCube.class; - break; - case MOOSHROOM: - mobType = MushroomCow.class; - break; - case SPIDERJOCKEY: - mobType = Spider.class; - break; - case GIANT: - mobType = Giant.class; - break; - case SNOWGOLEM: - mobType = Snowman.class; - break; - case OCELOT: - mobType = Ocelot.class; - break; - case IRONGOLEM: - mobType = IronGolem.class; - break; - case BAT: - mobType = Bat.class; - break; - case WITHER: - mobType = Wither.class; - break; - case WITHER_SKULL: - mobType = WitherSkull.class; - break; - case WITCH: - mobType = Witch.class; - break; - case HORSE: - mobType = Horse.class; - break; - } - } catch (IllegalArgumentException e) { - throw new ConfigRuntimeException("No mob of type " + name + " exists", - ExceptionType.FormatException, t); - } - for (int i = 0; i < qty; i++) { - MCEntity e = l.getWorld().spawn(l, mobType); - String[] subTypes = subClass.toUpperCase().split("-"); - if (name == MCMobs.SPIDERJOCKEY) { - e.setPassenger(l.getWorld().spawn(l, Skeleton.class)); - } - if (!subClass.equals("")) { //if subClass is blank, none of this needs to run at all - if (((BukkitMCEntity)e).getHandle() instanceof Sheep) { - Sheep s = (Sheep) ((BukkitMCEntity)e).getHandle(); - MCDyeColor color = MCDyeColor.WHITE; - for (String type : subTypes) { - try { - color = MCDyeColor.valueOf(type); - s.setColor(BukkitMCDyeColor.getConvertor().getConcreteEnum(color)); - } catch (IllegalArgumentException ex) { - throw new ConfigRuntimeException(type + " is not a valid color", - ExceptionType.FormatException, t); - } - } - } - if(((BukkitMCEntity)e).getHandle() instanceof Ocelot){ - Ocelot o = (Ocelot)((BukkitMCEntity)e).getHandle(); - MCOcelotType otype = MCOcelotType.WILD_OCELOT; - for (String type : subTypes) { - try { - otype = MCOcelotType.valueOf(type); - o.setCatType(BukkitMCOcelotType.getConvertor().getConcreteEnum(otype)); - } catch (IllegalArgumentException ex){ - throw new ConfigRuntimeException(type + " is not an ocelot type", - ExceptionType.FormatException, t); - } - } - } - if(((BukkitMCEntity)e).getHandle() instanceof Creeper){ - Creeper c = (Creeper)((BukkitMCEntity)e).getHandle(); - for (String type : subTypes) { - try { - MCCreeperType ctype = MCCreeperType.valueOf(type); - switch (ctype) { - case POWERED: - c.setPowered(true); - break; - default: - break; - } - } catch (IllegalArgumentException ex){ - throw new ConfigRuntimeException(type + " is not a creeper state", - ExceptionType.FormatException, t); - } - } - } - if(((BukkitMCEntity)e).getHandle() instanceof Wolf){ - Wolf w = (Wolf)((BukkitMCEntity)e).getHandle(); - for (String type : subTypes) { - try { - MCWolfType wtype = MCWolfType.valueOf(type); - switch (wtype) { - case ANGRY: - w.setAngry(true); - break; - case TAMED: - w.setTamed(true); - break; - default: - break; - } - } catch (IllegalArgumentException ex){ - throw new ConfigRuntimeException(type + " is not a wolf state", - ExceptionType.FormatException, t); - } - } - } - if (((BukkitMCEntity)e).getHandle() instanceof Villager) { - Villager v = (Villager) ((BukkitMCEntity)e).getHandle(); - MCProfession job = MCProfession.FARMER; - for (String type : subTypes){ - try { - job = MCProfession.valueOf(type); - v.setProfession(BukkitMCProfession.getConvertor().getConcreteEnum(job)); - } catch (IllegalArgumentException ex) { - throw new ConfigRuntimeException(type + " is not a valid profession", - ExceptionType.FormatException, t); - } - } - } - if (((BukkitMCEntity)e).getHandle() instanceof Enderman) { - Enderman en = (Enderman) ((BukkitMCEntity)e).getHandle(); - for (String type : subTypes){ - try { - MaterialData held = new MaterialData(Material.valueOf(type)); - en.setCarriedMaterial(held); - } catch (IllegalArgumentException ex) { - throw new ConfigRuntimeException(type + " is not a valid material", - ExceptionType.FormatException, t); - } - } - } - if(((BukkitMCEntity)e).getHandle() instanceof Slime){ - Slime sl = (Slime)((BukkitMCEntity)e).getHandle(); - for (String type : subTypes) { - if(!"".equals(type)){ - try{ - sl.setSize(Integer.parseInt(type)); - } catch (IllegalArgumentException ex){ - throw new ConfigRuntimeException(type + " is not a valid size", - ExceptionType.FormatException, t); - } - } - } - } - if(((BukkitMCEntity)e).getHandle() instanceof Skeleton){ - Skeleton sk = (Skeleton)((BukkitMCEntity)e).getHandle(); - MCSkeletonType stype = MCSkeletonType.NORMAL; - for (String type : subTypes) { - try { - stype = MCSkeletonType.valueOf(type); - sk.setSkeletonType(BukkitMCSkeletonType.getConvertor().getConcreteEnum(stype)); - } catch (IllegalArgumentException ex){ - throw new ConfigRuntimeException(type + " is not a skeleton type", - ExceptionType.FormatException, t); - } - } - } - if(((BukkitMCEntity)e).getHandle() instanceof Zombie){ - Zombie z = (Zombie)((BukkitMCEntity)e).getHandle(); - for (String type : subTypes) { - try { - MCZombieType ztype = MCZombieType.valueOf(type); - switch (ztype) { - case BABY: - z.setBaby(true); - break; - case VILLAGER: - z.setVillager(true); - break; - } - } catch (IllegalArgumentException ex){ - if (z instanceof PigZombie) { - try { - ((PigZombie) z).setAnger(Integer.valueOf(type)); - } catch (IllegalArgumentException iae) { - throw new ConfigRuntimeException(type + " was neither a zombie state nor a number.", - ExceptionType.FormatException, t); - } - } else { - throw new ConfigRuntimeException(type + " is not a zombie state", - ExceptionType.FormatException, t); - } - } - } - } - if(((BukkitMCEntity)e).getHandle() instanceof Pig){ - Pig p = (Pig)((BukkitMCEntity)e).getHandle(); - for (String type : subTypes) { - try { - MCPigType ptype = MCPigType.valueOf(type); - switch (ptype) { - case SADDLED: - p.setSaddle(true); - break; - default: - break; - } - } catch (IllegalArgumentException ex){ - throw new ConfigRuntimeException(type + " is not a pig state", - ExceptionType.FormatException, t); - } - } - } - if(((BukkitMCEntity) e).getHandle() instanceof Horse) { - Horse h = (Horse) ((BukkitMCEntity) e).getHandle(); - for (String type : subTypes) { - try { - MCHorse.MCHorseVariant htype = MCHorse.MCHorseVariant.valueOf(type); - h.setVariant(BukkitMCHorse.BukkitMCHorseVariant.getConvertor().getConcreteEnum(htype)); - } catch (IllegalArgumentException notVar) { - try { - MCHorse.MCHorseColor hcolor = MCHorse.MCHorseColor.valueOf(type); - h.setColor(BukkitMCHorse.BukkitMCHorseColor.getConvertor().getConcreteEnum(hcolor)); - } catch (IllegalArgumentException notColor) { - try { - MCHorse.MCHorsePattern hpattern = MCHorse.MCHorsePattern.valueOf(type); - h.setStyle(BukkitMCHorse.BukkitMCHorsePattern.getConvertor().getConcreteEnum(hpattern)); - } catch (IllegalArgumentException notAnything) { - throw new ConfigRuntimeException("Type " + type + " did not match any horse variants," - + " colors, or patterns.", ExceptionType.FormatException, t); - } - } - } - } - } - } - ids.push(new CInt(e.getEntityId(), t)); - } - return ids; - } + return w.createExplosion(loc, size, fire, !safe, src); + } + + @Override + public void setSpawnLocation(int x, int y, int z) { + w.setSpawnLocation(x, y, z); + } + + @Override + public void setSpawnLocation(MCLocation location) { + Location loc = (Location) location.getHandle(); + loc.setYaw(Location.normalizeYaw(loc.getYaw())); + w.setSpawnLocation(loc); + } @Override public boolean exists() { //I dunno how well this will work, but it's worth a shot. - try{ + try { w.getName(); return true; - } catch(Exception e){ + } catch (Exception e) { return false; } } @Override - public MCFallingBlock spawnFallingBlock(MCLocation loc, int type, byte data) { - Location mcloc = (Location)((BukkitMCLocation)loc).getHandle(); - return new BukkitMCFallingBlock(w.spawnFallingBlock(mcloc, type, data)); + public boolean isAutoSave() { + return w.isAutoSave(); + } + + @Override + public void setAutoSave(boolean autoSave) { + w.setAutoSave(autoSave); + } + + @Override + public MCFallingBlock spawnFallingBlock(MCLocation loc, MCBlockData data) { + return new BukkitMCFallingBlock(w.spawnFallingBlock((Location) loc.getHandle(), (BlockData) data.getHandle())); + } + + @Override + public MCFirework launchFirework(MCLocation l, int strength, List effects) { + Firework firework = w.spawn(((BukkitMCLocation) l).asLocation(), Firework.class); + FireworkMeta meta = firework.getFireworkMeta(); + meta.setPower(Math.max(strength, 0)); + for(MCFireworkEffect effect : effects) { + meta.addEffect((FireworkEffect) effect.getHandle()); + } + firework.setFireworkMeta(meta); + if(strength < 0) { + firework.detonate(); + } + return new BukkitMCFirework(firework); } @Override @@ -710,11 +595,16 @@ public MCChunk getChunkAt(MCLocation l) { @Override public MCChunk[] getLoadedChunks() { Chunk[] chunks = w.getLoadedChunks(); - MCChunk[] MCChunks = new MCChunk[chunks.length]; - for (int i = 0; i < chunks.length; i++) { - MCChunks[i] = new BukkitMCChunk(chunks[i]); + MCChunk[] mcChunks = new MCChunk[chunks.length]; + for(int i = 0; i < chunks.length; i++) { + mcChunks[i] = new BukkitMCChunk(chunks[i]); } - return MCChunks; + return mcChunks; + } + + @Override + public boolean isChunkLoaded(int x, int z) { + return w.isChunkLoaded(x, z); } @Override @@ -732,6 +622,11 @@ public void setThunderDuration(int time) { w.setThunderDuration(time); } + @Override + public void setClearWeatherDuration(int time) { + w.setClearWeatherDuration(time); + } + @Override public boolean isStorming() { return w.hasStorm(); @@ -743,7 +638,16 @@ public boolean isThundering() { } @Override - public void save(){ + public void save() { w.save(); } + + @Override + public void setKeepSpawnInMemory(boolean keepLoaded) { + try { + w.setKeepSpawnInMemory(keepLoaded); + } catch(NoSuchMethodError ex) { + // some version after 1.21.9 + } + } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCWorldBorder.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCWorldBorder.java new file mode 100644 index 0000000000..89c4cf8284 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCWorldBorder.java @@ -0,0 +1,105 @@ +package com.laytonsmith.abstraction.bukkit; + +import com.laytonsmith.abstraction.MCLocation; +import com.laytonsmith.abstraction.MCWorldBorder; +import org.bukkit.Location; +import org.bukkit.WorldBorder; + +public class BukkitMCWorldBorder implements MCWorldBorder { + + private final WorldBorder wb; + + public BukkitMCWorldBorder(WorldBorder wb) { + this.wb = wb; + } + + @Override + public void reset() { + wb.reset(); + } + + @Override + public double getSize() { + return wb.getSize(); + } + + @Override + public void setSize(double size) { + wb.setSize(size); + } + + @Override + public void setSize(double size, int seconds) { + wb.setSize(size, seconds); + } + + @Override + public MCLocation getCenter() { + return new BukkitMCLocation(wb.getCenter()); + } + + @Override + public void setCenter(MCLocation location) { + wb.setCenter((Location) location.getHandle()); + } + + @Override + public double getDamageBuffer() { + return wb.getDamageBuffer(); + } + + @Override + public void setDamageBuffer(double blocks) { + wb.setDamageBuffer(blocks); + } + + @Override + public double getDamageAmount() { + return wb.getDamageAmount(); + } + + @Override + public void setDamageAmount(double damage) { + wb.setDamageAmount(damage); + } + + @Override + public int getWarningTime() { + return wb.getWarningTime(); + } + + @Override + public void setWarningTime(int seconds) { + wb.setWarningTime(seconds); + } + + @Override + public int getWarningDistance() { + return wb.getWarningDistance(); + } + + @Override + public void setWarningDistance(int distance) { + wb.setWarningDistance(distance); + } + + @Override + public Object getHandle() { + return wb; + } + + @Override + public String toString() { + return wb.toString(); + } + + @Override + public boolean equals(Object obj) { + return (obj instanceof BukkitMCWorldBorder && wb.equals(((BukkitMCWorldBorder) obj).wb)); + } + + @Override + public int hashCode() { + return wb.hashCode(); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCWorldCreator.java b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCWorldCreator.java index 61bacb4ae9..b57107e981 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCWorldCreator.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCWorldCreator.java @@ -1,4 +1,3 @@ - package com.laytonsmith.abstraction.bukkit; import com.laytonsmith.abstraction.MCWorld; @@ -7,24 +6,39 @@ import com.laytonsmith.abstraction.enums.MCWorldType; import com.laytonsmith.abstraction.enums.bukkit.BukkitMCWorldEnvironment; import com.laytonsmith.abstraction.enums.bukkit.BukkitMCWorldType; +import com.laytonsmith.core.Static; import org.bukkit.World; +import org.bukkit.World.Environment; import org.bukkit.WorldCreator; import org.bukkit.WorldType; -/** - * - * - */ +import java.io.File; +import java.util.logging.Level; + public class BukkitMCWorldCreator implements MCWorldCreator { - + WorldCreator creator; - public BukkitMCWorldCreator(String name){ + + public BukkitMCWorldCreator(String name) { creator = new WorldCreator(name); } @Override public MCWorld createWorld() { - return new BukkitMCWorld(creator.createWorld()); + World w = creator.createWorld(); + if(w != null && w.getEnvironment() == Environment.NORMAL) { + File nether = new File(w.getWorldFolder(), "DIM-1"); + if(nether.exists()) { + Static.getLogger().log(Level.WARNING, "Loaded " + w.getName() + " world with overworld (NORMAL)" + + " environment but found DIM-1 (NETHER) directory exists."); + } + File end = new File(w.getWorldFolder(), "DIM1"); + if(end.exists()) { + Static.getLogger().log(Level.WARNING, "Loaded " + w.getName() + " world with overworld (NORMAL)" + + " environment but found DIM1 (THE_END) directory exists."); + } + } + return new BukkitMCWorld(w); } @Override @@ -46,13 +60,13 @@ public MCWorldCreator seed(long seed) { creator.seed(seed); return this; } - + @Override public MCWorldCreator generator(String generator) { creator.generator(generator); return this; } - + @Override public MCWorldCreator copy(MCWorld toCopy) { creator.copy((World) toCopy.getHandle()); diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCBanner.java b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCBanner.java new file mode 100644 index 0000000000..b35f90022f --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCBanner.java @@ -0,0 +1,65 @@ +package com.laytonsmith.abstraction.bukkit.blocks; + +import com.laytonsmith.abstraction.MCPattern; +import com.laytonsmith.abstraction.blocks.MCBanner; +import com.laytonsmith.abstraction.bukkit.BukkitMCPattern; +import com.laytonsmith.abstraction.enums.MCDyeColor; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCDyeColor; +import org.bukkit.DyeColor; +import org.bukkit.block.Banner; +import org.bukkit.block.banner.Pattern; + +import java.util.ArrayList; +import java.util.List; + +public class BukkitMCBanner extends BukkitMCBlockState implements MCBanner { + + Banner b; + + public BukkitMCBanner(Banner block) { + super(block); + b = block; + } + + @Override + public MCDyeColor getBaseColor() { + DyeColor c = b.getBaseColor(); + if(c != null) { + return BukkitMCDyeColor.getConvertor().getAbstractedEnum(c); + } + // fallback method in case basecolor is incorrectly null + return MCDyeColor.valueOf(b.getType().name().replace("_BANNER", "")); + } + + @Override + public void setBaseColor(MCDyeColor color) { + b.setBaseColor(BukkitMCDyeColor.getConvertor().getConcreteEnum(color)); + } + + @Override + public int numberOfPatterns() { + return b.numberOfPatterns(); + } + + @Override + public List getPatterns() { + List bukkitPatterns = b.getPatterns(); + List patterns = new ArrayList<>(bukkitPatterns.size()); + for(Pattern p : bukkitPatterns) { + patterns.add(new BukkitMCPattern(p)); + } + return patterns; + } + + @Override + public void addPattern(MCPattern pattern) { + b.addPattern((Pattern) pattern.getHandle()); + } + + @Override + public void clearPatterns() { + for(int i = b.numberOfPatterns() - 1; i >= 0; i--) { + b.removePattern(i); + } + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCBeacon.java b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCBeacon.java new file mode 100644 index 0000000000..1130262c53 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCBeacon.java @@ -0,0 +1,57 @@ +package com.laytonsmith.abstraction.bukkit.blocks; + +import com.laytonsmith.abstraction.MCLivingEntity; +import com.laytonsmith.abstraction.blocks.MCBeacon; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCLivingEntity; + +import java.util.Collection; +import java.util.HashSet; + +import org.bukkit.block.Beacon; +import org.bukkit.entity.LivingEntity; + +public class BukkitMCBeacon extends BukkitMCBlockState implements MCBeacon { + + private Beacon beacon; + + public BukkitMCBeacon(Beacon block) { + super(block); + this.beacon = block; + } + + @Override + public Collection getEntitiesInRange() { + HashSet ret = new HashSet<>(); + for(LivingEntity entity : this.beacon.getEntitiesInRange()) { + ret.add(new BukkitMCLivingEntity(entity)); + } + return ret; + } + +// @Override +// public MCPotionEffect getPrimaryEffect() { +// // TODO Implement. +// return null; +// } +// +// @Override +// public MCPotionEffect getSecondaryEffect() { +// // TODO Implement. +// return null; +// } + + @Override + public int getTier() { + return this.beacon.getTier(); + } + +// @Override +// public void setPrimaryEffect(MCPotionEffect effect) { +// this.beacon.setPrimaryEffect(effect.getHandle()); +// } +// +// @Override +// public void setSecondaryEffect(MCPotionEffect effect) { +// this.beacon.setSecondaryEffect(effect.getHandle()); +// } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCBeehive.java b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCBeehive.java new file mode 100644 index 0000000000..1cdf849406 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCBeehive.java @@ -0,0 +1,68 @@ +package com.laytonsmith.abstraction.bukkit.blocks; + +import com.laytonsmith.abstraction.MCLocation; +import com.laytonsmith.abstraction.blocks.MCBeehive; +import com.laytonsmith.abstraction.bukkit.BukkitMCLocation; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Beehive; +import org.bukkit.entity.Bee; +import org.bukkit.entity.EntitySnapshot; + +public class BukkitMCBeehive extends BukkitMCBlockState implements MCBeehive { + + Beehive bh; + + public BukkitMCBeehive(Beehive hive) { + super(hive); + this.bh = hive; + } + + @Override + public Beehive getHandle() { + return bh; + } + + @Override + public MCLocation getFlowerLocation() { + Location loc = bh.getFlower(); + if(loc == null) { + return null; + } + return new BukkitMCLocation(loc); + } + + @Override + public void setFlowerLocation(MCLocation loc) { + if(loc == null) { + bh.setFlower(null); + } else { + bh.setFlower((Location) loc.getHandle()); + } + } + + @Override + public void addBees(int count) { + try { + EntitySnapshot beeSnapshot = Bukkit.getEntityFactory().createEntitySnapshot("{id:\"minecraft:bee\"}"); + World world; + if(bh.isPlaced()) { + world = bh.getWorld(); + } else { + // use dummy world + world = Bukkit.getWorlds().get(0); + } + for(int i = 0; i < count; i++) { + bh.addEntity((Bee) beeSnapshot.createEntity(world)); + } + } catch(Exception ignore) { + // probably before 1.20.6 + } + } + + @Override + public int getEntityCount() { + return bh.getEntityCount(); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCBlock.java b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCBlock.java index 492627d55c..93348cfb19 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCBlock.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCBlock.java @@ -1,161 +1,149 @@ - - package com.laytonsmith.abstraction.bukkit.blocks; import com.laytonsmith.abstraction.MCItemStack; import com.laytonsmith.abstraction.MCLocation; import com.laytonsmith.abstraction.MCWorld; import com.laytonsmith.abstraction.blocks.MCBlock; +import com.laytonsmith.abstraction.blocks.MCBlockData; import com.laytonsmith.abstraction.blocks.MCBlockFace; import com.laytonsmith.abstraction.blocks.MCBlockState; import com.laytonsmith.abstraction.blocks.MCCommandBlock; +import com.laytonsmith.abstraction.blocks.MCDispenser; import com.laytonsmith.abstraction.blocks.MCMaterial; import com.laytonsmith.abstraction.blocks.MCSign; -import com.laytonsmith.abstraction.bukkit.BukkitMCCreatureSpawner; +import com.laytonsmith.abstraction.bukkit.BukkitConvertor; import com.laytonsmith.abstraction.bukkit.BukkitMCItemStack; import com.laytonsmith.abstraction.bukkit.BukkitMCLocation; import com.laytonsmith.abstraction.bukkit.BukkitMCMetadatable; import com.laytonsmith.abstraction.bukkit.BukkitMCWorld; import com.laytonsmith.abstraction.enums.bukkit.BukkitMCBlockFace; -import java.util.ArrayList; -import java.util.Collection; import org.bukkit.Material; import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; import org.bukkit.block.CommandBlock; -import org.bukkit.block.CreatureSpawner; +import org.bukkit.block.Dispenser; import org.bukkit.block.Sign; +import org.bukkit.block.data.BlockData; import org.bukkit.inventory.ItemStack; -/** - * - * - */ +import java.util.ArrayList; +import java.util.Collection; +import org.bukkit.block.Banner; + public class BukkitMCBlock extends BukkitMCMetadatable implements MCBlock { - Block b; + Block b; - public BukkitMCBlock(Block b){ + public BukkitMCBlock(Block b) { super(b); - this.b = b; - } - - @Override - public int getTypeId(){ - if(b == null){ - return 0; - } - return b.getTypeId(); - } - + this.b = b; + } + + public Block __Block() { + return b; + } + @Override - public byte getData(){ - return b.getData(); - } + public String toString() { + return b.toString(); + } @Override - public void setTypeId(int idata) { - b.setTypeId(idata); - } + public MCMaterial getType() { + return BukkitMCMaterial.valueOfConcrete(b.getType()); + } @Override - public void setData(byte imeta) { - b.setData(imeta); - } + public void setType(MCMaterial mat) { + b.setType((Material) mat.getHandle()); + } @Override - public void setTypeAndData(int type, byte data, boolean physics) { - b.setTypeIdAndData(type, data, physics); + public void setType(MCMaterial mat, boolean physics) { + b.setType((Material) mat.getHandle(), physics); } @Override - public MCBlockState getState() { - if(b.getState() == null){ - return null; - } - if(b.getState() instanceof CreatureSpawner){ - return new BukkitMCCreatureSpawner((CreatureSpawner)b.getState()); - } - return new BukkitMCBlockState(b.getState()); - } + public MCBlockData getBlockData() { + return new BukkitMCBlockData(b.getBlockData()); + } @Override - public MCMaterial getType() { - if(b.getType() == null){ - return null; - } - return new BukkitMCMaterial(b.getType()); - } + public void setBlockData(MCBlockData data, boolean physics) { + b.setBlockData((BlockData) data.getHandle(), physics); + } @Override - public MCWorld getWorld() { - return new BukkitMCWorld(b.getWorld()); - } + public MCBlockState getState() { + return BukkitConvertor.BukkitGetCorrectBlockState(b.getState()); + } @Override - public int getX() { - return b.getX(); - } + public MCWorld getWorld() { + return new BukkitMCWorld(b.getWorld()); + } @Override - public int getY() { - return b.getY(); - } + public int getX() { + return b.getX(); + } @Override - public int getZ() { - return b.getZ(); - } + public int getY() { + return b.getY(); + } - public Block __Block() { - return b; - } + @Override + public int getZ() { + return b.getZ(); + } @Override - public MCSign getSign() { - return new BukkitMCSign((Sign)b.getState()); - } + public MCSign getSign() { + return new BukkitMCSign((Sign) b.getState()); + } @Override - public boolean isSign() { - return (b.getType() == Material.SIGN || b.getType() == Material.SIGN_POST || b.getType() == Material.WALL_SIGN); - } - + public boolean isSign() { + return b.getState() instanceof Sign; + } + @Override public MCCommandBlock getCommandBlock() { - return new BukkitMCCommandBlock((CommandBlock)b.getState()); + return new BukkitMCCommandBlock((CommandBlock) b.getState()); } - + @Override public boolean isCommandBlock() { - return(b.getType() == Material.COMMAND); + return b.getState() instanceof CommandBlock; } @Override - public boolean isNull() { - return b == null; - } + public MCDispenser getDispenser() { + return new BukkitMCDispenser((Dispenser) b.getState()); + } @Override - public Collection getDrops() { - Collection collection = new ArrayList(); - for(ItemStack is : b.getDrops()){ - collection.add(new BukkitMCItemStack(is)); - } - return collection; - } - + public boolean isDispenser() { + return (b.getType() == Material.DISPENSER); + } + @Override - public Collection getDrops(MCItemStack tool) { - Collection collection = new ArrayList(); - for(ItemStack is : b.getDrops(((BukkitMCItemStack) tool).asItemStack())){ + public Collection getDrops() { + Collection collection = new ArrayList<>(); + for(ItemStack is : b.getDrops()) { collection.add(new BukkitMCItemStack(is)); } return collection; } @Override - public String toString() { - return b.toString(); + public Collection getDrops(MCItemStack tool) { + Collection collection = new ArrayList<>(); + for(ItemStack is : b.getDrops(((BukkitMCItemStack) tool).asItemStack())) { + collection.add(new BukkitMCItemStack(is)); + } + return collection; } @Override @@ -183,27 +171,57 @@ public boolean isBurnable() { return b.getType().isBurnable(); } + @Override + public boolean isPassable() { + return b.isPassable(); + } + + @Override + public boolean isBanner() { + return b.getState() instanceof Banner; + } + @Override public MCLocation getLocation() { return new BukkitMCLocation(b.getLocation()); } + @Override + public double getTemperature() { + return b.getTemperature(); + } + @Override public int getLightLevel() { return b.getLightLevel(); } + @Override + public int getLightFromSky() { + return b.getLightFromSky(); + } + + @Override + public int getLightFromBlocks() { + return b.getLightFromBlocks(); + } + @Override public int getBlockPower() { // this is not useful return b.getBlockPower(); } - + @Override public boolean isBlockPowered() { return b.isBlockPowered(); } + @Override + public boolean isBlockIndirectlyPowered() { + return b.isBlockIndirectlyPowered(); + } + @Override public MCBlock getRelative(MCBlockFace face) { return new BukkitMCBlock(b.getRelative(face.getModX(), face.getModY(), face.getModZ())); @@ -211,6 +229,24 @@ public MCBlock getRelative(MCBlockFace face) { @Override public MCBlockFace getFace(MCBlock block) { - return BukkitMCBlockFace.getConvertor().getAbstractedEnum(b.getFace(((BukkitMCBlock)block).b)); + return BukkitMCBlockFace.getConvertor().getAbstractedEnum(b.getFace(((BukkitMCBlock) block).b)); + } + + @Override + public boolean isEmpty() { + return b == null || b.isEmpty(); + } + + @Override + public boolean applyBoneMeal() { + return b.applyBoneMeal(BlockFace.UP); + } + + @Override + public boolean breakNaturally(MCItemStack item) { + if(item == null) { + return b.breakNaturally(null); + } + return b.breakNaturally((ItemStack) item.getHandle()); } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCBlockData.java b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCBlockData.java new file mode 100644 index 0000000000..d3de01a563 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCBlockData.java @@ -0,0 +1,45 @@ +package com.laytonsmith.abstraction.bukkit.blocks; + +import com.laytonsmith.abstraction.blocks.MCBlockData; +import com.laytonsmith.abstraction.blocks.MCMaterial; +import org.bukkit.block.data.BlockData; + +public class BukkitMCBlockData implements MCBlockData { + BlockData bd; + + protected BukkitMCBlockData() {} + + public BukkitMCBlockData(BlockData data) { + this.bd = data; + } + + @Override + public BlockData getHandle() { + return bd; + } + + @Override + public MCMaterial getMaterial() { + return BukkitMCMaterial.valueOfConcrete(bd.getMaterial()); + } + + @Override + public String getAsString() { + return bd.getAsString(); + } + + @Override + public String toString() { + return bd.toString(); + } + + @Override + public int hashCode() { + return bd.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof BlockData && bd.equals(obj); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCBlockProjectileSource.java b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCBlockProjectileSource.java index 357c4638a1..dde45f9b6c 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCBlockProjectileSource.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCBlockProjectileSource.java @@ -1,38 +1,35 @@ package com.laytonsmith.abstraction.bukkit.blocks; import com.laytonsmith.abstraction.MCEntity; -import com.laytonsmith.abstraction.MCProjectile; -import com.laytonsmith.abstraction.Velocity; +import com.laytonsmith.abstraction.entities.MCProjectile; +import com.laytonsmith.PureUtilities.Vector3D; import com.laytonsmith.abstraction.blocks.MCBlock; import com.laytonsmith.abstraction.blocks.MCBlockProjectileSource; import com.laytonsmith.abstraction.bukkit.BukkitConvertor; -import com.laytonsmith.abstraction.enums.MCProjectileType; +import com.laytonsmith.abstraction.enums.MCEntityType; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.Projectile; import org.bukkit.projectiles.BlockProjectileSource; import org.bukkit.util.Vector; -/** - * - * @author jb_aero - */ public class BukkitMCBlockProjectileSource implements MCBlockProjectileSource { BlockProjectileSource bps; + public BukkitMCBlockProjectileSource(BlockProjectileSource source) { bps = source; } - + @Override - public MCProjectile launchProjectile(MCProjectileType projectile) { - EntityType et = EntityType.valueOf(projectile.name()); + public MCProjectile launchProjectile(MCEntityType projectile) { + EntityType et = (EntityType) projectile.getConcrete(); Class c = et.getEntityClass(); Projectile proj = bps.launchProjectile(c.asSubclass(Projectile.class)); MCEntity e = BukkitConvertor.BukkitGetCorrectEntity(proj); - if (e instanceof MCProjectile) { + if(e instanceof MCProjectile) { return (MCProjectile) e; } else { return null; @@ -40,15 +37,13 @@ public MCProjectile launchProjectile(MCProjectileType projectile) { } @Override - public MCProjectile launchProjectile(MCProjectileType projectile, Velocity init) { - EntityType et = EntityType.valueOf(projectile.name()); + public MCProjectile launchProjectile(MCEntityType projectile, Vector3D init) { + EntityType et = (EntityType) projectile.getConcrete(); Class c = et.getEntityClass(); - Vector vector = new Vector(init.x, init.y, init.z); + Vector vector = new Vector(init.X(), init.Y(), init.Z()); Projectile proj = bps.launchProjectile(c.asSubclass(Projectile.class), vector); - MCEntity e = BukkitConvertor.BukkitGetCorrectEntity(proj); - - if (e instanceof MCProjectile) { + if(e instanceof MCProjectile) { return (MCProjectile) e; } else { return null; diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCBlockState.java b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCBlockState.java index 8015096c44..ffc410671f 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCBlockState.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCBlockState.java @@ -1,26 +1,22 @@ package com.laytonsmith.abstraction.bukkit.blocks; import com.laytonsmith.abstraction.MCLocation; -import com.laytonsmith.abstraction.MCMaterialData; import com.laytonsmith.abstraction.blocks.MCBlock; import com.laytonsmith.abstraction.blocks.MCBlockState; +import com.laytonsmith.abstraction.blocks.MCMaterial; import com.laytonsmith.abstraction.bukkit.BukkitMCLocation; -import com.laytonsmith.abstraction.bukkit.BukkitMCMaterialData; import com.laytonsmith.abstraction.bukkit.BukkitMCMetadatable; import org.bukkit.block.BlockState; +import org.bukkit.block.Lockable; -/** - * - * - */ public class BukkitMCBlockState extends BukkitMCMetadatable implements MCBlockState { - - BlockState bs; - public BukkitMCBlockState(BlockState state) { + BlockState bs; + + public BukkitMCBlockState(BlockState state) { super(state); - this.bs = state; - } + this.bs = state; + } @Override public BlockState getHandle() { @@ -28,14 +24,9 @@ public BlockState getHandle() { } @Override - public MCMaterialData getData() { - return new BukkitMCMaterialData(bs.getData()); - } - - @Override - public int getTypeId() { - return bs.getTypeId(); - } + public MCMaterial getType() { + return BukkitMCMaterial.valueOfConcrete(bs.getType()); + } @Override public MCBlock getBlock() { @@ -46,4 +37,42 @@ public MCBlock getBlock() { public MCLocation getLocation() { return new BukkitMCLocation(bs.getLocation()); } -} \ No newline at end of file + + @Override + public void update() { + bs.update(); + } + + @Override + public boolean isLockable() { + return bs instanceof Lockable; + } + + @Override + public boolean isLocked() { + if(bs instanceof Lockable) { + return ((Lockable) bs).isLocked(); + } else { + throw new ClassCastException("Block is not a Lockable."); + } + } + + @Override + public String getLock() { + if(bs instanceof Lockable) { + return ((Lockable) bs).getLock(); + } else { + throw new ClassCastException("Block is not a Lockable."); + } + } + + @Override + public void setLock(String key) { + if(bs instanceof Lockable) { + ((Lockable) bs).setLock(key); + bs.update(); + } else { + throw new ClassCastException("Block is not a Lockable."); + } + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCBrewingStand.java b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCBrewingStand.java new file mode 100644 index 0000000000..1277067c5e --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCBrewingStand.java @@ -0,0 +1,42 @@ +package com.laytonsmith.abstraction.bukkit.blocks; + +import com.laytonsmith.abstraction.MCBrewerInventory; +import com.laytonsmith.abstraction.blocks.MCBrewingStand; +import com.laytonsmith.abstraction.bukkit.BukkitMCBrewerInventory; + +import org.bukkit.block.BrewingStand; + +public class BukkitMCBrewingStand extends BukkitMCBlockState implements MCBrewingStand { + + private BrewingStand bs; + + public BukkitMCBrewingStand(BrewingStand block) { + super(block); + this.bs = block; + } + + @Override + public MCBrewerInventory getInventory() { + return new BukkitMCBrewerInventory(this.bs.getInventory()); + } + + @Override + public int getBrewingTime() { + return this.bs.getBrewingTime(); + } + + @Override + public int getFuelLevel() { + return this.bs.getFuelLevel(); + } + + @Override + public void setBrewingTime(int brewTime) { + this.bs.setBrewingTime(brewTime); + } + + @Override + public void setFuelLevel(int level) { + this.bs.setFuelLevel(level); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCChest.java b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCChest.java new file mode 100644 index 0000000000..78a17fe554 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCChest.java @@ -0,0 +1,21 @@ +package com.laytonsmith.abstraction.bukkit.blocks; + +import com.laytonsmith.abstraction.MCInventory; +import com.laytonsmith.abstraction.blocks.MCChest; +import com.laytonsmith.abstraction.bukkit.BukkitMCInventory; +import org.bukkit.block.Chest; + +public class BukkitMCChest extends BukkitMCContainer implements MCChest { + + private final Chest chest; + + public BukkitMCChest(Chest block) { + super(block); + this.chest = block; + } + + @Override + public MCInventory getInventory() { + return new BukkitMCInventory(this.chest.getBlockInventory()); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCCommandBlock.java b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCCommandBlock.java index d46e53e198..4367b808f1 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCCommandBlock.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCCommandBlock.java @@ -3,19 +3,15 @@ import com.laytonsmith.abstraction.blocks.MCCommandBlock; import org.bukkit.block.CommandBlock; -/** - * - * @author jb_aero - */ -public class BukkitMCCommandBlock extends BukkitMCBlockState implements - MCCommandBlock { +public class BukkitMCCommandBlock extends BukkitMCBlockState implements MCCommandBlock { CommandBlock cb; + public BukkitMCCommandBlock(CommandBlock block) { super(block); cb = block; } - + @Override public String getCommand() { return cb.getCommand(); diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCContainer.java b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCContainer.java new file mode 100644 index 0000000000..e23a5b0bf5 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCContainer.java @@ -0,0 +1,22 @@ +package com.laytonsmith.abstraction.bukkit.blocks; + +import com.laytonsmith.abstraction.MCInventory; +import com.laytonsmith.abstraction.blocks.MCContainer; +import com.laytonsmith.abstraction.bukkit.BukkitMCInventory; + +import org.bukkit.block.Container; + +public class BukkitMCContainer extends BukkitMCBlockState implements MCContainer { + + private Container cont; + + public BukkitMCContainer(Container block) { + super(block); + this.cont = block; + } + + @Override + public MCInventory getInventory() { + return new BukkitMCInventory(this.cont.getInventory()); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCDecoratedPot.java b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCDecoratedPot.java new file mode 100644 index 0000000000..142dd2e46f --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCDecoratedPot.java @@ -0,0 +1,61 @@ +package com.laytonsmith.abstraction.bukkit.blocks; + +import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.blocks.MCDecoratedPot; +import com.laytonsmith.abstraction.blocks.MCMaterial; +import com.laytonsmith.abstraction.bukkit.BukkitMCItemStack; +import org.bukkit.Material; +import org.bukkit.block.DecoratedPot; +import org.bukkit.inventory.ItemStack; + +import java.util.HashMap; +import java.util.Map; + +public class BukkitMCDecoratedPot extends BukkitMCBlockState implements MCDecoratedPot { + + DecoratedPot dp; + + public BukkitMCDecoratedPot(DecoratedPot pot) { + super(pot); + this.dp = pot; + } + + @Override + public DecoratedPot getHandle() { + return this.dp; + } + + @Override + public Map getSherds() { + Map sherds = new HashMap<>(); + for(Map.Entry sherd : this.dp.getSherds().entrySet()) { + sherds.put(MCDecoratedPot.Side.valueOf(sherd.getKey().name()), BukkitMCMaterial.valueOfConcrete(sherd.getValue())); + } + return sherds; + } + + @Override + public void setSherd(MCDecoratedPot.Side side, MCMaterial sherd) { + DecoratedPot.Side concreteSide = DecoratedPot.Side.valueOf(side.name()); + this.dp.setSherd(concreteSide, (Material) sherd.getHandle()); + } + + @Override + public MCItemStack getItemStack() { + try { + return new BukkitMCItemStack(dp.getInventory().getItem()); + } catch(NoSuchMethodError ex) { + // probably before 1.20.4 + return null; + } + } + + @Override + public void setItemStack(MCItemStack item) { + try { + dp.getInventory().setItem((ItemStack) item.getHandle()); + } catch(NoSuchMethodError ex) { + // probably before 1.20.4 + } + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCDispenser.java b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCDispenser.java new file mode 100644 index 0000000000..3b84e9cbe2 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCDispenser.java @@ -0,0 +1,33 @@ +package com.laytonsmith.abstraction.bukkit.blocks; + +import com.laytonsmith.abstraction.MCInventory; +import com.laytonsmith.abstraction.blocks.MCBlockProjectileSource; +import com.laytonsmith.abstraction.blocks.MCDispenser; +import com.laytonsmith.abstraction.bukkit.BukkitMCInventory; + +import org.bukkit.block.Dispenser; + +public class BukkitMCDispenser extends BukkitMCBlockState implements MCDispenser { + + Dispenser d; + + public BukkitMCDispenser(Dispenser block) { + super(block); + d = block; + } + + @Override + public MCBlockProjectileSource getBlockProjectileSource() { + return new BukkitMCBlockProjectileSource(d.getBlockProjectileSource()); + } + + @Override + public MCInventory getInventory() { + return new BukkitMCInventory(this.d.getInventory()); + } + + @Override + public boolean dispense() { + return this.d.dispense(); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCDropper.java b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCDropper.java new file mode 100644 index 0000000000..f658b11453 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCDropper.java @@ -0,0 +1,27 @@ +package com.laytonsmith.abstraction.bukkit.blocks; + +import com.laytonsmith.abstraction.MCInventory; +import com.laytonsmith.abstraction.blocks.MCDropper; +import com.laytonsmith.abstraction.bukkit.BukkitMCInventory; + +import org.bukkit.block.Dropper; + +public class BukkitMCDropper extends BukkitMCBlockState implements MCDropper { + + private Dropper dropper; + + public BukkitMCDropper(Dropper block) { + super(block); + this.dropper = block; + } + + @Override + public MCInventory getInventory() { + return new BukkitMCInventory(this.dropper.getInventory()); + } + + @Override + public void drop() { + this.dropper.drop(); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCEndGateway.java b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCEndGateway.java new file mode 100644 index 0000000000..bcf79d1db9 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCEndGateway.java @@ -0,0 +1,55 @@ +package com.laytonsmith.abstraction.bukkit.blocks; + +import com.laytonsmith.abstraction.MCLocation; +import com.laytonsmith.abstraction.blocks.MCEndGateway; +import com.laytonsmith.abstraction.bukkit.BukkitMCLocation; +import org.bukkit.Location; +import org.bukkit.block.EndGateway; + +public class BukkitMCEndGateway extends BukkitMCBlockState implements MCEndGateway { + + EndGateway eg; + + public BukkitMCEndGateway(EndGateway block) { + super(block); + this.eg = block; + } + + @Override + public MCLocation getExitLocation() { + Location location = this.eg.getExitLocation(); + if(location == null) { + return null; + } + return new BukkitMCLocation(location); + } + + @Override + public void setExitLocation(MCLocation location) { + if(location == null) { + this.eg.setExitLocation(null); + } else { + this.eg.setExitLocation((Location) location.getHandle()); + } + } + + @Override + public boolean isExactTeleport() { + return this.eg.isExactTeleport(); + } + + @Override + public void setExactTeleport(boolean isExact) { + this.eg.setExactTeleport(isExact); + } + + @Override + public long getAge() { + return this.eg.getAge(); + } + + @Override + public void setAge(long age) { + this.eg.setAge(age); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCFallingBlock.java b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCFallingBlock.java deleted file mode 100644 index 96b5db89cc..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCFallingBlock.java +++ /dev/null @@ -1,51 +0,0 @@ - -package com.laytonsmith.abstraction.bukkit.blocks; - -import com.laytonsmith.abstraction.blocks.MCFallingBlock; -import com.laytonsmith.abstraction.blocks.MCMaterial; -import com.laytonsmith.abstraction.bukkit.BukkitMCEntity; -import org.bukkit.entity.FallingBlock; - -/** - * - * @author import - */ -public class BukkitMCFallingBlock extends BukkitMCEntity implements MCFallingBlock { - FallingBlock f; - - public BukkitMCFallingBlock(FallingBlock f) { - super(f); - this.f = f; - } - - @Override - public byte getBlockData() { - return f.getBlockData(); - } - - @Override - public int getBlockId() { - return f.getBlockId(); - } - - @Override - public boolean getDropItem() { - return f.getDropItem(); - } - - @Override - public MCMaterial getMaterial() { - return new BukkitMCMaterial(f.getMaterial()); - } - - @Override - public void setDropItem(boolean drop) { - f.setDropItem(drop); - } - - @Override - public FallingBlock getHandle() { - return f; - } - -} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCFurnace.java b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCFurnace.java new file mode 100644 index 0000000000..abeccc8b53 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCFurnace.java @@ -0,0 +1,42 @@ +package com.laytonsmith.abstraction.bukkit.blocks; + +import com.laytonsmith.abstraction.MCFurnaceInventory; +import com.laytonsmith.abstraction.blocks.MCFurnace; +import com.laytonsmith.abstraction.bukkit.BukkitMCFurnaceInventory; + +import org.bukkit.block.Furnace; + +public class BukkitMCFurnace extends BukkitMCBlockState implements MCFurnace { + + private Furnace furnace; + + public BukkitMCFurnace(Furnace block) { + super(block); + this.furnace = block; + } + + @Override + public MCFurnaceInventory getInventory() { + return new BukkitMCFurnaceInventory(this.furnace.getInventory()); + } + + @Override + public short getBurnTime() { + return this.furnace.getBurnTime(); + } + + @Override + public void setBurnTime(short burnTime) { + this.furnace.setBurnTime(burnTime); + } + + @Override + public short getCookTime() { + return this.furnace.getCookTime(); + } + + @Override + public void setCookTime(short cookTime) { + this.furnace.setCookTime(cookTime); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCLectern.java b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCLectern.java new file mode 100644 index 0000000000..9e84f41540 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCLectern.java @@ -0,0 +1,31 @@ +package com.laytonsmith.abstraction.bukkit.blocks; + +import com.laytonsmith.abstraction.MCInventory; +import com.laytonsmith.abstraction.blocks.MCLectern; +import com.laytonsmith.abstraction.bukkit.BukkitMCInventory; +import org.bukkit.block.Lectern; + +public class BukkitMCLectern extends BukkitMCBlockState implements MCLectern { + + private Lectern l; + + public BukkitMCLectern(Lectern block) { + super(block); + this.l = block; + } + + @Override + public MCInventory getInventory() { + return new BukkitMCInventory(this.l.getInventory()); + } + + @Override + public int getPage() { + return l.getPage(); + } + + @Override + public void setPage(int page) { + l.setPage(page); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCMaterial.java b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCMaterial.java index 5c2490303f..678b8c9e38 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCMaterial.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCMaterial.java @@ -1,91 +1,189 @@ - - package com.laytonsmith.abstraction.bukkit.blocks; -import com.laytonsmith.abstraction.MCMaterialData; +import com.laytonsmith.abstraction.blocks.MCBlockData; import com.laytonsmith.abstraction.blocks.MCMaterial; -import com.laytonsmith.abstraction.bukkit.BukkitMCMaterialData; +import com.laytonsmith.core.MSLog; +import com.laytonsmith.core.Static; +import com.laytonsmith.core.constructs.Target; import org.bukkit.Material; -import org.bukkit.material.MaterialData; -/** - * - * - */ -public class BukkitMCMaterial implements MCMaterial { - Material m; +import java.util.EnumMap; +import java.util.Map; + +public class BukkitMCMaterial extends MCMaterial { + + private static final Map BUKKIT_MAP = new EnumMap<>(Material.class); + + public BukkitMCMaterial(Material type) { + this(null, type); + } + + private BukkitMCMaterial(MCVanillaMaterial vanillaMaterial, Material type) { + super(vanillaMaterial, type); + } + + @Override + public String name() { + return getAbstracted() == null ? getConcrete().name() : getAbstracted().name(); + } + + public static MCMaterial valueOfConcrete(Material test) { + MCMaterial type = BUKKIT_MAP.get(test); + if(type == null) { + MSLog.GetLogger().e(MSLog.Tags.GENERAL, "Bukkit Material missing in BUKKIT_MAP: " + test.name(), + Target.UNKNOWN); + return new BukkitMCMaterial(null, test); + } + return type; + } - public BukkitMCMaterial(Material type) { - this.m = type; - } + public static void build() { + for(MCVanillaMaterial v : MCVanillaMaterial.values()) { + if(v.existsIn(Static.getServer().getMinecraftVersion())) { + Material type; + try { + type = Material.valueOf(v.name()); + } catch (IllegalArgumentException | NoSuchFieldError ex) { + // This means something was removed or changed; MCVanillaMaterial will need an update. + MSLog.GetLogger().e(MSLog.Tags.GENERAL, "Could not find a Bukkit Material for " + v.name(), + Target.UNKNOWN); + continue; + } + BukkitMCMaterial wrapper = new BukkitMCMaterial(v, type); + BY_STRING.put(v.name(), wrapper); + BUKKIT_MAP.put(type, wrapper); + } + } + // Add missing values from Concrete. + // These values will still be accepted on an MC server, but will be missing from cmdline. + for(Material m : Material.values()) { + if(!m.isLegacy() && !BUKKIT_MAP.containsKey(m)) { + MSLog.GetLogger().w(MSLog.Tags.GENERAL, "Could not find MCMaterial for " + m.name(), Target.UNKNOWN); + BukkitMCMaterial wrapper = new BukkitMCMaterial(null, m); + BY_STRING.put(m.name(), wrapper); + BUKKIT_MAP.put(m, wrapper); + } + } + } @Override - public short getMaxDurability() { - return this.m.getMaxDurability(); - } + public MCBlockData createBlockData() { + return new BukkitMCBlockData(getHandle().createBlockData()); + } @Override - public int getType() { - return m.getId(); - } + public short getMaxDurability() { + return getHandle().getMaxDurability(); + } @Override - public MCMaterialData getData() { - return new BukkitMCMaterialData(new MaterialData(m)); + public int getType() { + return getHandle().getId(); } @Override public String getName() { - return m.name(); + return name(); } @Override - public int getMaxStackSize() { - return m.getMaxStackSize(); - } + public int getMaxStackSize() { + return getHandle().getMaxStackSize(); + } @Override public boolean hasGravity() { - return m.hasGravity(); + return getHandle().hasGravity(); } @Override public boolean isBlock() { - return m.isBlock(); + return getHandle().isBlock(); + } + + @Override + public boolean isItem() { + return getHandle().isItem(); } @Override public boolean isBurnable() { - return m.isBurnable(); + return getHandle().isBurnable(); } @Override public boolean isEdible() { - return m.isEdible(); + return getHandle().isEdible(); } @Override public boolean isFlammable() { - return m.isFlammable(); + return getHandle().isFlammable(); } @Override public boolean isOccluding() { - return m.isOccluding(); + return getHandle().isOccluding(); } @Override public boolean isRecord() { - return m.isRecord(); + return getHandle().isRecord(); } @Override public boolean isSolid() { - return m.isSolid(); + return getHandle().isSolid(); } @Override public boolean isTransparent() { - return m.isTransparent(); + return getHandle().isTransparent(); + } + + @Override + public boolean isInteractable() { + return getHandle().isInteractable(); + } + + @Override + public boolean isAir() { + return getHandle().isAir(); + } + + @Override + public boolean isLegacy() { + return getHandle().isLegacy(); + } + + @Override + public float getHardness() { + return getHandle().getHardness(); + } + + @Override + public float getBlastResistance() { + return getHandle().getBlastResistance(); + } + + @Override + public Material getHandle() { + return getConcrete(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof MCMaterial && getHandle().equals(((MCMaterial) obj).getHandle()); + } + + @Override + public int hashCode() { + return getHandle().hashCode(); + } + + @Override + public String toString() { + return name(); } + } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCSign.java b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCSign.java index e38a770f84..d4e268c488 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCSign.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCSign.java @@ -1,24 +1,19 @@ - - package com.laytonsmith.abstraction.bukkit.blocks; -import com.laytonsmith.abstraction.MCMaterialData; import com.laytonsmith.abstraction.blocks.MCSign; -import com.laytonsmith.abstraction.bukkit.BukkitMCMaterialData; +import com.laytonsmith.abstraction.blocks.MCSignText; +import com.laytonsmith.abstraction.enums.MCDyeColor; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCDyeColor; import org.bukkit.block.Sign; -/** - * - * - */ public class BukkitMCSign extends BukkitMCBlockState implements MCSign { - - Sign s; - public BukkitMCSign(Sign sign) { + Sign s; + + public BukkitMCSign(Sign sign) { super(sign); - this.s = sign; - } + this.s = sign; + } @Override public Sign getHandle() { @@ -26,24 +21,76 @@ public Sign getHandle() { } @Override - public void setLine(int i, String line1) { - s.setLine(i, line1); - s.update(); - } + public String[] getLines() { + return s.getLines(); + } + + @Override + public void setLine(int i, String line1) { + s.setLine(i, line1); + } @Override - public String getLine(int i) { - return s.getLine(i); - } + public String getLine(int i) { + return s.getLine(i); + } @Override - public MCMaterialData getData() { - return new BukkitMCMaterialData(s.getData()); - } + public boolean isGlowingText() { + try { + return s.isGlowingText(); + } catch (NoSuchMethodError ex) { + // probably before 1.17 + } + return false; + } @Override - public int getTypeId() { - return s.getTypeId(); - } - + public void setGlowingText(boolean glowing) { + try { + s.setGlowingText(glowing); + } catch (NoSuchMethodError ex) { + // probably before 1.17 + } + } + + @Override + public MCDyeColor getDyeColor() { + return BukkitMCDyeColor.getConvertor().getAbstractedEnum(s.getColor()); + } + + @Override + public void setDyeColor(MCDyeColor color) { + s.setColor(BukkitMCDyeColor.getConvertor().getConcreteEnum(color)); + } + + @Override + public boolean isWaxed() { + try { + return s.isWaxed(); + } catch(NoSuchMethodError ex) { + // probably before 1.20.1 + return false; + } + } + + @Override + public void setWaxed(boolean waxed) { + try { + s.setWaxed(waxed); + } catch(NoSuchMethodError ignore) { + // probably before 1.20.1 + } + } + + @Override + public MCSignText getBackText() { + try { + return new BukkitMCSignText(s.getSide(org.bukkit.block.sign.Side.BACK)); + } catch(NoSuchMethodError | NoClassDefFoundError ex) { + // probably before 1.20 + return null; + } + } + } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCSignText.java b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCSignText.java new file mode 100644 index 0000000000..f7d4443a9a --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCSignText.java @@ -0,0 +1,71 @@ +package com.laytonsmith.abstraction.bukkit.blocks; + +import com.laytonsmith.abstraction.blocks.MCSignText; +import com.laytonsmith.abstraction.enums.MCDyeColor; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCDyeColor; +import org.bukkit.block.sign.SignSide; + +public class BukkitMCSignText implements MCSignText { + + SignSide ss; + + public BukkitMCSignText(SignSide sign) { + this.ss = sign; + } + + @Override + public SignSide getHandle() { + return ss; + } + + @Override + public String[] getLines() { + return ss.getLines(); + } + + @Override + public void setLine(int i, String line1) { + ss.setLine(i, line1); + } + + @Override + public String getLine(int i) { + return ss.getLine(i); + } + + @Override + public boolean isGlowingText() { + return ss.isGlowingText(); + } + + @Override + public void setGlowingText(boolean glowing) { + ss.setGlowingText(glowing); + } + + @Override + public MCDyeColor getDyeColor() { + return BukkitMCDyeColor.getConvertor().getAbstractedEnum(ss.getColor()); + } + + @Override + public void setDyeColor(MCDyeColor color) { + ss.setColor(BukkitMCDyeColor.getConvertor().getConcreteEnum(color)); + } + + @Override + public String toString() { + return ss.toString(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof BukkitMCSignText && ss.equals(((BukkitMCSignText) obj).ss); + } + + @Override + public int hashCode() { + return ss.hashCode(); + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCSkull.java b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCSkull.java new file mode 100644 index 0000000000..e97b489c01 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/blocks/BukkitMCSkull.java @@ -0,0 +1,69 @@ +package com.laytonsmith.abstraction.bukkit.blocks; + +import com.laytonsmith.PureUtilities.Common.ReflectionUtils; +import com.laytonsmith.abstraction.MCOfflinePlayer; +import com.laytonsmith.abstraction.MCPlayerProfile; +import com.laytonsmith.abstraction.blocks.MCSkull; +import com.laytonsmith.abstraction.bukkit.BukkitMCOfflinePlayer; + +import com.laytonsmith.abstraction.bukkit.BukkitMCPlayerProfile; +import com.laytonsmith.abstraction.bukkit.BukkitMCServer; +import com.laytonsmith.core.Static; +import org.bukkit.OfflinePlayer; +import org.bukkit.block.Skull; + +public class BukkitMCSkull extends BukkitMCBlockState implements MCSkull { + + Skull skull; + + public BukkitMCSkull(Skull skull) { + super(skull); + this.skull = skull; + } + + @Override + public Skull getHandle() { + return this.skull; + } + + @Override + public MCOfflinePlayer getOwningPlayer() { + OfflinePlayer player = this.skull.getOwningPlayer(); + return (player == null ? null : new BukkitMCOfflinePlayer(this.skull.getOwningPlayer())); + } + + @Override + public boolean hasOwner() { + return this.skull.hasOwner(); + } + + @Override + public void setOwningPlayer(MCOfflinePlayer player) { + + // Handle resetting the skull owner. The Bukkit API does not allow this and clients will need to refresh chunks + // to see this change. + if(player == null) { + ReflectionUtils.set(this.skull.getClass(), this.skull, "profile", null); + return; + } + + // Set the skull owner. + this.skull.setOwningPlayer((OfflinePlayer) player.getHandle()); + } + + @Override + public MCPlayerProfile getPlayerProfile() { + if(((BukkitMCServer) Static.getServer()).isPaper()) { + Object profile = ReflectionUtils.invokeMethod(Skull.class, this.skull, "getPlayerProfile"); + if(profile != null) { + return new BukkitMCPlayerProfile(profile); + } + } + return null; + } + + @Override + public void setPlayerProfile(MCPlayerProfile profile) { + ReflectionUtils.invokeMethod(this.skull, "setPlayerProfile", profile.getHandle()); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCAbstractHorse.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCAbstractHorse.java new file mode 100644 index 0000000000..68a0dad0e5 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCAbstractHorse.java @@ -0,0 +1,65 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.MCInventory; +import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.bukkit.BukkitMCInventory; +import com.laytonsmith.abstraction.bukkit.BukkitMCItemStack; +import com.laytonsmith.abstraction.entities.MCAbstractHorse; +import org.bukkit.entity.AbstractHorse; +import org.bukkit.entity.Entity; +import org.bukkit.inventory.InventoryHolder; + +public class BukkitMCAbstractHorse extends BukkitMCTameable implements MCAbstractHorse { + + AbstractHorse ah; + + public BukkitMCAbstractHorse(Entity t) { + super(t); + ah = (AbstractHorse) t; + } + + @Override + public MCInventory getInventory() { + return new BukkitMCInventory(ah.getInventory()); + } + + @Override + public double getJumpStrength() { + return ah.getJumpStrength(); + } + + @Override + public void setJumpStrength(double strength) { + ah.setJumpStrength(strength); + } + + @Override + public int getDomestication() { + return ah.getDomestication(); + } + + @Override + public int getMaxDomestication() { + return ah.getMaxDomestication(); + } + + @Override + public void setDomestication(int level) { + ah.setDomestication(level); + } + + @Override + public void setMaxDomestication(int level) { + ah.setMaxDomestication(level); + } + + @Override + public void setSaddle(MCItemStack stack) { + ((InventoryHolder) ah).getInventory().setItem(0, ((BukkitMCItemStack) stack).asItemStack()); + } + + @Override + public MCItemStack getSaddle() { + return new BukkitMCItemStack(((InventoryHolder) ah).getInventory().getItem(0)); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCAgeable.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCAgeable.java new file mode 100644 index 0000000000..cd2d7ef05a --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCAgeable.java @@ -0,0 +1,46 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCAgeable; +import org.bukkit.entity.Ageable; +import org.bukkit.entity.Entity; + +public class BukkitMCAgeable extends BukkitMCLivingEntity implements MCAgeable { + + Ageable a; + + public BukkitMCAgeable(Entity be) { + super(be); + this.a = (Ageable) be; + } + + @Override + public int getAge() { + return a.getAge(); + } + + @Override + public void setAge(int age) { + a.setAge(age); + } + + @Override + public boolean isAdult() { + return a.isAdult(); + } + + @Override + public void setAdult() { + a.setAdult(); + } + + @Override + public boolean isBaby() { + return !a.isAdult(); + } + + @Override + public void setBaby() { + a.setBaby(); + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCAllay.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCAllay.java new file mode 100644 index 0000000000..4f86161508 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCAllay.java @@ -0,0 +1,27 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.MCInventory; +import com.laytonsmith.abstraction.bukkit.BukkitMCInventory; +import com.laytonsmith.abstraction.entities.MCAllay; +import org.bukkit.entity.Allay; +import org.bukkit.entity.Entity; + +public class BukkitMCAllay extends BukkitMCLivingEntity implements MCAllay { + + Allay a; + + public BukkitMCAllay(Entity allay) { + super(allay); + this.a = (Allay) allay; + } + + @Override + public Allay getHandle() { + return a; + } + + @Override + public MCInventory getInventory() { + return new BukkitMCInventory(getHandle().getInventory()); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCAnimal.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCAnimal.java new file mode 100644 index 0000000000..79e6196f79 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCAnimal.java @@ -0,0 +1,37 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCAnimal; +import org.bukkit.entity.Animals; +import org.bukkit.entity.Entity; + +import java.util.UUID; + +public class BukkitMCAnimal extends BukkitMCBreedable implements MCAnimal { + + private final Animals animal; + + public BukkitMCAnimal(Entity e) { + super(e); + this.animal = (Animals) e; + } + + @Override + public int getLoveTicks() { + return this.animal.getLoveModeTicks(); + } + + @Override + public void setLoveTicks(int ticks) { + this.animal.setLoveModeTicks(ticks); + } + + @Override + public UUID getBreedCause() { + return this.animal.getBreedCause(); + } + + @Override + public void setBreedCause(UUID cause) { + this.animal.setBreedCause(cause); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCAreaEffectCloud.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCAreaEffectCloud.java new file mode 100644 index 0000000000..2622f0d38c --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCAreaEffectCloud.java @@ -0,0 +1,212 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.PureUtilities.Common.ReflectionUtils; +import com.laytonsmith.abstraction.MCColor; +import com.laytonsmith.abstraction.MCLivingEntity; +import com.laytonsmith.abstraction.MCPotionData; +import com.laytonsmith.abstraction.MCProjectileSource; +import com.laytonsmith.abstraction.blocks.MCBlockProjectileSource; +import com.laytonsmith.abstraction.bukkit.BukkitConvertor; +import com.laytonsmith.abstraction.bukkit.BukkitMCColor; +import com.laytonsmith.abstraction.bukkit.BukkitMCPotionData; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCBlockProjectileSource; +import com.laytonsmith.abstraction.entities.MCAreaEffectCloud; +import com.laytonsmith.abstraction.enums.MCParticle; +import com.laytonsmith.abstraction.enums.MCPotionType; +import com.laytonsmith.abstraction.enums.MCVersion; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCParticle; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCPotionEffectType; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCPotionType; +import com.laytonsmith.core.Static; +import org.bukkit.Particle; +import org.bukkit.entity.AreaEffectCloud; +import org.bukkit.entity.Entity; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.potion.PotionType; +import org.bukkit.projectiles.BlockProjectileSource; +import org.bukkit.projectiles.ProjectileSource; + +import java.util.ArrayList; +import java.util.List; + +public class BukkitMCAreaEffectCloud extends BukkitMCEntity implements MCAreaEffectCloud { + + AreaEffectCloud aec; + + public BukkitMCAreaEffectCloud(Entity aec) { + super(aec); + this.aec = (AreaEffectCloud) aec; + } + + @Override + public MCPotionData getBasePotionData() { + return new BukkitMCPotionData(ReflectionUtils.invokeMethod(aec, "getBasePotionData")); + } + + @Override + public MCPotionType getBasePotionType() { + PotionType type = aec.getBasePotionType(); + if(type == null) { + return null; + } + return BukkitMCPotionType.valueOfConcrete(type); + } + + @Override + public MCColor getColor() { + return BukkitMCColor.GetMCColor(aec.getColor()); + } + + @Override + public List getCustomEffects() { + List list = new ArrayList<>(); + for(PotionEffect pe : aec.getCustomEffects()) { + list.add(new MCLivingEntity.MCEffect(BukkitMCPotionEffectType.valueOfConcrete(pe.getType()), + pe.getAmplifier(), pe.getDuration(), pe.isAmbient(), pe.hasParticles(), pe.hasIcon())); + } + return list; + } + + @Override + public int getDuration() { + return aec.getDuration(); + } + + @Override + public int getDurationOnUse() { + return aec.getDurationOnUse(); + } + + @Override + public MCParticle getParticle() { + return BukkitMCParticle.valueOfConcrete(aec.getParticle()); + } + + @Override + public float getRadius() { + return aec.getRadius(); + } + + @Override + public float getRadiusOnUse() { + return aec.getRadiusOnUse(); + } + + @Override + public float getRadiusPerTick() { + return aec.getRadiusPerTick(); + } + + @Override + public int getReapplicationDelay() { + return aec.getReapplicationDelay(); + } + + @Override + public MCProjectileSource getSource() { + ProjectileSource source = aec.getSource(); + if(source instanceof BlockProjectileSource) { + return new BukkitMCBlockProjectileSource((BlockProjectileSource) source); + } + return (MCProjectileSource) BukkitConvertor.BukkitGetCorrectEntity((Entity) source); + } + + @Override + public int getWaitTime() { + return aec.getWaitTime(); + } + + @Override + public void addCustomEffect(MCLivingEntity.MCEffect effect) { + int ticks = effect.getTicksRemaining(); + if(ticks < 0) { + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_19_4)) { + ticks = -1; + } else { + ticks = Integer.MAX_VALUE; + } + } + PotionEffectType type = (PotionEffectType) effect.getPotionEffectType().getConcrete(); + PotionEffect pe = new PotionEffect(type, ticks, effect.getStrength(), effect.isAmbient(), + effect.hasParticles(), effect.showIcon()); + aec.addCustomEffect(pe, true); + } + + @Override + public void clearCustomEffects() { + aec.clearCustomEffects(); + } + + @Override + public void setBasePotionData(MCPotionData pd) { + ReflectionUtils.invokeMethod(aec, "setBasePotionData", pd.getHandle()); + } + + @Override + public void setBasePotionType(MCPotionType type) { + if(type == null) { + aec.setBasePotionType(null); + } else { + aec.setBasePotionType((PotionType) type.getConcrete()); + } + } + + @Override + public void setColor(MCColor color) { + aec.setColor(BukkitMCColor.GetColor(color)); + } + + @Override + public void setDuration(int ticks) { + aec.setDuration(ticks); + } + + @Override + public void setDurationOnUse(int ticks) { + aec.setDurationOnUse(ticks); + } + + @Override + public void setParticle(MCParticle particle, Object data) { + Particle type = ((BukkitMCParticle) particle).getConcrete(); + aec.setParticle(type, ((BukkitMCParticle) particle).getParticleData(getLocation(), data)); + } + + @Override + public void setRadius(float radius) { + aec.setRadius(radius); + } + + @Override + public void setRadiusOnUse(float radius) { + aec.setRadiusOnUse(radius); + } + + @Override + public void setRadiusPerTick(float radius) { + aec.setRadiusPerTick(radius); + } + + @Override + public void setReapplicationDelay(int ticks) { + aec.setReapplicationDelay(ticks); + } + + @Override + public void setSource(MCProjectileSource source) { + if(source == null) { + aec.setSource(null); + } else if(source instanceof MCBlockProjectileSource) { + aec.setSource((BlockProjectileSource) source.getHandle()); + } else { + aec.setSource((ProjectileSource) source.getHandle()); + } + } + + @Override + public void setWaitTime(int ticks) { + aec.setWaitTime(ticks); + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCArmorStand.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCArmorStand.java new file mode 100644 index 0000000000..9ef47113c5 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCArmorStand.java @@ -0,0 +1,203 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.PureUtilities.Vector3D; +import com.laytonsmith.abstraction.entities.MCArmorStand; +import com.laytonsmith.abstraction.enums.MCBodyPart; +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.Entity; +import org.bukkit.util.EulerAngle; + +import java.util.EnumMap; +import java.util.Map; + +public class BukkitMCArmorStand extends BukkitMCLivingEntity implements MCArmorStand { + + ArmorStand as; + + public BukkitMCArmorStand(Entity le) { + super(le); + as = (ArmorStand) le; + } + + @Override + public Map getAllPoses() { + Map slots = new EnumMap<>(MCBodyPart.class); + for(MCBodyPart key : MCBodyPart.values()) { + switch(key) { + case Head: + slots.put(key, getHeadPose()); + break; + case Torso: + slots.put(key, getBodyPose()); + break; + case ArmLeft: + slots.put(key, getLeftArmPose()); + break; + case ArmRight: + slots.put(key, getRightArmPose()); + break; + case LegLeft: + slots.put(key, getLeftLegPose()); + break; + case LegRight: + slots.put(key, getRightLegPose()); + break; + } + } + return slots; + } + + @Override + public void setAllPoses(Map posemap) { + Vector3D pose; + for(Map.Entry part : posemap.entrySet()) { + pose = part.getValue(); + switch(part.getKey()) { + case Head: + setHeadPose(pose); + break; + case Torso: + setBodyPose(pose); + break; + case ArmLeft: + setLeftArmPose(pose); + break; + case ArmRight: + setRightArmPose(pose); + break; + case LegLeft: + setLeftLegPose(pose); + break; + case LegRight: + setRightLegPose(pose); + break; + } + } + } + + @Override + public Vector3D getBodyPose() { + EulerAngle pose = as.getBodyPose(); + return new Vector3D(pose.getX(), pose.getY(), pose.getZ()); + } + + @Override + public void setBodyPose(Vector3D pose) { + as.setBodyPose(new EulerAngle(pose.X(), pose.Y(), pose.Z())); + } + + @Override + public Vector3D getLeftArmPose() { + EulerAngle pose = as.getLeftArmPose(); + return new Vector3D(pose.getX(), pose.getY(), pose.getZ()); + } + + @Override + public void setLeftArmPose(Vector3D pose) { + as.setLeftArmPose(new EulerAngle(pose.X(), pose.Y(), pose.Z())); + } + + @Override + public Vector3D getRightArmPose() { + EulerAngle pose = as.getRightArmPose(); + return new Vector3D(pose.getX(), pose.getY(), pose.getZ()); + } + + @Override + public void setRightArmPose(Vector3D pose) { + as.setRightArmPose(new EulerAngle(pose.X(), pose.Y(), pose.Z())); + } + + @Override + public Vector3D getLeftLegPose() { + EulerAngle pose = as.getLeftLegPose(); + return new Vector3D(pose.getX(), pose.getY(), pose.getZ()); + } + + @Override + public void setLeftLegPose(Vector3D pose) { + as.setLeftLegPose(new EulerAngle(pose.X(), pose.Y(), pose.Z())); + } + + @Override + public Vector3D getRightLegPose() { + EulerAngle pose = as.getRightLegPose(); + return new Vector3D(pose.getX(), pose.getY(), pose.getZ()); + } + + @Override + public void setRightLegPose(Vector3D pose) { + as.setRightLegPose(new EulerAngle(pose.X(), pose.Y(), pose.Z())); + } + + @Override + public Vector3D getHeadPose() { + EulerAngle pose = as.getHeadPose(); + return new Vector3D(pose.getX(), pose.getY(), pose.getZ()); + } + + @Override + public void setHeadPose(Vector3D pose) { + as.setHeadPose(new EulerAngle(pose.X(), pose.Y(), pose.Z())); + } + + @Override + public boolean hasBasePlate() { + return as.hasBasePlate(); + } + + @Override + public void setHasBasePlate(boolean basePlate) { + as.setBasePlate(basePlate); + } + + @Override + public boolean hasGravity() { + return as.hasGravity(); + } + + @Override + public void setHasGravity(boolean gravity) { + as.setGravity(gravity); + } + + @Override + public boolean isVisible() { + return as.isVisible(); + } + + @Override + public void setVisible(boolean visible) { + as.setVisible(visible); + } + + @Override + public boolean hasArms() { + return as.hasArms(); + } + + @Override + public void setHasArms(boolean arms) { + as.setArms(arms); + } + + @Override + public boolean isSmall() { + return as.isSmall(); + } + + @Override + public void setSmall(boolean small) { + as.setSmall(small); + } + + @Override + public Boolean isMarker() { + return as.isMarker(); + } + + @Override + public void setMarker(boolean marker) { + as.setMarker(marker); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCArrow.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCArrow.java index 5539ee05ec..4ff1c583a1 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCArrow.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCArrow.java @@ -1,40 +1,161 @@ package com.laytonsmith.abstraction.bukkit.entities; +import com.laytonsmith.PureUtilities.Common.ReflectionUtils; +import com.laytonsmith.abstraction.MCColor; +import com.laytonsmith.abstraction.MCLivingEntity; +import com.laytonsmith.abstraction.MCPotionData; +import com.laytonsmith.abstraction.bukkit.BukkitMCColor; +import com.laytonsmith.abstraction.bukkit.BukkitMCPotionData; +import com.laytonsmith.abstraction.entities.MCArrow; +import com.laytonsmith.abstraction.enums.MCPotionType; +import com.laytonsmith.abstraction.enums.MCVersion; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCPotionEffectType; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCPotionType; +import com.laytonsmith.core.Static; +import org.bukkit.Color; +import org.bukkit.entity.AbstractArrow; import org.bukkit.entity.Arrow; +import org.bukkit.entity.Entity; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.potion.PotionType; -import com.laytonsmith.abstraction.entities.MCArrow; -import com.laytonsmith.abstraction.bukkit.BukkitMCProjectile; +import java.util.ArrayList; +import java.util.List; -/** - * - * @author Veyyn - */ public class BukkitMCArrow extends BukkitMCProjectile implements MCArrow { - private final Arrow _arrow; + private final Arrow arrow; - public BukkitMCArrow(Arrow arrow) { + public BukkitMCArrow(Entity arrow) { super(arrow); - _arrow = arrow; + this.arrow = (Arrow) arrow; } @Override public int getKnockbackStrength() { - return _arrow.getKnockbackStrength(); + return this.arrow.getKnockbackStrength(); } @Override public void setKnockbackStrength(int strength) { - _arrow.setKnockbackStrength(strength); + this.arrow.setKnockbackStrength(strength); } @Override public boolean isCritical() { - return _arrow.isCritical(); + return this.arrow.isCritical(); } @Override public void setCritical(boolean critical) { - _arrow.setCritical(critical); + this.arrow.setCritical(critical); + } + + @Override + public double getDamage() { + return this.arrow.getDamage(); + } + + @Override + public void setDamage(double damage) { + this.arrow.setDamage(damage); + } + + @Override + public MCPotionData getBasePotionData() { + return new BukkitMCPotionData(ReflectionUtils.invokeMethod(arrow, "getBasePotionData")); + } + + @Override + public MCPotionType getBasePotionType() { + PotionType type = this.arrow.getBasePotionType(); + if(type == null) { + return null; + } + return BukkitMCPotionType.valueOfConcrete(type); + } + + @Override + public List getCustomEffects() { + List list = new ArrayList<>(); + for(PotionEffect pe : arrow.getCustomEffects()) { + list.add(new MCLivingEntity.MCEffect(BukkitMCPotionEffectType.valueOfConcrete(pe.getType()), + pe.getAmplifier(), pe.getDuration(), pe.isAmbient(), pe.hasParticles(), pe.hasIcon())); + } + return list; + } + + @Override + public void addCustomEffect(MCLivingEntity.MCEffect effect) { + int ticks = effect.getTicksRemaining(); + if(ticks < 0) { + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_19_4)) { + ticks = -1; + } else { + ticks = Integer.MAX_VALUE; + } + } + PotionEffect pe = new PotionEffect((PotionEffectType) effect.getPotionEffectType().getConcrete(), + ticks, effect.getStrength(), effect.isAmbient(), effect.hasParticles(), + effect.showIcon()); + arrow.addCustomEffect(pe, true); + } + + @Override + public void clearCustomEffects() { + arrow.clearCustomEffects(); + } + + @Override + public void setBasePotionData(MCPotionData pd) { + ReflectionUtils.invokeMethod(arrow, "setBasePotionData", pd.getHandle()); + } + + @Override + public void setBasePotionType(MCPotionType type) { + if(type == null) { + this.arrow.setBasePotionType(null); + } else { + this.arrow.setBasePotionType((PotionType) type.getConcrete()); + } + } + + @Override + public int getPierceLevel() { + return arrow.getPierceLevel(); + } + + @Override + public void setPierceLevel(int level) { + arrow.setPierceLevel(level); + } + + @Override + public MCArrow.PickupStatus getPickupStatus() { + return MCArrow.PickupStatus.valueOf(arrow.getPickupStatus().name()); + } + + @Override + public void setPickupStatus(MCArrow.PickupStatus status) { + arrow.setPickupStatus(AbstractArrow.PickupStatus.valueOf(status.name())); + } + + @Override + public MCColor getColor() { + Color c = arrow.getColor(); + if(c == null) { + return null; + } + return BukkitMCColor.GetMCColor(c); + } + + @Override + public void setColor(MCColor color) { + if(color == null) { + arrow.setColor(null); + } else { + arrow.setColor(BukkitMCColor.GetColor(color)); + } } -} \ No newline at end of file +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCAxolotl.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCAxolotl.java new file mode 100644 index 0000000000..0e37973146 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCAxolotl.java @@ -0,0 +1,36 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCAxolotl; +import com.laytonsmith.abstraction.enums.MCAxolotlType; +import org.bukkit.entity.Axolotl; +import org.bukkit.entity.Entity; + +public class BukkitMCAxolotl extends BukkitMCAnimal implements MCAxolotl { + + Axolotl a; + + public BukkitMCAxolotl(Entity be) { + super(be); + this.a = (Axolotl) be; + } + + @Override + public boolean isPlayingDead() { + return a.isPlayingDead(); + } + + @Override + public void setPlayingDead(boolean playingDead) { + a.setPlayingDead(playingDead); + } + + @Override + public MCAxolotlType getAxolotlType() { + return MCAxolotlType.valueOf(a.getVariant().name()); + } + + @Override + public void setAxolotlType(MCAxolotlType type) { + a.setVariant(Axolotl.Variant.valueOf(type.name())); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCBee.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCBee.java new file mode 100644 index 0000000000..6e8b2f4bd2 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCBee.java @@ -0,0 +1,84 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.MCLocation; +import com.laytonsmith.abstraction.bukkit.BukkitMCLocation; +import com.laytonsmith.abstraction.entities.MCBee; +import org.bukkit.Location; +import org.bukkit.entity.Bee; +import org.bukkit.entity.Entity; + +public class BukkitMCBee extends BukkitMCAnimal implements MCBee { + + Bee b; + + public BukkitMCBee(Entity e) { + super(e); + this.b = (Bee) e; + } + + @Override + public MCLocation getHiveLocation() { + Location loc = b.getHive(); + if(loc == null) { + return null; + } + return new BukkitMCLocation(loc); + } + + @Override + public void setHiveLocation(MCLocation loc) { + if(loc == null) { + b.setHive(null); + } else { + b.setHive((Location) loc.getHandle()); + } + } + + @Override + public MCLocation getFlowerLocation() { + Location loc = b.getFlower(); + if(loc == null) { + return null; + } + return new BukkitMCLocation(loc); + } + + @Override + public void setFlowerLocation(MCLocation loc) { + if(loc == null) { + b.setFlower(null); + } else { + b.setFlower((Location) loc.getHandle()); + } + } + + @Override + public boolean hasNectar() { + return b.hasNectar(); + } + + @Override + public void setHasNectar(boolean nectar) { + b.setHasNectar(nectar); + } + + @Override + public boolean hasStung() { + return b.hasStung(); + } + + @Override + public void setHasStung(boolean stung) { + b.setHasStung(stung); + } + + @Override + public int getAnger() { + return b.getAnger(); + } + + @Override + public void setAnger(int ticks) { + b.setAnger(ticks); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCBlockDisplay.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCBlockDisplay.java new file mode 100644 index 0000000000..eba51affdc --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCBlockDisplay.java @@ -0,0 +1,28 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.blocks.MCBlockData; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCBlockData; +import com.laytonsmith.abstraction.entities.MCBlockDisplay; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.BlockDisplay; +import org.bukkit.entity.Entity; + +public class BukkitMCBlockDisplay extends BukkitMCDisplay implements MCBlockDisplay { + + BlockDisplay bd; + + public BukkitMCBlockDisplay(Entity e) { + super(e); + this.bd = (BlockDisplay) e; + } + + @Override + public MCBlockData getBlockData() { + return new BukkitMCBlockData(this.bd.getBlock()); + } + + @Override + public void setBlockData(MCBlockData data) { + this.bd.setBlock((BlockData) data.getHandle()); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCBoat.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCBoat.java index e40402ce6d..b67a8c6624 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCBoat.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCBoat.java @@ -1,55 +1,66 @@ package com.laytonsmith.abstraction.bukkit.entities; -import com.laytonsmith.abstraction.bukkit.BukkitMCVehicle; +import com.laytonsmith.abstraction.MCEntity; +import com.laytonsmith.abstraction.bukkit.BukkitMCServer; import com.laytonsmith.abstraction.entities.MCBoat; +import com.laytonsmith.abstraction.enums.MCTreeSpecies; +import com.laytonsmith.abstraction.enums.MCVersion; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCTreeSpecies; +import com.laytonsmith.core.Static; import org.bukkit.entity.Boat; +import org.bukkit.entity.Entity; -public class BukkitMCBoat extends BukkitMCVehicle - implements MCBoat { +public class BukkitMCBoat extends BukkitMCVehicle implements MCBoat { Boat b; - public BukkitMCBoat(Boat e) { - super(e); - this.b = e; - } - - @Override - public double getMaxSpeed() { - return b.getMaxSpeed(); - } - @Override - public void setMaxSpeed(double speed) { - b.setMaxSpeed(speed); - } - - @Override - public double getOccupiedDeclaration() { - return b.getOccupiedDeceleration(); + public BukkitMCBoat(Entity e) { + super(e); + this.b = (Boat) e; } @Override - public void setOccupiedDeclaration(double rate) { - b.setOccupiedDeceleration(rate); + public MCTreeSpecies getWoodType() { + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_19)) { + return MCTreeSpecies.valueOf(b.getBoatType().name()); + } + return BukkitMCTreeSpecies.getConvertor().getAbstractedEnum(b.getWoodType()); } @Override - public double getUnoccupiedDeclaration() { - return b.getOccupiedDeceleration(); + public void setWoodType(MCTreeSpecies type) { + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_19)) { + b.setBoatType(Boat.Type.valueOf(type.name())); + } else { + b.setWoodType(BukkitMCTreeSpecies.getConvertor().getConcreteEnum(type)); + } } @Override - public void setUnoccupiedDeclaration(double rate) { - b.setUnoccupiedDeceleration(rate); + public MCEntity getLeashHolder() { + return new BukkitMCEntity(b.getLeashHolder()); } @Override - public boolean getWorkOnLand() { - return b.getWorkOnLand(); + public boolean isLeashed() { + if(((BukkitMCServer) Static.getServer()).isPaper() + && Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_21_3) + || Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_21_10)) { + return b.isLeashed(); + } + return false; } @Override - public void setWorkOnLand(boolean workOnLand) { - b.setWorkOnLand(workOnLand); + public void setLeashHolder(MCEntity holder) { + if(((BukkitMCServer) Static.getServer()).isPaper() + && Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_21_3) + || Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_21_10)) { + if(holder == null) { + b.setLeashHolder(null); + } else { + b.setLeashHolder((Entity) holder.getHandle()); + } + } } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCBogged.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCBogged.java new file mode 100644 index 0000000000..8e91b71f89 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCBogged.java @@ -0,0 +1,12 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCBogged; +import org.bukkit.entity.Entity; + +public class BukkitMCBogged extends BukkitMCSkeleton implements MCBogged { + + public BukkitMCBogged(Entity skeleton) { + super(skeleton); + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCBreedable.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCBreedable.java new file mode 100644 index 0000000000..06e62e53fa --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCBreedable.java @@ -0,0 +1,36 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCBreedable; +import org.bukkit.entity.Breedable; +import org.bukkit.entity.Entity; + +public class BukkitMCBreedable extends BukkitMCAgeable implements MCBreedable { + + Breedable b; + + public BukkitMCBreedable(Entity be) { + super(be); + this.b = (Breedable) be; + } + + @Override + public boolean getCanBreed() { + return b.canBreed(); + } + + @Override + public void setCanBreed(boolean breed) { + b.setBreed(breed); + } + + @Override + public boolean getAgeLock() { + return b.getAgeLock(); + } + + @Override + public void setAgeLock(boolean lock) { + b.setAgeLock(lock); + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCCamel.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCCamel.java new file mode 100644 index 0000000000..046e3c409d --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCCamel.java @@ -0,0 +1,25 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCCamel; +import org.bukkit.entity.Camel; +import org.bukkit.entity.Entity; + +public class BukkitMCCamel extends BukkitMCAbstractHorse implements MCCamel { + + Camel c; + + public BukkitMCCamel(Entity t) { + super(t); + c = (Camel) t; + } + + @Override + public boolean isDashing() { + return c.isDashing(); + } + + @Override + public void setDashing(boolean dashing) { + c.setDashing(dashing); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCCat.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCCat.java new file mode 100644 index 0000000000..bc5ecc762e --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCCat.java @@ -0,0 +1,77 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.PureUtilities.Common.ReflectionUtils; +import com.laytonsmith.PureUtilities.Common.ReflectionUtils.ReflectionException; +import com.laytonsmith.abstraction.entities.MCCat; +import com.laytonsmith.abstraction.enums.MCCatType; +import com.laytonsmith.abstraction.enums.MCDyeColor; +import org.bukkit.DyeColor; +import org.bukkit.Keyed; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; +import org.bukkit.entity.Cat; +import org.bukkit.entity.Entity; + +import java.util.Locale; + +public class BukkitMCCat extends BukkitMCTameable implements MCCat { + + Cat c; + + public BukkitMCCat(Entity be) { + super(be); + this.c = (Cat) be; + } + + @Override + public MCDyeColor getCollarColor() { + return MCDyeColor.valueOf(c.getCollarColor().name()); + } + + @Override + public void setCollarColor(MCDyeColor color) { + c.setCollarColor(DyeColor.valueOf(color.name())); + } + + @Override + public boolean isSitting() { + return c.isSitting(); + } + + @Override + public void setSitting(boolean sitting) { + c.setSitting(sitting); + } + + @Override + public MCCatType getCatType() { + // changed from enum to interface in 1.21 + try { + NamespacedKey key = ReflectionUtils.invokeMethod(Keyed.class, c.getCatType(), "getKey"); + return MCCatType.valueOf(key.getKey().toUpperCase(Locale.ROOT)); + } catch(ReflectionException ex) { + // probably before 1.20.4 + return MCCatType.valueOf(ReflectionUtils.invokeMethod(Enum.class, c.getCatType(), "name")); + } + } + + @Override + public void setCatType(MCCatType type) { + try { + Cat.Type t = Registry.CAT_VARIANT.get(NamespacedKey.minecraft(type.name().toLowerCase(Locale.ROOT))); + if(t == null) { + return; + } + c.setCatType(t); + } catch(NoSuchFieldError ex) { + // probably before 1.20.4 + try { + Class cls = Class.forName("org.bukkit.entity.Cat$Type"); + c.setCatType(ReflectionUtils.invokeMethod(cls, null, "valueOf", + new Class[]{String.class}, new Object[]{type.name()})); + } catch (ClassNotFoundException exc) { + throw new RuntimeException(exc); + } + } + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCChestBoat.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCChestBoat.java new file mode 100644 index 0000000000..da7076cd38 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCChestBoat.java @@ -0,0 +1,27 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.MCInventory; +import com.laytonsmith.abstraction.bukkit.BukkitMCInventory; +import com.laytonsmith.abstraction.entities.MCChestBoat; +import org.bukkit.entity.ChestBoat; +import org.bukkit.entity.Entity; + +public class BukkitMCChestBoat extends BukkitMCBoat implements MCChestBoat { + + ChestBoat cb; + + public BukkitMCChestBoat(Entity cb) { + super(cb); + this.cb = (ChestBoat) cb; + } + + @Override + public ChestBoat getHandle() { + return this.cb; + } + + @Override + public MCInventory getInventory() { + return new BukkitMCInventory(this.cb.getInventory()); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCChestedHorse.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCChestedHorse.java new file mode 100644 index 0000000000..6f967ffe05 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCChestedHorse.java @@ -0,0 +1,25 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCChestedHorse; +import org.bukkit.entity.ChestedHorse; +import org.bukkit.entity.Entity; + +public class BukkitMCChestedHorse extends BukkitMCAbstractHorse implements MCChestedHorse { + + ChestedHorse ch; + + public BukkitMCChestedHorse(Entity t) { + super(t); + ch = (ChestedHorse) t; + } + + @Override + public boolean hasChest() { + return ch.isCarryingChest(); + } + + @Override + public void setHasChest(boolean hasChest) { + ch.setCarryingChest(hasChest); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCCommandMinecart.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCCommandMinecart.java index aaa67bbaf9..f16f6bf787 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCCommandMinecart.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCCommandMinecart.java @@ -1,22 +1,25 @@ package com.laytonsmith.abstraction.bukkit.entities; import com.laytonsmith.abstraction.entities.MCCommandMinecart; +import java.util.ArrayList; +import java.util.List; +import org.bukkit.entity.Entity; import org.bukkit.entity.minecart.CommandMinecart; -public class BukkitMCCommandMinecart extends BukkitMCMinecart - implements MCCommandMinecart { +public class BukkitMCCommandMinecart extends BukkitMCMinecart implements MCCommandMinecart { CommandMinecart cm; - public BukkitMCCommandMinecart(CommandMinecart e) { + + public BukkitMCCommandMinecart(Entity e) { super(e); - this.cm = e; + this.cm = (CommandMinecart) e; } @Override public String getCommand() { return cm.getCommand(); } - + @Override public String getName() { return cm.getName(); @@ -31,4 +34,36 @@ public void setName(String name) { public void setCommand(String cmd) { cm.setCommand(cmd); } + + @Override + public void sendMessage(String string) { + cm.sendMessage(string); + } + + @Override + public boolean isOp() { + return cm.isOp(); + } + + @Override + public boolean hasPermission(String perm) { + return cm.hasPermission(perm); + } + + @Override + public boolean isPermissionSet(String perm) { + return cm.isPermissionSet(perm); + } + + @Override + public List getGroups() { + // CommandMinecarts cannot be in a group. + return new ArrayList(); + } + + @Override + public boolean inGroup(String groupName) { + // CommandMinecarts cannot be in a group. + return false; + } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCComplexEntityPart.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCComplexEntityPart.java index c4e77ebfa2..21f25e3cb6 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCComplexEntityPart.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCComplexEntityPart.java @@ -1,28 +1,24 @@ package com.laytonsmith.abstraction.bukkit.entities; import com.laytonsmith.abstraction.bukkit.BukkitConvertor; -import com.laytonsmith.abstraction.bukkit.BukkitMCEntity; import com.laytonsmith.abstraction.entities.MCComplexEntityPart; import com.laytonsmith.abstraction.entities.MCComplexLivingEntity; import org.bukkit.entity.ComplexEntityPart; +import org.bukkit.entity.Entity; -/** - * - * @author Hekta - */ public class BukkitMCComplexEntityPart extends BukkitMCEntity implements MCComplexEntityPart { - public BukkitMCComplexEntityPart(ComplexEntityPart part) { + public BukkitMCComplexEntityPart(Entity part) { super(part); } @Override public ComplexEntityPart getHandle() { - return (ComplexEntityPart)super.getHandle(); + return (ComplexEntityPart) super.getHandle(); } @Override public MCComplexLivingEntity getParent() { return (MCComplexLivingEntity) BukkitConvertor.BukkitGetCorrectEntity(getHandle().getParent()); } -} \ No newline at end of file +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCComplexLivingEntity.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCComplexLivingEntity.java index f4970d9be0..65badf6a94 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCComplexLivingEntity.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCComplexLivingEntity.java @@ -1,35 +1,32 @@ package com.laytonsmith.abstraction.bukkit.entities; import com.laytonsmith.abstraction.bukkit.BukkitConvertor; -import com.laytonsmith.abstraction.bukkit.BukkitMCLivingEntity; import com.laytonsmith.abstraction.entities.MCComplexEntityPart; import com.laytonsmith.abstraction.entities.MCComplexLivingEntity; -import java.util.HashSet; -import java.util.Set; import org.bukkit.entity.ComplexEntityPart; import org.bukkit.entity.ComplexLivingEntity; +import org.bukkit.entity.Entity; + +import java.util.HashSet; +import java.util.Set; -/** - * - * @author Hekta - */ public class BukkitMCComplexLivingEntity extends BukkitMCLivingEntity implements MCComplexLivingEntity { - public BukkitMCComplexLivingEntity(ComplexLivingEntity complex) { + public BukkitMCComplexLivingEntity(Entity complex) { super(complex); } @Override public ComplexLivingEntity getHandle() { - return (ComplexLivingEntity)super.getHandle(); + return (ComplexLivingEntity) super.getHandle(); } @Override public Set getParts() { Set parts = new HashSet<>(); - for (ComplexEntityPart part : getHandle().getParts()) { + for(ComplexEntityPart part : getHandle().getParts()) { parts.add((MCComplexEntityPart) BukkitConvertor.BukkitGetCorrectEntity(part)); } return parts; } -} \ No newline at end of file +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCCreeper.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCCreeper.java index afd15c84ee..53b2a36f3e 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCCreeper.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCCreeper.java @@ -1,25 +1,21 @@ - package com.laytonsmith.abstraction.bukkit.entities; import com.laytonsmith.abstraction.AbstractionObject; -import com.laytonsmith.abstraction.bukkit.BukkitMCLivingEntity; import com.laytonsmith.abstraction.entities.MCCreeper; import org.bukkit.entity.Creeper; +import org.bukkit.entity.Entity; -/** - * - * @author Jason Unger - */ public class BukkitMCCreeper extends BukkitMCLivingEntity implements MCCreeper { + Creeper creeper; - - public BukkitMCCreeper(Creeper c) { + + public BukkitMCCreeper(Entity c) { super(c); - creeper = c; + creeper = (Creeper) c; } - + public BukkitMCCreeper(AbstractionObject ao) { - this ((Creeper) ao.getHandle()); + this((Creeper) ao.getHandle()); } @Override @@ -31,4 +27,34 @@ public boolean isPowered() { public void setPowered(boolean powered) { creeper.setPowered(powered); } + + @Override + public int getMaxFuseTicks() { + return creeper.getMaxFuseTicks(); + } + + @Override + public void setMaxFuseTicks(int ticks) { + creeper.setMaxFuseTicks(ticks); + } + + @Override + public int getFuseTicks() { + return creeper.getFuseTicks(); + } + + @Override + public void setFuseTicks(int ticks) { + creeper.setFuseTicks(ticks); + } + + @Override + public int getExplosionRadius() { + return creeper.getExplosionRadius(); + } + + @Override + public void setExplosionRadius(int radius) { + creeper.setExplosionRadius(radius); + } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCDisplay.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCDisplay.java new file mode 100644 index 0000000000..71549c7089 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCDisplay.java @@ -0,0 +1,169 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.MCColor; +import com.laytonsmith.abstraction.bukkit.BukkitMCColor; +import com.laytonsmith.abstraction.entities.MCDisplay; +import com.laytonsmith.abstraction.entities.MCTransformation; +import org.bukkit.Color; +import org.bukkit.entity.Display; +import org.bukkit.entity.Entity; +import org.joml.Matrix4f; + +public class BukkitMCDisplay extends BukkitMCEntity implements MCDisplay { + + Display d; + + public BukkitMCDisplay(Entity e) { + super(e); + this.d = (Display) e; + } + + @Override + public MCDisplay.Billboard getBillboard() { + return MCDisplay.Billboard.valueOf(this.d.getBillboard().name()); + } + + @Override + public void setBillboard(MCDisplay.Billboard billboard) { + this.d.setBillboard(Display.Billboard.valueOf(billboard.name())); + } + + @Override + public MCDisplay.Brightness getBrightness() { + Display.Brightness brightness = this.d.getBrightness(); + if(brightness == null) { + return null; + } + return new MCDisplay.Brightness(brightness.getBlockLight(), brightness.getSkyLight()); + } + + @Override + public void setBrightness(MCDisplay.Brightness brightness) { + if(brightness == null) { + this.d.setBrightness(null); + } else { + this.d.setBrightness(new Display.Brightness(brightness.block(), brightness.sky())); + } + } + + @Override + public MCColor getGlowColorOverride() { + Color color = this.d.getGlowColorOverride(); + if(color == null) { + return null; + } + return BukkitMCColor.GetMCColor(color); + } + + @Override + public void setGlowColorOverride(MCColor color) { + if(color == null) { + this.d.setGlowColorOverride(null); + } else { + this.d.setGlowColorOverride(BukkitMCColor.GetColor(color)); + } + } + + @Override + public float getDisplayHeight() { + return this.d.getDisplayHeight(); + } + + @Override + public void setDisplayHeight(float height) { + this.d.setDisplayHeight(height); + } + + @Override + public float getDisplayWidth() { + return this.d.getDisplayWidth(); + } + + @Override + public void setDisplayWidth(float width) { + this.d.setDisplayWidth(width); + } + + @Override + public int getInterpolationDurationTicks() { + return this.d.getInterpolationDuration(); + } + + @Override + public void setInterpolationDurationTicks(int ticks) { + this.d.setInterpolationDuration(ticks); + } + + @Override + public int getInterpolationDelayTicks() { + return this.d.getInterpolationDelay(); + } + + @Override + public void setInterpolationDelayTicks(int ticks) { + this.d.setInterpolationDelay(ticks); + } + + @Override + public int getTeleportDuration() { + return this.d.getTeleportDuration(); + } + + @Override + public void setTeleportDuration(int ticks) { + this.d.setTeleportDuration(ticks); + } + + @Override + public float getShadowRadius() { + return this.d.getShadowRadius(); + } + + @Override + public void setShadowRadius(float radius) { + this.d.setShadowRadius(radius); + } + + @Override + public float getShadowStrength() { + return this.d.getShadowStrength(); + } + + @Override + public void setShadowStrength(float strength) { + this.d.setShadowStrength(strength); + } + + @Override + public float getViewRange() { + return this.d.getViewRange(); + } + + @Override + public void setViewRange(float range) { + this.d.setViewRange(range); + } + + @Override + public MCTransformation getTransformation() { + return new BukkitMCTransformation(this.d.getTransformation()); + } + + @Override + public void setTransformation(MCTransformation transformation) { + this.d.setTransformation(((BukkitMCTransformation) transformation).transformation); + } + + @Override + public void setTransformationMatrix(float[] mtrxf) { + // Note that the order of the matrix is flipped about the identity of the + // matrix. This allows us to accept inputs that are the same order as the + // /data command would accept. + Matrix4f matrix = new Matrix4f( + mtrxf[0], mtrxf[4], mtrxf[8], mtrxf[12], + mtrxf[1], mtrxf[5], mtrxf[9], mtrxf[13], + mtrxf[2], mtrxf[6], mtrxf[10], mtrxf[14], + mtrxf[3], mtrxf[7], mtrxf[11], mtrxf[15]); + this.d.setTransformationMatrix(matrix); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCDonkey.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCDonkey.java new file mode 100644 index 0000000000..e3cd5b1f46 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCDonkey.java @@ -0,0 +1,12 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCDonkey; +import org.bukkit.entity.Entity; + +public class BukkitMCDonkey extends BukkitMCChestedHorse implements MCDonkey { + + public BukkitMCDonkey(Entity t) { + super(t); + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCDrowned.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCDrowned.java new file mode 100644 index 0000000000..97501cfd77 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCDrowned.java @@ -0,0 +1,12 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCDrowned; +import org.bukkit.entity.Entity; + +public class BukkitMCDrowned extends BukkitMCZombie implements MCDrowned { + + public BukkitMCDrowned(Entity zombie) { + super(zombie); + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCElderGuardian.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCElderGuardian.java new file mode 100644 index 0000000000..fd10fc167e --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCElderGuardian.java @@ -0,0 +1,12 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCElderGuardian; +import org.bukkit.entity.Entity; + +public class BukkitMCElderGuardian extends BukkitMCGuardian implements MCElderGuardian { + + public BukkitMCElderGuardian(Entity ent) { + super(ent); + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCEnderCrystal.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCEnderCrystal.java new file mode 100644 index 0000000000..57fa353d87 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCEnderCrystal.java @@ -0,0 +1,46 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCEnderCrystal; +import com.laytonsmith.abstraction.MCLocation; +import com.laytonsmith.abstraction.bukkit.BukkitMCLocation; +import org.bukkit.Location; +import org.bukkit.entity.EnderCrystal; +import org.bukkit.entity.Entity; + +public class BukkitMCEnderCrystal extends BukkitMCEntity implements MCEnderCrystal { + + EnderCrystal ec; + + public BukkitMCEnderCrystal(Entity ec) { + super(ec); + this.ec = (EnderCrystal) ec; + } + + @Override + public boolean isShowingBottom() { + return ec.isShowingBottom(); + } + + @Override + public void setShowingBottom(boolean showing) { + ec.setShowingBottom(showing); + } + + @Override + public MCLocation getBeamTarget() { + Location target = ec.getBeamTarget(); + if(target == null) { + return null; + } + return new BukkitMCLocation(target); + } + + @Override + public void setBeamTarget(MCLocation target) { + if(target == null) { + ec.setBeamTarget(null); + } else { + ec.setBeamTarget((Location) target.getHandle()); + } + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCEnderDragon.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCEnderDragon.java index 6a8c6c24ea..17e4ba1cff 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCEnderDragon.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCEnderDragon.java @@ -2,16 +2,18 @@ import com.laytonsmith.abstraction.AbstractionObject; import com.laytonsmith.abstraction.entities.MCEnderDragon; +import com.laytonsmith.abstraction.enums.MCEnderDragonPhase; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCEnderDragonPhase; import org.bukkit.entity.EnderDragon; +import org.bukkit.entity.Entity; -/** - * - * @author Hekta - */ public class BukkitMCEnderDragon extends BukkitMCComplexLivingEntity implements MCEnderDragon { - public BukkitMCEnderDragon(EnderDragon dragon) { - super(dragon); + EnderDragon ed; + + public BukkitMCEnderDragon(Entity ent) { + super(ent); + ed = (EnderDragon) ent; } public BukkitMCEnderDragon(AbstractionObject ao) { @@ -20,6 +22,16 @@ public BukkitMCEnderDragon(AbstractionObject ao) { @Override public EnderDragon getHandle() { - return (EnderDragon)super.getHandle(); + return ed; + } + + @Override + public MCEnderDragonPhase getPhase() { + return MCEnderDragonPhase.valueOf(ed.getPhase().name()); + } + + @Override + public void setPhase(MCEnderDragonPhase phase) { + ed.setPhase(BukkitMCEnderDragonPhase.getConvertor().getConcreteEnum(phase)); } -} \ No newline at end of file +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCEnderDragonPart.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCEnderDragonPart.java index 223ffe7e52..e4e4f2508d 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCEnderDragonPart.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCEnderDragonPart.java @@ -3,14 +3,11 @@ import com.laytonsmith.abstraction.AbstractionObject; import com.laytonsmith.abstraction.entities.MCEnderDragonPart; import org.bukkit.entity.EnderDragonPart; +import org.bukkit.entity.Entity; -/** - * - * @author Hekta - */ public class BukkitMCEnderDragonPart extends BukkitMCComplexEntityPart implements MCEnderDragonPart { - public BukkitMCEnderDragonPart(EnderDragonPart part) { + public BukkitMCEnderDragonPart(Entity part) { super(part); } @@ -20,6 +17,6 @@ public BukkitMCEnderDragonPart(AbstractionObject ao) { @Override public EnderDragonPart getHandle() { - return (EnderDragonPart)super.getHandle(); + return (EnderDragonPart) super.getHandle(); } -} \ No newline at end of file +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCEnderSignal.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCEnderSignal.java index d3cab0232f..e81d42326e 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCEnderSignal.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCEnderSignal.java @@ -1,16 +1,65 @@ package com.laytonsmith.abstraction.bukkit.entities; -import com.laytonsmith.abstraction.bukkit.BukkitMCEntity; +import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.MCLocation; +import com.laytonsmith.abstraction.bukkit.BukkitMCItemStack; +import com.laytonsmith.abstraction.bukkit.BukkitMCLocation; import com.laytonsmith.abstraction.entities.MCEnderSignal; +import org.bukkit.Location; import org.bukkit.entity.EnderSignal; +import org.bukkit.entity.Entity; +import org.bukkit.inventory.ItemStack; -public class BukkitMCEnderSignal extends BukkitMCEntity implements - MCEnderSignal { +public class BukkitMCEnderSignal extends BukkitMCEntity implements MCEnderSignal { EnderSignal es; - public BukkitMCEnderSignal(EnderSignal e) { + + public BukkitMCEnderSignal(Entity e) { super(e); - this.es = e; + this.es = (EnderSignal) e; + } + + @Override + public int getDespawnTicks() { + return es.getDespawnTimer(); + } + + @Override + public void setDespawnTicks(int ticks) { + es.setDespawnTimer(ticks); + } + + @Override + public boolean getDropItem() { + return es.getDropItem(); + } + + @Override + public void setDropItem(boolean drop) { + es.setDropItem(drop); + } + + @Override + public MCLocation getTargetLocation() { + return new BukkitMCLocation(es.getTargetLocation()); + } + + @Override + public void setTargetLocation(MCLocation loc) { + es.setTargetLocation((Location) loc.getHandle()); + } + + @Override + public MCItemStack getItem() { + return new BukkitMCItemStack(es.getItem()); } + @Override + public void setItem(MCItemStack stack) { + if(stack == null) { + es.setItem(null); + } else { + es.setItem((ItemStack) stack.getHandle()); + } + } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCEnderman.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCEnderman.java index b1e152c4a7..e2f70b8f0d 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCEnderman.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCEnderman.java @@ -1,42 +1,29 @@ package com.laytonsmith.abstraction.bukkit.entities; -import com.laytonsmith.abstraction.MCMaterialData; -import com.laytonsmith.abstraction.bukkit.BukkitMCLivingEntity; -import com.laytonsmith.abstraction.bukkit.BukkitMCMaterialData; +import com.laytonsmith.abstraction.blocks.MCBlockData; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCBlockData; import com.laytonsmith.abstraction.entities.MCEnderman; +import org.bukkit.block.data.BlockData; import org.bukkit.entity.Enderman; -import org.bukkit.material.MaterialData; +import org.bukkit.entity.Entity; + +public class BukkitMCEnderman extends BukkitMCLivingEntity implements MCEnderman { -public class BukkitMCEnderman extends BukkitMCLivingEntity implements - MCEnderman { Enderman e; - public BukkitMCEnderman(Enderman ent) { + + public BukkitMCEnderman(Entity ent) { super(ent); - e = ent; + e = (Enderman) ent; } @Override - public MCMaterialData getCarriedMaterial() { - return new BukkitMCMaterialData(e.getCarriedMaterial()); - } - - @Override - public int getCarriedType() { - return e.getCarriedMaterial().getItemTypeId(); - } - - @Override - public byte getCarriedData() { - return e.getCarriedMaterial().getData(); + public MCBlockData getCarriedMaterial() { + BlockData data = e.getCarriedBlock(); + return (data == null ? null : new BukkitMCBlockData(data)); } @Override - public void setCarriedMaterial(MCMaterialData held) { - e.setCarriedMaterial((MaterialData) held.getHandle()); - } - - @Override - public void setCarriedMaterial(int type, byte data) { - e.setCarriedMaterial(new MaterialData(type, data)); + public void setCarriedMaterial(MCBlockData held) { + e.setCarriedBlock((BlockData) held.getHandle()); } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCEntity.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCEntity.java new file mode 100644 index 0000000000..b83e96dd3f --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCEntity.java @@ -0,0 +1,450 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.PureUtilities.Common.ReflectionUtils; +import com.laytonsmith.PureUtilities.Vector3D; +import com.laytonsmith.abstraction.MCEntity; +import com.laytonsmith.abstraction.MCLivingEntity; +import com.laytonsmith.abstraction.MCLocation; +import com.laytonsmith.abstraction.MCServer; +import com.laytonsmith.abstraction.MCWorld; +import com.laytonsmith.abstraction.bukkit.BukkitConvertor; +import com.laytonsmith.abstraction.bukkit.BukkitMCLocation; +import com.laytonsmith.abstraction.bukkit.BukkitMCMetadatable; +import com.laytonsmith.abstraction.bukkit.BukkitMCServer; +import com.laytonsmith.abstraction.bukkit.BukkitMCWorld; +import com.laytonsmith.abstraction.bukkit.events.BukkitEntityEvents; +import com.laytonsmith.abstraction.enums.MCEntityEffect; +import com.laytonsmith.abstraction.enums.MCEntityType; +import com.laytonsmith.abstraction.enums.MCPose; +import com.laytonsmith.abstraction.enums.MCTeleportCause; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCEntityType; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCPose; +import com.laytonsmith.abstraction.events.MCEntityDamageEvent; +import io.papermc.paper.entity.TeleportFlag; +import org.bukkit.EntityEffect; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Tameable; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; +import org.bukkit.util.Vector; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import org.bukkit.Location; + +public class BukkitMCEntity extends BukkitMCMetadatable implements MCEntity { + + private static final boolean USE_RETAIN_PASSENGERS; + + static { + boolean useRetainPassengers = false; + try { + Class clz = Class.forName("io.papermc.paper.entity.TeleportFlag$EntityState"); + if(clz.getAnnotation(Deprecated.class) == null) { + useRetainPassengers = true; + } + } catch(ClassNotFoundException ignored) {} + USE_RETAIN_PASSENGERS = useRetainPassengers; + } + + private final Entity e; + + public BukkitMCEntity(Entity e) { + super(e); + this.e = e; + } + + @Override + public boolean eject() { + return e.eject(); + } + + @Override + public float getFallDistance() { + return e.getFallDistance(); + } + + @Override + public int getFireTicks() { + return e.getFireTicks(); + } + + @Override + public Entity getHandle() { + return e; + } + + @Override + public MCEntityDamageEvent getLastDamageCause() { + EntityDamageEvent ldc = e.getLastDamageCause(); + if(ldc == null) { + return null; + } + if(ldc instanceof EntityDamageByEntityEvent) { + return new BukkitEntityEvents.BukkitMCEntityDamageByEntityEvent(ldc); + } + return new BukkitEntityEvents.BukkitMCEntityDamageEvent(ldc); + } + + public MCLivingEntity getLivingEntity() { + if(e instanceof LivingEntity) { + return new BukkitMCLivingEntity(e); + } + return null; + } + + @Override + public MCLocation getLocation() { + return new BukkitMCLocation(e.getLocation()); + } + + @Override + public int getMaxFireTicks() { + return e.getMaxFireTicks(); + } + + @Override + public List getNearbyEntities(double x, double y, double z) { + List lst = e.getNearbyEntities(x, y, z); + List retn = new ArrayList<>(); + + for(Entity e : lst) { + retn.add(BukkitConvertor.BukkitGetCorrectEntity(e)); + } + + return retn; + } + + @Override + public List getPassengers() { + List passengers = new ArrayList<>(); + for(Entity passenger : e.getPassengers()) { + passengers.add(BukkitConvertor.BukkitGetCorrectEntity(passenger)); + } + return passengers; + } + + @Override + public MCServer getServer() { + return new BukkitMCServer(e.getServer()); + } + + @Override + public int getTicksLived() { + return e.getTicksLived(); + } + + @Override + public MCEntityType getType() { + return BukkitMCEntityType.valueOfConcrete(e.getType()); + } + + @Override + public UUID getUniqueId() { + return e.getUniqueId(); + } + + @Override + public MCEntity getVehicle() { + return BukkitConvertor.BukkitGetCorrectEntity(e.getVehicle()); + } + + @Override + public Vector3D getVelocity() { + Vector v = e.getVelocity(); + return new Vector3D(v.getX(), v.getY(), v.getZ()); + } + + @Override + public MCWorld getWorld() { + return new BukkitMCWorld(e.getWorld()); + } + + @Override + public boolean isDead() { + return e.isDead(); + } + + @Override + public boolean isEmpty() { + return e.isEmpty(); + } + + @Override + public boolean isInsideVehicle() { + return e.isInsideVehicle(); + } + + @Override + public boolean isOnGround() { + return e.isOnGround(); + } + + public boolean isLivingEntity() { + return e instanceof LivingEntity; + } + + public boolean isTameable() { + return e instanceof Tameable; + } + + @Override + public boolean leaveVehicle() { + return e.leaveVehicle(); + } + + @Override + public void playEffect(MCEntityEffect type) { + try { + e.playEffect(EntityEffect.valueOf(type.name())); + } catch(IllegalArgumentException ignore) { + // Likely a non-applicable effect (enforced in Paper 1.21.4+) + // Do nothing, as per documentation. + } + } + + @Override + public void remove() { + e.remove(); + } + + @Override + public boolean savesOnUnload() { + return e.isPersistent(); + } + + @Override + public void setSavesOnUnload(boolean saves) { + e.setPersistent(saves); + } + + @Override + public void setFallDistance(float distance) { + e.setFallDistance(distance); + } + + @Override + public void setFireTicks(int ticks) { + e.setFireTicks(ticks); + } + + @Override + public boolean setPassenger(MCEntity passenger) { + return e.addPassenger((Entity) passenger.getHandle()); + } + + @Override + public void setTicksLived(int value) { + e.setTicksLived(value); + } + + @Override + public void setVelocity(Vector3D velocity) { + Vector v = new Vector(velocity.X(), velocity.Y(), velocity.Z()); + e.setVelocity(v); + } + + @Override + public boolean teleport(MCEntity destination) { + return this.teleport(destination.getLocation(), MCTeleportCause.PLUGIN); + } + + @Override + public boolean teleport(MCEntity destination, MCTeleportCause cause) { + return this.teleport(destination.getLocation(), cause); + } + + @Override + public boolean teleport(MCLocation location) { + return this.teleport(location, MCTeleportCause.PLUGIN); + } + + @Override + public boolean teleport(MCLocation location, MCTeleportCause cause) { + Location l = (Location) location.getHandle(); + TeleportCause c = TeleportCause.valueOf(cause.name()); + if(USE_RETAIN_PASSENGERS) { + // Paper requires a third parameter to maintain consistent behavior when teleporting entities with passengers. + // TeleportFlag.EntityState added in 1.19.3 but RETAIN_PASSENGERS was made default in 1.21.10. + TeleportFlag teleportFlag = TeleportFlag.EntityState.RETAIN_PASSENGERS; + // Paper only method: e.teleport(l, c, teleportFlag); + return ReflectionUtils.invokeMethod(Entity.class, e, "teleport", + new Class[] { + org.bukkit.Location.class, + org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.class, + TeleportFlag[].class + }, + new Object[] { l, c, new TeleportFlag[] { teleportFlag } }); + } + return e.teleport(l, c); + } + + @Override + public String getCustomName() { + return e.getCustomName(); + } + + @Override + public boolean isCustomNameVisible() { + return e.isCustomNameVisible(); + } + + @Override + public void setCustomName(String name) { + e.setCustomName(name); + } + + @Override + public void setCustomNameVisible(boolean visible) { + e.setCustomNameVisible(visible); + } + + @Override + public boolean isGlowing() { + return e.isGlowing(); + } + + @Override + public void setGlowing(Boolean glow) { + e.setGlowing(glow); + } + + @Override + public boolean hasGravity() { + return e.hasGravity(); + } + + @Override + public void setHasGravity(boolean gravity) { + e.setGravity(gravity); + } + + @Override + public boolean isSilent() { + return e.isSilent(); + } + + @Override + public void setSilent(boolean silent) { + e.setSilent(silent); + } + + @Override + public boolean isInvulnerable() { + return e.isInvulnerable(); + } + + @Override + public void setInvulnerable(boolean invulnerable) { + e.setInvulnerable(invulnerable); + } + + @Override + public Set getScoreboardTags() { + return e.getScoreboardTags(); + } + + @Override + public boolean hasScoreboardTag(String tag) { + return e.getScoreboardTags().contains(tag); + } + + @Override + public boolean addScoreboardTag(String tag) { + return e.addScoreboardTag(tag); + } + + @Override + public boolean removeScoreboardTag(String tag) { + return e.removeScoreboardTag(tag); + } + + @Override + public int getFreezingTicks() { + try { + return e.getFreezeTicks(); + } catch (NoSuchMethodError ex) { + // prior to 1.17 + return 0; + } + } + + @Override + public void setFreezingTicks(int ticks) { + try { + if(ticks > e.getMaxFreezeTicks()) { + ticks = e.getMaxFreezeTicks(); + } + e.setFreezeTicks(ticks); + } catch (NoSuchMethodError ex) { + // prior to 1.17 + } + } + + @Override + public String toString() { + return e.toString(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof BukkitMCEntity && e.equals(((BukkitMCEntity) obj).getHandle()); + } + + @Override + public int hashCode() { + return e.hashCode(); + } + + @Override + public int getEntityId() { + return e.getEntityId(); + } + + @Override + public boolean isInWater() { + return e.isInWater(); + } + + @Override + public void setRotation(float yaw, float pitch) { + e.setRotation(yaw, pitch); + } + + @Override + public boolean isVisibleByDefault() { + try { + return e.isVisibleByDefault(); + } catch (NoSuchMethodError ex) { + // probably before 1.19.3 + return true; + } + } + + @Override + public void setVisibleByDefault(boolean visible) { + try { + e.setVisibleByDefault(visible); + } catch (NoSuchMethodError ex) { + // probably before 1.19.3 + } + } + + @Override + public MCPose getPose() { + return BukkitMCPose.getConvertor().getAbstractedEnum(e.getPose()); + } + + @Override + public void setPose(MCPose pose, boolean fixed) { + try { + e.setPose(BukkitMCPose.getConvertor().getConcreteEnum(pose), fixed); + } catch (NoSuchMethodError ex) { + // either Spigot or prior to 1.20.1 + } + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCEntityProjectileSource.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCEntityProjectileSource.java index 79c675258a..35bc0b23c2 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCEntityProjectileSource.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCEntityProjectileSource.java @@ -1,12 +1,9 @@ package com.laytonsmith.abstraction.bukkit.entities; -import com.laytonsmith.abstraction.MCEntity; -import com.laytonsmith.abstraction.MCProjectile; +import com.laytonsmith.PureUtilities.Vector3D; +import com.laytonsmith.abstraction.entities.MCProjectile; import com.laytonsmith.abstraction.MCProjectileSource; -import com.laytonsmith.abstraction.Velocity; -import com.laytonsmith.abstraction.bukkit.BukkitConvertor; -import com.laytonsmith.abstraction.bukkit.BukkitMCEntity; -import com.laytonsmith.abstraction.enums.MCProjectileType; +import com.laytonsmith.abstraction.enums.MCEntityType; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.Projectile; @@ -15,49 +12,34 @@ /** * Workaround class to accommodate for the likelihood of non-living entities that can shoot stuff. - * + * * @author jb_aero */ public class BukkitMCEntityProjectileSource extends BukkitMCEntity implements MCProjectileSource { ProjectileSource eps; + public BukkitMCEntityProjectileSource(Entity source) { super(source); - if (!(source instanceof ProjectileSource)) { + if(!(source instanceof ProjectileSource)) { throw new IllegalArgumentException("Tried to construct BukkitMCEntityProjectileSource from invalid source."); } eps = (ProjectileSource) source; } - - @Override - public MCProjectile launchProjectile(MCProjectileType projectile) { - EntityType et = EntityType.valueOf(projectile.name()); - Class c = et.getEntityClass(); - Projectile proj = eps.launchProjectile(c.asSubclass(Projectile.class)); - MCEntity mcproj = BukkitConvertor.BukkitGetCorrectEntity(proj); - - if (mcproj instanceof MCProjectile) { - return (MCProjectile) mcproj; - } else { - return null; - } + @Override + public MCProjectile launchProjectile(MCEntityType projectile) { + EntityType et = (EntityType) projectile.getConcrete(); + Class p = et.getEntityClass().asSubclass(Projectile.class); + return new BukkitMCProjectile(eps.launchProjectile(p)); } @Override - public MCProjectile launchProjectile(MCProjectileType projectile, Velocity init) { - EntityType et = EntityType.valueOf(projectile.name()); - Class c = et.getEntityClass(); - Vector vector = new Vector(init.x, init.y, init.z); - Projectile proj = eps.launchProjectile(c.asSubclass(Projectile.class), vector); - - MCEntity mcproj = BukkitConvertor.BukkitGetCorrectEntity(proj); - - if (mcproj instanceof MCProjectile) { - return (MCProjectile) mcproj; - } else { - return null; - } + public MCProjectile launchProjectile(MCEntityType projectile, Vector3D init) { + EntityType et = (EntityType) projectile.getConcrete(); + Vector vector = new Vector(init.X(), init.Y(), init.Z()); + Class p = et.getEntityClass().asSubclass(Projectile.class); + return new BukkitMCProjectile(eps.launchProjectile(p, vector)); } @Override @@ -72,8 +54,6 @@ public int hashCode() { @Override public boolean equals(Object obj) { - return eps.equals(obj); + return obj instanceof BukkitMCEntityProjectileSource && eps.equals(((BukkitMCEntityProjectileSource) obj).eps); } - - } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCEvoker.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCEvoker.java new file mode 100644 index 0000000000..453730000b --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCEvoker.java @@ -0,0 +1,12 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCEvoker; +import org.bukkit.entity.Entity; + +public class BukkitMCEvoker extends BukkitMCLivingEntity implements MCEvoker { + + public BukkitMCEvoker(Entity ent) { + super(ent); + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCEvokerFangs.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCEvokerFangs.java new file mode 100644 index 0000000000..e96542822b --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCEvokerFangs.java @@ -0,0 +1,35 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.MCLivingEntity; +import com.laytonsmith.abstraction.bukkit.BukkitConvertor; +import com.laytonsmith.abstraction.entities.MCEvokerFangs; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EvokerFangs; +import org.bukkit.entity.LivingEntity; + +public class BukkitMCEvokerFangs extends BukkitMCEntity implements MCEvokerFangs { + + private EvokerFangs ef; + + public BukkitMCEvokerFangs(Entity ent) { + super(ent); + ef = (EvokerFangs) ent; + } + + @Override + public MCLivingEntity getOwner() { + if(ef.getOwner() == null) { + return null; + } + return (MCLivingEntity) BukkitConvertor.BukkitGetCorrectEntity(ef.getOwner()); + } + + @Override + public void setOwner(MCLivingEntity owner) { + if(owner == null) { + ef.setOwner(null); + } else { + ef.setOwner((LivingEntity) owner.getHandle()); + } + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCExperienceOrb.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCExperienceOrb.java new file mode 100644 index 0000000000..961bfeccde --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCExperienceOrb.java @@ -0,0 +1,29 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCExperienceOrb; +import org.bukkit.entity.Entity; +import org.bukkit.entity.ExperienceOrb; + +/** + * + * @author Jim + */ +public class BukkitMCExperienceOrb extends BukkitMCEntity implements MCExperienceOrb { + + ExperienceOrb eo; + + public BukkitMCExperienceOrb(Entity eo) { + super(eo); + this.eo = (ExperienceOrb) eo; + } + + @Override + public int getExperience() { + return eo.getExperience(); + } + + @Override + public void setExperience(int amount) { + eo.setExperience(amount); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCFallingBlock.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCFallingBlock.java new file mode 100644 index 0000000000..0c110e663f --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCFallingBlock.java @@ -0,0 +1,48 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCFallingBlock; +import com.laytonsmith.abstraction.blocks.MCMaterial; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCMaterial; +import org.bukkit.entity.Entity; +import org.bukkit.entity.FallingBlock; + +public class BukkitMCFallingBlock extends BukkitMCEntity implements MCFallingBlock { + + FallingBlock f; + + public BukkitMCFallingBlock(Entity e) { + super(e); + this.f = (FallingBlock) e; + } + + @Override + public boolean getDropItem() { + return f.getDropItem(); + } + + @Override + public MCMaterial getMaterial() { + return BukkitMCMaterial.valueOfConcrete(f.getBlockData().getMaterial()); + } + + @Override + public void setDropItem(boolean drop) { + f.setDropItem(drop); + } + + @Override + public boolean canHurtEntities() { + return f.canHurtEntities(); + } + + @Override + public void setHurtEntities(boolean hurt) { + f.setHurtEntities(hurt); + } + + @Override + public FallingBlock getHandle() { + return f; + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCFireball.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCFireball.java new file mode 100644 index 0000000000..dad8511b14 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCFireball.java @@ -0,0 +1,28 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.PureUtilities.Vector3D; +import com.laytonsmith.abstraction.entities.MCFireball; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Fireball; +import org.bukkit.util.Vector; + +public class BukkitMCFireball extends BukkitMCProjectile implements MCFireball { + + Fireball f; + + public BukkitMCFireball(Entity be) { + super(be); + f = (Fireball) be; + } + + @Override + public Vector3D getDirection() { + return new Vector3D(f.getDirection().getX(), f.getDirection().getY(), f.getDirection().getZ()); + } + + @Override + public void setDirection(Vector3D vector) { + f.setDirection(new Vector(vector.X(), vector.Y(), vector.Z())); + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCFirework.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCFirework.java index 999ac6913c..32ca1dc92a 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCFirework.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCFirework.java @@ -1,17 +1,18 @@ package com.laytonsmith.abstraction.bukkit.entities; import com.laytonsmith.abstraction.MCFireworkMeta; -import com.laytonsmith.abstraction.bukkit.BukkitMCEntity; import com.laytonsmith.abstraction.bukkit.BukkitMCFireworkMeta; import com.laytonsmith.abstraction.entities.MCFirework; +import org.bukkit.entity.Entity; import org.bukkit.entity.Firework; -public class BukkitMCFirework extends BukkitMCEntity implements MCFirework { +public class BukkitMCFirework extends BukkitMCProjectile implements MCFirework { Firework f; - public BukkitMCFirework(Firework e) { + + public BukkitMCFirework(Entity e) { super(e); - f = e; + f = (Firework) e; } @Override @@ -24,4 +25,19 @@ public void setFireWorkMeta(MCFireworkMeta fm) { f.setFireworkMeta(((BukkitMCFireworkMeta) fm).asItemMeta()); } + @Override + public boolean isShotAtAngle() { + return f.isShotAtAngle(); + } + + @Override + public void setShotAtAngle(boolean shotAtAngle) { + f.setShotAtAngle(shotAtAngle); + } + + @Override + public void detonate() { + f.detonate(); + } + } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCFishHook.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCFishHook.java index 2cef93a925..f976900e6d 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCFishHook.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCFishHook.java @@ -1,25 +1,16 @@ package com.laytonsmith.abstraction.bukkit.entities; -import com.laytonsmith.abstraction.bukkit.BukkitMCProjectile; import com.laytonsmith.abstraction.entities.MCFishHook; -import org.bukkit.entity.Fish; +import org.bukkit.entity.Entity; +import org.bukkit.entity.FishHook; public class BukkitMCFishHook extends BukkitMCProjectile implements MCFishHook { - Fish f; - public BukkitMCFishHook(Fish e) { - super(e); - f = e; - } + FishHook f; - @Override - public double getBiteChance() { - return f.getBiteChance(); - } - - @Override - public void setBiteChance(double chance) { - f.setBiteChance(chance); + public BukkitMCFishHook(Entity e) { + super(e); + f = (FishHook) e; } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCFox.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCFox.java new file mode 100644 index 0000000000..c6d03ddb9e --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCFox.java @@ -0,0 +1,51 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCFox; +import com.laytonsmith.abstraction.enums.MCFoxType; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Fox; + +public class BukkitMCFox extends BukkitMCAnimal implements MCFox { + + Fox f; + + public BukkitMCFox(Entity fox) { + super(fox); + this.f = (Fox) fox; + } + + @Override + public Fox getHandle() { + return f; + } + + @Override + public MCFoxType getVariant() { + return MCFoxType.valueOf(f.getFoxType().name()); + } + + @Override + public void setVariant(MCFoxType type) { + f.setFoxType(Fox.Type.valueOf(type.name())); + } + + @Override + public boolean isCrouching() { + return f.isCrouching(); + } + + @Override + public void setCrouching(boolean crouching) { + f.setCrouching(crouching); + } + + @Override + public boolean isSitting() { + return f.isSitting(); + } + + @Override + public void setSitting(boolean sitting) { + f.setSitting(sitting); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCFrog.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCFrog.java new file mode 100644 index 0000000000..ffca588b21 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCFrog.java @@ -0,0 +1,61 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.PureUtilities.Common.ReflectionUtils; +import com.laytonsmith.abstraction.MCEntity; +import com.laytonsmith.abstraction.bukkit.BukkitConvertor; +import com.laytonsmith.abstraction.entities.MCFrog; +import org.bukkit.Keyed; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Frog; + +import java.util.Locale; + +public class BukkitMCFrog extends BukkitMCAnimal implements MCFrog { + + Frog f; + + public BukkitMCFrog(Entity frog) { + super(frog); + this.f = (Frog) frog; + } + + @Override + public Frog getHandle() { + return f; + } + + @Override + public MCFrogType getFrogType() { + // changed from enum to interface in 1.21 + NamespacedKey key = ReflectionUtils.invokeMethod(Keyed.class, f.getVariant(), "getKey"); + return MCFrogType.valueOf(key.getKey().toUpperCase(Locale.ROOT)); + } + + @Override + public void setFrogType(MCFrogType type) { + Frog.Variant v = Registry.FROG_VARIANT.get(NamespacedKey.minecraft(type.name().toLowerCase(Locale.ROOT))); + if(v != null) { + f.setVariant(v); + } + } + + @Override + public MCEntity getTongueTarget() { + Entity target = f.getTongueTarget(); + if(target == null) { + return null; + } + return BukkitConvertor.BukkitGetCorrectEntity(target); + } + + @Override + public void setTongueTarget(MCEntity target) { + if(target == null) { + f.setTongueTarget(null); + } else { + f.setTongueTarget((Entity) target.getHandle()); + } + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCGlowItemFrame.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCGlowItemFrame.java new file mode 100644 index 0000000000..2f65849cb8 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCGlowItemFrame.java @@ -0,0 +1,12 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCGlowItemFrame; +import org.bukkit.entity.Entity; + +public class BukkitMCGlowItemFrame extends BukkitMCItemFrame implements MCGlowItemFrame { + + public BukkitMCGlowItemFrame(Entity frame) { + super(frame); + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCGoat.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCGoat.java new file mode 100644 index 0000000000..5bbc81a469 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCGoat.java @@ -0,0 +1,30 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCGoat; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Goat; + +public class BukkitMCGoat extends BukkitMCAnimal implements MCGoat { + + Goat g; + + public BukkitMCGoat(Entity goat) { + super(goat); + this.g = (Goat) goat; + } + + @Override + public Goat getHandle() { + return g; + } + + @Override + public boolean isScreaming() { + return g.isScreaming(); + } + + @Override + public void setScreaming(boolean screaming) { + g.setScreaming(screaming); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCGuardian.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCGuardian.java new file mode 100644 index 0000000000..20a74e13f3 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCGuardian.java @@ -0,0 +1,15 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCGuardian; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Guardian; + +public class BukkitMCGuardian extends BukkitMCLivingEntity implements MCGuardian { + + Guardian e; + + public BukkitMCGuardian(Entity ent) { + super(ent); + e = (Guardian) ent; + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCHanging.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCHanging.java similarity index 87% rename from src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCHanging.java rename to src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCHanging.java index 27c9f22c36..081afbafba 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCHanging.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCHanging.java @@ -1,6 +1,6 @@ -package com.laytonsmith.abstraction.bukkit; +package com.laytonsmith.abstraction.bukkit.entities; -import com.laytonsmith.abstraction.MCHanging; +import com.laytonsmith.abstraction.entities.MCHanging; import com.laytonsmith.abstraction.blocks.MCBlockFace; import org.bukkit.block.BlockFace; import org.bukkit.entity.Entity; @@ -9,23 +9,24 @@ public class BukkitMCHanging extends BukkitMCEntity implements MCHanging { Hanging h; + public BukkitMCHanging(Entity e) { super(e); this.h = (Hanging) e; } - + @Override public MCBlockFace getFacing() { String dir = h.getFacing().name(); return MCBlockFace.valueOf(dir); } - + @Override public void setFacingDirection(MCBlockFace direction) { BlockFace dir = BlockFace.valueOf(direction.name()); h.setFacingDirection(dir); } - + @Override public boolean setFacingDirection(MCBlockFace direction, boolean force) { BlockFace dir = BlockFace.valueOf(direction.name()); diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCHoglin.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCHoglin.java new file mode 100644 index 0000000000..541eccf45c --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCHoglin.java @@ -0,0 +1,11 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCHoglin; +import org.bukkit.entity.Entity; + +public class BukkitMCHoglin extends BukkitMCAnimal implements MCHoglin { + + public BukkitMCHoglin(Entity be) { + super(be); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCHopperMinecart.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCHopperMinecart.java new file mode 100644 index 0000000000..1f67fd8cb8 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCHopperMinecart.java @@ -0,0 +1,22 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.MCInventory; +import com.laytonsmith.abstraction.bukkit.BukkitMCInventory; +import com.laytonsmith.abstraction.entities.MCHopperMinecart; +import org.bukkit.entity.Entity; +import org.bukkit.entity.minecart.HopperMinecart; + +public class BukkitMCHopperMinecart extends BukkitMCMinecart implements MCHopperMinecart { + + HopperMinecart hm; + + public BukkitMCHopperMinecart(Entity e) { + super(e); + this.hm = (HopperMinecart) e; + } + + @Override + public MCInventory getInventory() { + return new BukkitMCInventory(hm.getInventory()); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCHorse.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCHorse.java index beed4adb2d..bf7a340c84 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCHorse.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCHorse.java @@ -1,206 +1,98 @@ package com.laytonsmith.abstraction.bukkit.entities; import com.laytonsmith.abstraction.Implementation; -import com.laytonsmith.abstraction.MCInventory; import com.laytonsmith.abstraction.MCItemStack; -import com.laytonsmith.abstraction.bukkit.BukkitMCInventory; import com.laytonsmith.abstraction.bukkit.BukkitMCItemStack; -import com.laytonsmith.abstraction.bukkit.BukkitMCTameable; import com.laytonsmith.abstraction.entities.MCHorse; import com.laytonsmith.abstraction.enums.EnumConvertor; import com.laytonsmith.annotations.abstractionenum; +import org.bukkit.entity.Entity; import org.bukkit.entity.Horse; -/** - * - * @author jb_aero - */ -public class BukkitMCHorse extends BukkitMCTameable implements MCHorse { +public class BukkitMCHorse extends BukkitMCAbstractHorse implements MCHorse { Horse h; - public BukkitMCHorse(Horse t) { + + public BukkitMCHorse(Entity t) { super(t); - this.h = t; - } - - @Override - public MCInventory getInventory() { - return new BukkitMCInventory(h.getInventory()); + this.h = (Horse) t; } - - @Override - public MCHorseVariant getVariant() { - return BukkitMCHorseVariant.getConvertor().getAbstractedEnum(h.getVariant()); - } - + @Override public MCHorseColor getColor() { return BukkitMCHorseColor.getConvertor().getAbstractedEnum(h.getColor()); } - + @Override public MCHorsePattern getPattern() { return BukkitMCHorsePattern.getConvertor().getAbstractedEnum(h.getStyle()); } - - @Override - public void setVariant(MCHorseVariant variant) { - h.setVariant(BukkitMCHorseVariant.getConvertor().getConcreteEnum(variant)); - } - + @Override public void setColor(MCHorseColor color) { h.setColor(BukkitMCHorseColor.getConvertor().getConcreteEnum(color)); } - + @Override public void setPattern(MCHorsePattern pattern) { h.setStyle(BukkitMCHorsePattern.getConvertor().getConcreteEnum(pattern)); } - - @Override - public double getJumpStrength() { - return h.getJumpStrength(); - } - - @Override - public void setJumpStrength(double strength) { - h.setJumpStrength(strength); - } - @Override - public boolean hasChest() { - return h.isCarryingChest(); - } - - @Override - public void setHasChest(boolean hasChest) { - h.setCarryingChest(hasChest); - } - - @Override - public int getDomestication() { - return h.getDomestication(); - } - - @Override - public int getMaxDomestication() { - return h.getMaxDomestication(); - } - - @Override - public void setDomestication(int level) { - h.setDomestication(level); - } - - @Override - public void setMaxDomestication(int level) { - h.setMaxDomestication(level); - } - - @Override - public void setSaddle(MCItemStack stack) { - h.getInventory().setSaddle(((BukkitMCItemStack)stack).asItemStack()); - } - - @Override - public MCItemStack getSaddle() { - return new BukkitMCItemStack(h.getInventory().getSaddle()); - } @Override public void setArmor(MCItemStack stack) { - h.getInventory().setArmor(((BukkitMCItemStack)stack).asItemStack()); + h.getInventory().setArmor(((BukkitMCItemStack) stack).asItemStack()); } @Override public MCItemStack getArmor() { return new BukkitMCItemStack(h.getInventory().getArmor()); } - - @abstractionenum( - implementation= Implementation.Type.BUKKIT, - forAbstractEnum=MCHorseVariant.class, - forConcreteEnum=Horse.Variant.class - ) - public static class BukkitMCHorseVariant extends EnumConvertor{ - private static BukkitMCHorseVariant instance; - - public static BukkitMCHorseVariant getConvertor() { - if (instance == null) { - instance = new BukkitMCHorseVariant(); - } - return instance; - } - - @Override - protected MCHorseVariant getAbstractedEnumCustom(Horse.Variant concrete) { - switch (concrete) { - case SKELETON_HORSE: - return MCHorseVariant.SKELETON; - case UNDEAD_HORSE: - return MCHorseVariant.ZOMBIE; - } - return super.getAbstractedEnumCustom(concrete); - } - - @Override - protected Horse.Variant getConcreteEnumCustom(MCHorseVariant abstracted) { - switch (abstracted) { - case SKELETON: - return Horse.Variant.SKELETON_HORSE; - case ZOMBIE: - return Horse.Variant.UNDEAD_HORSE; - } - return super.getConcreteEnumCustom(abstracted); - } - } - @abstractionenum( - implementation= Implementation.Type.BUKKIT, - forAbstractEnum=MCHorseColor.class, - forConcreteEnum=Horse.Color.class + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCHorseColor.class, + forConcreteEnum = Horse.Color.class ) - public static class BukkitMCHorseColor extends EnumConvertor{ + public static class BukkitMCHorseColor extends EnumConvertor { private static BukkitMCHorseColor instance; - + public static BukkitMCHorseColor getConvertor() { - if (instance == null) { + if(instance == null) { instance = new BukkitMCHorseColor(); } return instance; } } - + @abstractionenum( - implementation= Implementation.Type.BUKKIT, - forAbstractEnum=MCHorsePattern.class, - forConcreteEnum=Horse.Style.class + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCHorsePattern.class, + forConcreteEnum = Horse.Style.class ) - public static class BukkitMCHorsePattern extends EnumConvertor{ + public static class BukkitMCHorsePattern extends EnumConvertor { private static BukkitMCHorsePattern instance; - + public static BukkitMCHorsePattern getConvertor() { - if (instance == null) { + if(instance == null) { instance = new BukkitMCHorsePattern(); } return instance; } - + @Override protected MCHorsePattern getAbstractedEnumCustom(Horse.Style concrete) { - switch (concrete) { + switch(concrete) { case WHITE: return MCHorsePattern.SOCKS; } return super.getAbstractedEnumCustom(concrete); } - + @Override protected Horse.Style getConcreteEnumCustom(MCHorsePattern abstracted) { - switch (abstracted) { + switch(abstracted) { case SOCKS: return Horse.Style.WHITE; } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCHumanEntity.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCHumanEntity.java new file mode 100644 index 0000000000..55be00a90c --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCHumanEntity.java @@ -0,0 +1,152 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.MCHumanEntity; +import com.laytonsmith.abstraction.MCInventory; +import com.laytonsmith.abstraction.MCInventoryView; +import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.MCLocation; +import com.laytonsmith.abstraction.MCMerchant; +import com.laytonsmith.abstraction.MCNamespacedKey; +import com.laytonsmith.abstraction.blocks.MCMaterial; +import com.laytonsmith.abstraction.bukkit.BukkitMCInventory; +import com.laytonsmith.abstraction.bukkit.BukkitMCInventoryView; +import com.laytonsmith.abstraction.bukkit.BukkitMCItemStack; +import com.laytonsmith.abstraction.enums.MCGameMode; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.entity.Entity; +import org.bukkit.entity.HumanEntity; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.Merchant; + +import java.util.UUID; + +public class BukkitMCHumanEntity extends BukkitMCLivingEntity implements MCHumanEntity { + + HumanEntity he; + + public BukkitMCHumanEntity(Entity humanEntity) { + super(humanEntity); + he = (HumanEntity) humanEntity; + } + + public HumanEntity asHumanEntity() { + return he; + } + + @Override + public String getName() { + return he.getName(); + } + + @Override + public UUID getUniqueID() { + return he.getUniqueId(); + } + + @Override + public void closeInventory() { + he.closeInventory(); + } + + @Override + public MCGameMode getGameMode() { + return MCGameMode.valueOf(he.getGameMode().name()); + } + + @Override + public MCItemStack getItemInHand() { + return new BukkitMCItemStack(he.getInventory().getItemInMainHand()); + } + + @Override + public MCItemStack getItemOnCursor() { + return new BukkitMCItemStack(he.getItemOnCursor()); + } + + @Override + public int getSleepTicks() { + return he.getSleepTicks(); + } + + @Override + public boolean isBlocking() { + return he.isBlocking(); + } + + @Override + public void setGameMode(MCGameMode mode) { + he.setGameMode(org.bukkit.GameMode.valueOf(mode.name())); + } + + @Override + public void setItemInHand(MCItemStack item) { + he.getInventory().setItemInMainHand(((BukkitMCItemStack) item).asItemStack()); + } + + @Override + public void setItemOnCursor(MCItemStack item) { + he.setItemOnCursor(((BukkitMCItemStack) item).asItemStack()); + } + + @Override + public int getCooldown(MCMaterial material) { + return he.getCooldown((Material) material.getHandle()); + } + + @Override + public void setCooldown(MCMaterial material, int ticks) { + he.setCooldown((Material) material.getHandle(), ticks); + } + + @Override + public float getAttackCooldown() { + return he.getAttackCooldown(); + } + + @Override + public MCInventoryView openInventory(MCInventory inventory) { + return new BukkitMCInventoryView(he.openInventory((Inventory) inventory.getHandle())); + } + + @Override + public MCInventoryView getOpenInventory() { + return new BukkitMCInventoryView(he.getOpenInventory()); + } + + @Override + public MCInventory getInventory() { + return new BukkitMCInventory(he.getInventory()); + } + + @Override + public MCInventory getEnderChest() { + return new BukkitMCInventory(he.getEnderChest()); + } + + @Override + public MCInventoryView openWorkbench(MCLocation loc, boolean force) { + return new BukkitMCInventoryView(he.openWorkbench((Location) loc.getHandle(), force)); + } + + @Override + public MCInventoryView openMerchant(MCMerchant merchant, boolean force) { + return new BukkitMCInventoryView(he.openMerchant((Merchant) merchant.getHandle(), force)); + } + + @Override + public MCInventoryView openEnchanting(MCLocation loc, boolean force) { + return new BukkitMCInventoryView(he.openEnchanting((Location) loc.getHandle(), force)); + } + + @Override + public boolean discoverRecipe(MCNamespacedKey recipe) { + return he.discoverRecipe((NamespacedKey) recipe.getHandle()); + } + + @Override + public boolean hasDiscoveredRecipe(MCNamespacedKey recipe) { + return he.hasDiscoveredRecipe((NamespacedKey) recipe.getHandle()); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCHusk.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCHusk.java new file mode 100644 index 0000000000..1096e86c5c --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCHusk.java @@ -0,0 +1,12 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCHusk; +import org.bukkit.entity.Entity; + +public class BukkitMCHusk extends BukkitMCZombie implements MCHusk { + + public BukkitMCHusk(Entity zombie) { + super(zombie); + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCInteraction.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCInteraction.java new file mode 100644 index 0000000000..557fe1299c --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCInteraction.java @@ -0,0 +1,63 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCInteraction; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Interaction; +import org.bukkit.entity.Interaction.PreviousInteraction; + +public class BukkitMCInteraction extends BukkitMCEntity implements MCInteraction { + Interaction interaction; + + public BukkitMCInteraction(Entity i) { + super(i); + this.interaction = (Interaction) i; + } + + @Override + public double getWidth() { + return interaction.getInteractionWidth(); + } + + @Override + public void setWidth(double width) { + interaction.setInteractionWidth((float) width); + } + + @Override + public double getHeight() { + return interaction.getInteractionHeight(); + } + + @Override + public void setHeight(double height) { + interaction.setInteractionHeight((float) height); + } + + @Override + public boolean isResponsive() { + return interaction.isResponsive(); + } + + @Override + public void setResponsive(boolean response) { + interaction.setResponsive(response); + } + + @Override + public MCPreviousInteraction getLastAttack() { + PreviousInteraction pi = interaction.getLastAttack(); + if(pi == null) { + return null; + } + return new MCPreviousInteraction(pi.getPlayer().getUniqueId(), pi.getTimestamp()); + } + + @Override + public MCPreviousInteraction getLastInteraction() { + PreviousInteraction pi = interaction.getLastInteraction(); + if(pi == null) { + return null; + } + return new MCPreviousInteraction(pi.getPlayer().getUniqueId(), pi.getTimestamp()); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCIronGolem.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCIronGolem.java index 1056c43b6e..78d72f8b08 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCIronGolem.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCIronGolem.java @@ -1,17 +1,13 @@ package com.laytonsmith.abstraction.bukkit.entities; import com.laytonsmith.abstraction.AbstractionObject; -import com.laytonsmith.abstraction.bukkit.BukkitMCLivingEntity; import com.laytonsmith.abstraction.entities.MCIronGolem; +import org.bukkit.entity.Entity; import org.bukkit.entity.IronGolem; -/** - * - * @author Hekta - */ public class BukkitMCIronGolem extends BukkitMCLivingEntity implements MCIronGolem { - public BukkitMCIronGolem(IronGolem golem) { + public BukkitMCIronGolem(Entity golem) { super(golem); } @@ -21,7 +17,7 @@ public BukkitMCIronGolem(AbstractionObject ao) { @Override public IronGolem getHandle() { - return (IronGolem)super.getHandle(); + return (IronGolem) super.getHandle(); } @Override @@ -33,4 +29,4 @@ public boolean isPlayerCreated() { public void setPlayerCreated(boolean playerCreated) { getHandle().setPlayerCreated(playerCreated); } -} \ No newline at end of file +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCItem.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCItem.java new file mode 100644 index 0000000000..401a12640d --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCItem.java @@ -0,0 +1,69 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCItem; +import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.bukkit.BukkitMCItemStack; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Item; + +import java.util.UUID; + +public class BukkitMCItem extends BukkitMCEntity implements MCItem { + + Item i; + + public BukkitMCItem(Entity i) { + super(i); + this.i = (Item) i; + } + + @Override + public MCItemStack getItemStack() { + return new BukkitMCItemStack(i.getItemStack()); + } + + @Override + public int getPickupDelay() { + return i.getPickupDelay(); + } + + @Override + public void setItemStack(MCItemStack stack) { + i.setItemStack(((BukkitMCItemStack) stack).asItemStack()); + } + + @Override + public void setPickupDelay(int delay) { + i.setPickupDelay(delay); + } + + @Override + public UUID getOwner() { + return i.getOwner(); + } + + @Override + public void setOwner(UUID owner) { + i.setOwner(owner); + } + + @Override + public UUID getThrower() { + return i.getThrower(); + } + + @Override + public void setThrower(UUID thrower) { + i.setThrower(thrower); + } + + @Override + public boolean willDespawn() { + return !i.isUnlimitedLifetime(); + } + + @Override + public void setWillDespawn(boolean despawn) { + i.setUnlimitedLifetime(!despawn); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCItemDisplay.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCItemDisplay.java new file mode 100644 index 0000000000..fa1cb76daf --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCItemDisplay.java @@ -0,0 +1,47 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.bukkit.BukkitMCItemStack; +import com.laytonsmith.abstraction.entities.MCItemDisplay; +import org.bukkit.entity.Entity; +import org.bukkit.entity.ItemDisplay; +import org.bukkit.entity.ItemDisplay.ItemDisplayTransform; +import org.bukkit.inventory.ItemStack; + +public class BukkitMCItemDisplay extends BukkitMCDisplay implements MCItemDisplay { + + ItemDisplay id; + + public BukkitMCItemDisplay(Entity e) { + super(e); + this.id = (ItemDisplay) e; + } + + @Override + public MCItemStack getItem() { + ItemStack item = this.id.getItemStack(); + if(item == null) { + return null; + } + return new BukkitMCItemStack(item); + } + + @Override + public void setItem(MCItemStack item) { + if(item == null) { + this.id.setItemStack(null); + } else { + this.id.setItemStack((ItemStack) item.getHandle()); + } + } + + @Override + public ModelTransform getItemModelTransform() { + return ModelTransform.valueOf(this.id.getItemDisplayTransform().name()); + } + + @Override + public void setItemModelTransform(ModelTransform transform) { + this.id.setItemDisplayTransform(ItemDisplayTransform.valueOf(transform.name())); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCItemFrame.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCItemFrame.java index 2b1fc0f486..6227dbd9df 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCItemFrame.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCItemFrame.java @@ -2,21 +2,17 @@ import com.laytonsmith.abstraction.AbstractionObject; import com.laytonsmith.abstraction.MCItemStack; -import com.laytonsmith.abstraction.bukkit.BukkitMCHanging; import com.laytonsmith.abstraction.bukkit.BukkitMCItemStack; import com.laytonsmith.abstraction.entities.MCItemFrame; import com.laytonsmith.abstraction.enums.MCRotation; import com.laytonsmith.abstraction.enums.bukkit.BukkitMCRotation; +import org.bukkit.entity.Entity; import org.bukkit.entity.ItemFrame; import org.bukkit.inventory.ItemStack; -/** - * - * @author Hekta - */ public class BukkitMCItemFrame extends BukkitMCHanging implements MCItemFrame { - public BukkitMCItemFrame(ItemFrame frame) { + public BukkitMCItemFrame(Entity frame) { super(frame); } @@ -26,22 +22,17 @@ public BukkitMCItemFrame(AbstractionObject ao) { @Override public ItemFrame getHandle() { - return (ItemFrame)super.getHandle(); + return (ItemFrame) super.getHandle(); } @Override public MCItemStack getItem() { - ItemStack item = getHandle().getItem(); - if (item != null) { - return new BukkitMCItemStack(getHandle().getItem()); - } else { - return null; - } + return new BukkitMCItemStack(getHandle().getItem()); } @Override public void setItem(MCItemStack item) { - if (item != null) { + if(item != null) { getHandle().setItem((ItemStack) item.getHandle()); } else { getHandle().setItem(null); @@ -57,4 +48,42 @@ public MCRotation getRotation() { public void setRotation(MCRotation rotation) { getHandle().setRotation(BukkitMCRotation.getConvertor().getConcreteEnum(rotation)); } -} \ No newline at end of file + + @Override + public boolean isVisible() { + try { + return getHandle().isVisible(); + } catch (NoSuchMethodError ex) { + // prior to 1.16 + } + return true; + } + + @Override + public void setVisible(boolean visible) { + try { + getHandle().setVisible(visible); + } catch (NoSuchMethodError ex) { + // prior to 1.16 + } + } + + @Override + public boolean isFixed() { + try { + return getHandle().isFixed(); + } catch (NoSuchMethodError ex) { + // prior to 1.16 + } + return false; + } + + @Override + public void setFixed(boolean fixed) { + try { + getHandle().setFixed(fixed); + } catch (NoSuchMethodError ex) { + // prior to 1.16 + } + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCItemProjectile.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCItemProjectile.java new file mode 100644 index 0000000000..d72a1feae7 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCItemProjectile.java @@ -0,0 +1,25 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.bukkit.BukkitMCItemStack; +import com.laytonsmith.abstraction.entities.MCItemProjectile; +import org.bukkit.entity.Entity; +import org.bukkit.entity.ThrowableProjectile; +import org.bukkit.inventory.ItemStack; + +public class BukkitMCItemProjectile extends BukkitMCProjectile implements MCItemProjectile { + + public BukkitMCItemProjectile(Entity be) { + super(be); + } + + @Override + public MCItemStack getItem() { + return new BukkitMCItemStack(((ThrowableProjectile) getHandle()).getItem()); + } + + @Override + public void setItem(MCItemStack item) { + ((ThrowableProjectile) getHandle()).setItem((ItemStack) item.getHandle()); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCLightningStrike.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCLightningStrike.java new file mode 100644 index 0000000000..9f233f2622 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCLightningStrike.java @@ -0,0 +1,20 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCLightningStrike; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LightningStrike; + +public class BukkitMCLightningStrike extends BukkitMCEntity implements MCLightningStrike { + + LightningStrike ls; + + public BukkitMCLightningStrike(Entity ls) { + super(ls); + this.ls = (LightningStrike) ls; + } + + @Override + public boolean isEffect() { + return ls.isEffect(); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCLivingEntity.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCLivingEntity.java new file mode 100644 index 0000000000..ed3af29b67 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCLivingEntity.java @@ -0,0 +1,493 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.PureUtilities.Common.ReflectionUtils; +import com.laytonsmith.abstraction.MCAttributeModifier; +import com.laytonsmith.abstraction.MCEntity; +import com.laytonsmith.abstraction.MCEntityEquipment; +import com.laytonsmith.abstraction.MCLivingEntity; +import com.laytonsmith.abstraction.MCLocation; +import com.laytonsmith.abstraction.MCPlayer; +import com.laytonsmith.abstraction.blocks.MCBlock; +import com.laytonsmith.abstraction.blocks.MCMaterial; +import com.laytonsmith.abstraction.bukkit.BukkitMCAttributeModifier; +import com.laytonsmith.abstraction.bukkit.BukkitMCEntityEquipment; +import com.laytonsmith.abstraction.bukkit.BukkitMCLocation; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCBlock; +import com.laytonsmith.abstraction.enums.MCAttribute; +import com.laytonsmith.abstraction.enums.MCPotionEffectType; +import com.laytonsmith.abstraction.enums.MCVersion; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCPotionEffectType; +import com.laytonsmith.core.Static; +import com.laytonsmith.core.constructs.Target; +import com.laytonsmith.core.exceptions.CRE.CREBadEntityException; +import org.bukkit.Material; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; +import org.bukkit.attribute.AttributeModifier; +import org.bukkit.block.Block; +import org.bukkit.damage.DamageSource; +import org.bukkit.damage.DamageType; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Mob; +import org.bukkit.entity.Player; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.EntityDamageEvent.DamageCause; +import org.bukkit.inventory.EntityEquipment; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.util.BlockIterator; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; + +public class BukkitMCLivingEntity extends BukkitMCEntityProjectileSource implements MCLivingEntity { + + LivingEntity le; + + public BukkitMCLivingEntity(Entity ent) { + super(ent); + this.le = (LivingEntity) ent; + } + + @Override + public double getHealth() { + return le.getHealth(); + } + + @Override + public void setHealth(double i) { + le.setHealth(i); + } + + @Override + public double getMaxHealth() { + Attribute attribute = (Attribute) MCAttribute.valueOf("GENERIC_MAX_HEALTH").getConcrete(); + AttributeInstance maxHealth = le.getAttribute(attribute); + if(maxHealth == null) { + return le.getHealth(); + } + return maxHealth.getValue(); + } + + @Override + public void setMaxHealth(double health) { + Attribute attribute = (Attribute) MCAttribute.valueOf("GENERIC_MAX_HEALTH").getConcrete(); + AttributeInstance maxHealth = le.getAttribute(attribute); + if(maxHealth == null) { + le.setHealth(health); + return; + } + maxHealth.setBaseValue(health); + if(le.getHealth() > health) { + le.setHealth(health); + } + } + + @Override + public void resetMaxHealth() { + Attribute attribute = (Attribute) MCAttribute.valueOf("GENERIC_MAX_HEALTH").getConcrete(); + AttributeInstance maxHealth = le.getAttribute(attribute); + if(maxHealth == null) { + return; + } + double defaultHealth = maxHealth.getDefaultValue(); + maxHealth.setBaseValue(defaultHealth); + if(le.getHealth() > defaultHealth) { + le.setHealth(defaultHealth); + } + } + + @Override + public void damage(double i) { + le.damage(i); + } + + @Override + public void damage(double amount, MCEntity source) { + le.damage(amount, ((BukkitMCEntity) source).getHandle()); + } + + @Override + public double getEyeHeight() { + return le.getEyeHeight(); + } + + @Override + public double getEyeHeight(boolean ignoreSneaking) { + return le.getEyeHeight(ignoreSneaking); + } + + @Override + public MCLocation getEyeLocation() { + return new BukkitMCLocation(le.getEyeLocation()); + } + + @Override + public MCPlayer getKiller() { + Player killer = le.getKiller(); + if(killer == null) { + return null; + } + return new BukkitMCPlayer(killer); + } + + @Override + public void setKiller(MCPlayer killer) { + try { + if(killer == null) { + le.setKiller(null); + } else { + le.setKiller((Player) killer.getHandle()); + } + } catch(NoSuchMethodError ignore) { + // probably not Paper + } + } + + @Override + public double getLastDamage() { + return le.getLastDamage(); + } + + @Override + public MCBlock getTargetSpace(int maxDistance) { + List lst = getLineOfSight(null, maxDistance, 2); + return new BukkitMCBlock(lst.get(0)); + } + + @Override + public List getLineOfSight(HashSet transparent, int maxDistance) { + List lst = getLineOfSight(transparent, maxDistance, 512); + List retn = new ArrayList<>(); + + for(Block b : lst) { + retn.add(new BukkitMCBlock(b)); + } + return retn; + } + + private List getLineOfSight(HashSet transparent, int maxDistance, int maxLength) { + if(maxDistance > 512) { + maxDistance = 512; + } + + HashSet ignored = new HashSet<>(); + if(transparent != null) { + for(MCMaterial mat : transparent) { + ignored.add((Material) mat.getHandle()); + } + } + + ArrayList blocks = new ArrayList<>(); + Iterator itr = new BlockIterator(le, maxDistance); + + while(itr.hasNext()) { + Block block = itr.next(); + blocks.add(block); + if(maxLength != 0 && blocks.size() > maxLength) { + blocks.remove(0); + } + if(transparent == null) { + if(!block.isEmpty()) { + break; + } + } else { + Material id = block.getType(); + if(!ignored.contains(id)) { + break; + } + } + } + return blocks; + } + + @Override + public boolean hasLineOfSight(MCEntity other) { + return le.hasLineOfSight(((BukkitMCEntity) other).getHandle()); + } + + @Override + public int getMaximumAir() { + return le.getMaximumAir(); + } + + @Override + public int getNoDamageTicks() { + // Minecraft checks for noDamageTicks > 10 before causing damage. + // Since this is useless and confusing, return the actual amount of immunity ticks. + if(le.getNoDamageTicks() < 10) { + return 0; + } + return le.getNoDamageTicks() - 10; + } + + @Override + public int getRemainingAir() { + return le.getRemainingAir(); + } + + @Override + public MCBlock getTargetBlock(HashSet b, int i) { + List blocks = getLineOfSight(b, i, 1); + return new BukkitMCBlock(blocks.get(0)); + } + + @Override + public boolean hasAI() { + return le.hasAI(); + } + + @Override + public boolean addEffect(MCPotionEffectType type, int strength, int ticks, boolean ambient, boolean particles, boolean icon) { + if(ticks < 0) { + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_19_4)) { + ticks = -1; + } else { + ticks = Integer.MAX_VALUE; + } + } + PotionEffect pe = new PotionEffect((PotionEffectType) type.getConcrete(), ticks, strength, ambient, particles, icon); + return le.addPotionEffect(pe); + } + + @Override + public boolean removeEffect(MCPotionEffectType type) { + PotionEffectType t = (PotionEffectType) type.getConcrete(); + boolean hasIt = false; + for(PotionEffect pe : le.getActivePotionEffects()) { + if(pe.getType() == t) { + hasIt = true; + break; + } + } + le.removePotionEffect(t); + return hasIt; + } + + @Override + public void removeEffects() { + for(PotionEffect pe : le.getActivePotionEffects()) { + le.removePotionEffect(pe.getType()); + } + } + + @Override + public List getEffects() { + List effects = new ArrayList<>(); + for(PotionEffect pe : le.getActivePotionEffects()) { + MCEffect e = new MCEffect(BukkitMCPotionEffectType.valueOfConcrete(pe.getType()), pe.getAmplifier(), pe.getDuration(), + pe.isAmbient(), pe.hasParticles(), pe.hasIcon()); + effects.add(e); + } + return effects; + } + + @Override + public void setLastDamage(double damage) { + le.setLastDamage(damage); + } + + @Override + public void setMaximumAir(int ticks) { + le.setMaximumAir(ticks); + } + + @Override + public void setNoDamageTicks(int ticks) { + // Minecraft checks for noDamageTicks > 10 before causing damage, so adjust for the actual immunity ticks. + le.setNoDamageTicks(ticks + 10); + } + + @Override + public void setRemainingAir(int ticks) { + le.setRemainingAir(ticks); + } + + public LivingEntity asLivingEntity() { + return le; + } + + @Override + public MCEntityEquipment getEquipment() { + EntityEquipment eq = le.getEquipment(); + if(eq == null) { + return null; + } + return new BukkitMCEntityEquipment(eq); + } + + @Override + public boolean getCanPickupItems() { + return le.getCanPickupItems(); + } + + @Override + public void setCanPickupItems(boolean pickup) { + le.setCanPickupItems(pickup); + } + + @Override + public boolean getRemoveWhenFarAway() { + return le.getRemoveWhenFarAway(); + } + + @Override + public void setRemoveWhenFarAway(boolean remove) { + le.setRemoveWhenFarAway(remove); + } + + @Override + public MCLivingEntity getTarget(Target t) { + if(!(le instanceof Mob)) { + throw new CREBadEntityException("This type of entity does not have a target API", t); + } + LivingEntity target = ((Mob) le).getTarget(); + if(target == null) { + return null; + } + return new BukkitMCLivingEntity(target); + } + + @Override + public void setTarget(MCLivingEntity target, Target t) { + if(!(le instanceof Mob)) { + throw new CREBadEntityException("This type of entity not have a target API", t); + } + if(target == null) { + ((Mob) le).setTarget(null); + } else { + ((Mob) le).setTarget(((BukkitMCLivingEntity) target).asLivingEntity()); + } + } + + @Override + public void kill() { + try { + le.damage(le.getHealth(), DamageSource.builder(DamageType.GENERIC_KILL).build()); + } catch (NoClassDefFoundError | NoSuchMethodError ex) { + // probably before 1.20.4 + EntityDamageEvent event = ReflectionUtils.newInstance(EntityDamageEvent.class, + new Class[]{Entity.class, DamageCause.class, double.class}, + new Object[]{le, EntityDamageEvent.DamageCause.CUSTOM, le.getHealth()}); + ReflectionUtils.invokeMethod(le, "setLastDamageCause", event); + le.setHealth(0); + } + } + + @Override + public MCEntity getLeashHolder() { + return new BukkitMCEntity(le.getLeashHolder()); + } + + @Override + public boolean isLeashed() { + return le.isLeashed(); + } + + @Override + public void setLeashHolder(MCEntity holder) { + if(holder == null) { + le.setLeashHolder(null); + } else { + le.setLeashHolder(((BukkitMCEntity) holder).getHandle()); + } + } + + @Override + public boolean isGliding() { + return le.isGliding(); + } + + @Override + public void setGliding(Boolean glide) { + le.setGliding(glide); + } + + @Override + public void setAI(Boolean ai) { + le.setAI(ai); + } + + @Override + public boolean isCollidable() { + return le.isCollidable(); + } + + @Override + public void setCollidable(boolean collidable) { + le.setCollidable(collidable); + } + + @Override + public boolean isTameable() { + return false; + } + + private AttributeInstance getAttributeInstance(MCAttribute attr) { + AttributeInstance instance = le.getAttribute((Attribute) attr.getConcrete()); + if(instance == null) { + throw new IllegalArgumentException("This attribute is not applicable to this entity type."); + } + return instance; + } + + @Override + public double getAttributeValue(MCAttribute attr) { + return getAttributeInstance(attr).getValue(); + } + + @Override + public double getAttributeDefault(MCAttribute attr) { + return getAttributeInstance(attr).getDefaultValue(); + } + + @Override + public double getAttributeBase(MCAttribute attr) { + return getAttributeInstance(attr).getBaseValue(); + } + + @Override + public void setAttributeBase(MCAttribute attr, double base) { + getAttributeInstance(attr).setBaseValue(base); + } + + @Override + public void resetAttributeBase(MCAttribute attr) { + AttributeInstance instance = getAttributeInstance(attr); + instance.setBaseValue(instance.getDefaultValue()); + } + + @Override + public List getAttributeModifiers(MCAttribute attr) { + Attribute bukkitAttribute = (Attribute) attr.getConcrete(); + AttributeInstance instance = le.getAttribute(bukkitAttribute); + if(instance == null) { + throw new IllegalArgumentException("This attribute is not applicable to this entity type."); + } + Collection modifiers = instance.getModifiers(); + List ret = new ArrayList<>(); + for(AttributeModifier modifier : modifiers) { + ret.add(new BukkitMCAttributeModifier(bukkitAttribute, modifier)); + } + return ret; + } + + @Override + public void addAttributeModifier(MCAttributeModifier modifier) { + getAttributeInstance(modifier.getAttribute()).addModifier((AttributeModifier) modifier.getHandle()); + } + + @Override + public void removeAttributeModifier(MCAttributeModifier modifier) { + getAttributeInstance(modifier.getAttribute()).removeModifier((AttributeModifier) modifier.getHandle()); + } + + @Override + public boolean isSleeping() { + return le.isSleeping(); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCLlama.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCLlama.java new file mode 100644 index 0000000000..4cce9c3f70 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCLlama.java @@ -0,0 +1,57 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.Implementation; +import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.bukkit.BukkitMCItemStack; +import com.laytonsmith.abstraction.entities.MCLlama; +import com.laytonsmith.abstraction.enums.EnumConvertor; +import com.laytonsmith.annotations.abstractionenum; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Llama; + +public class BukkitMCLlama extends BukkitMCChestedHorse implements MCLlama { + + Llama l; + + public BukkitMCLlama(Entity t) { + super(t); + this.l = (Llama) t; + } + + @Override + public MCLlamaColor getLlamaColor() { + return BukkitMCLlamaColor.getConvertor().getAbstractedEnum(l.getColor()); + } + + @Override + public void setLlamaColor(MCLlamaColor color) { + l.setColor(BukkitMCLlamaColor.getConvertor().getConcreteEnum(color)); + } + + @Override + public void setSaddle(MCItemStack stack) { + l.getInventory().setItem(1, ((BukkitMCItemStack) stack).asItemStack()); + } + + @Override + public MCItemStack getSaddle() { + return new BukkitMCItemStack(l.getInventory().getItem(1)); + } + + @abstractionenum( + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCLlamaColor.class, + forConcreteEnum = Llama.Color.class + ) + public static class BukkitMCLlamaColor extends EnumConvertor { + + private static BukkitMCLlamaColor instance; + + public static BukkitMCLlamaColor getConvertor() { + if(instance == null) { + instance = new BukkitMCLlamaColor(); + } + return instance; + } + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCLlamaSpit.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCLlamaSpit.java new file mode 100644 index 0000000000..e8a7b671c4 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCLlamaSpit.java @@ -0,0 +1,16 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCLlamaSpit; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LlamaSpit; + +public class BukkitMCLlamaSpit extends BukkitMCProjectile implements MCLlamaSpit { + + LlamaSpit ls; + + public BukkitMCLlamaSpit(Entity ent) { + super(ent); + ls = (LlamaSpit) ent; + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCMagmaCube.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCMagmaCube.java index 2cb69c0615..0b71ea1d2c 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCMagmaCube.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCMagmaCube.java @@ -2,19 +2,16 @@ import com.laytonsmith.abstraction.AbstractionObject; import com.laytonsmith.abstraction.entities.MCMagmaCube; +import org.bukkit.entity.Entity; import org.bukkit.entity.MagmaCube; -/** - * - * @author Hekta - */ public class BukkitMCMagmaCube extends BukkitMCSlime implements MCMagmaCube { - public BukkitMCMagmaCube(MagmaCube cube) { + public BukkitMCMagmaCube(Entity cube) { super(cube); } public BukkitMCMagmaCube(AbstractionObject ao) { this((MagmaCube) ao.getHandle()); } -} \ No newline at end of file +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCMannequin.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCMannequin.java new file mode 100644 index 0000000000..b1820ca280 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCMannequin.java @@ -0,0 +1,37 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCMannequin; +import com.laytonsmith.abstraction.enums.MCPose; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCPose; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Mannequin; + +public class BukkitMCMannequin extends BukkitMCLivingEntity implements MCMannequin { + + private final Mannequin m; + + public BukkitMCMannequin(Entity mannequin) { + super(mannequin); + this.m = (Mannequin) mannequin; + } + + @Override + public Entity getHandle() { + return super.getHandle(); + } + + @Override + public boolean isImmovable() { + return this.m.isImmovable(); + } + + @Override + public void setImmovable(boolean immovable) { + this.m.setImmovable(immovable); + } + + @Override + public void setPose(MCPose pose, boolean fixed) { + this.m.setPose(BukkitMCPose.getConvertor().getConcreteEnum(pose)); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCMinecart.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCMinecart.java index db038318d6..523fc4a2dd 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCMinecart.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCMinecart.java @@ -1,16 +1,19 @@ package com.laytonsmith.abstraction.bukkit.entities; -import com.laytonsmith.abstraction.bukkit.BukkitMCVehicle; +import com.laytonsmith.abstraction.blocks.MCBlockData; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCBlockData; import com.laytonsmith.abstraction.entities.MCMinecart; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.Entity; import org.bukkit.entity.Minecart; -public class BukkitMCMinecart extends BukkitMCVehicle - implements MCMinecart { +public class BukkitMCMinecart extends BukkitMCVehicle implements MCMinecart { Minecart m; - public BukkitMCMinecart(Minecart e) { + + public BukkitMCMinecart(Entity e) { super(e); - this.m = e; + this.m = (Minecart) e; } @Override @@ -42,4 +45,24 @@ public boolean isSlowWhenEmpty() { public void setSlowWhenEmpty(boolean slow) { m.setSlowWhenEmpty(slow); } + + @Override + public void setDisplayBlock(MCBlockData data) { + m.setDisplayBlockData((BlockData) data.getHandle()); + } + + @Override + public MCBlockData getDisplayBlock() { + return new BukkitMCBlockData(m.getDisplayBlockData()); + } + + @Override + public void setDisplayBlockOffset(int offset) { + m.setDisplayBlockOffset(offset); + } + + @Override + public int getDisplayBlockOffset() { + return m.getDisplayBlockOffset(); + } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCMule.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCMule.java new file mode 100644 index 0000000000..c670fba00b --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCMule.java @@ -0,0 +1,12 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCMule; +import org.bukkit.entity.Entity; + +public class BukkitMCMule extends BukkitMCChestedHorse implements MCMule { + + public BukkitMCMule(Entity t) { + super(t); + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCMushroomCow.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCMushroomCow.java new file mode 100644 index 0000000000..f80e2220b9 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCMushroomCow.java @@ -0,0 +1,31 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCMushroomCow; +import com.laytonsmith.abstraction.enums.MCMushroomCowType; +import org.bukkit.entity.Entity; +import org.bukkit.entity.MushroomCow; + +public class BukkitMCMushroomCow extends BukkitMCAnimal implements MCMushroomCow { + + MushroomCow c; + + public BukkitMCMushroomCow(Entity cow) { + super(cow); + this.c = (MushroomCow) cow; + } + + @Override + public MushroomCow getHandle() { + return c; + } + + @Override + public MCMushroomCowType getVariant() { + return MCMushroomCowType.valueOf(c.getVariant().name()); + } + + @Override + public void setVariant(MCMushroomCowType type) { + c.setVariant(MushroomCow.Variant.valueOf(type.name())); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCOcelot.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCOcelot.java index f4313bc959..38ef82ba02 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCOcelot.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCOcelot.java @@ -1,41 +1,23 @@ package com.laytonsmith.abstraction.bukkit.entities; import com.laytonsmith.abstraction.AbstractionObject; -import com.laytonsmith.abstraction.bukkit.BukkitMCTameable; import com.laytonsmith.abstraction.entities.MCOcelot; -import com.laytonsmith.abstraction.enums.MCOcelotType; import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Ocelot; -public class BukkitMCOcelot extends BukkitMCTameable implements MCOcelot { +public class BukkitMCOcelot extends BukkitMCAnimal implements MCOcelot { Ocelot o; + public BukkitMCOcelot(Entity be) { super(be); this.o = (Ocelot) be; } - - public BukkitMCOcelot(AbstractionObject ao){ - super((LivingEntity)ao.getHandle()); - this.o = (Ocelot) ao.getHandle(); - } - - @Override - public MCOcelotType getCatType() { - return MCOcelotType.valueOf(o.getCatType().name()); - } - @Override - public boolean isSitting() { - return o.isSitting(); - } - @Override - public void setCatType(MCOcelotType type) { - o.setCatType(Ocelot.Type.valueOf(type.name())); - } - @Override - public void setSitting(boolean sitting) { - o.setSitting(sitting); + + public BukkitMCOcelot(AbstractionObject ao) { + super((LivingEntity) ao.getHandle()); + this.o = (Ocelot) ao.getHandle(); } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCOminousItemSpawner.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCOminousItemSpawner.java new file mode 100644 index 0000000000..bbf5c9ef17 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCOminousItemSpawner.java @@ -0,0 +1,45 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.bukkit.BukkitMCItemStack; +import com.laytonsmith.abstraction.entities.MCOminousItemSpawner; +import org.bukkit.entity.Entity; +import org.bukkit.entity.OminousItemSpawner; +import org.bukkit.inventory.ItemStack; + +public class BukkitMCOminousItemSpawner extends BukkitMCEntity implements MCOminousItemSpawner { + + OminousItemSpawner spawner; + + public BukkitMCOminousItemSpawner(Entity e) { + super(e); + this.spawner = (OminousItemSpawner) e; + } + + @Override + public MCItemStack getItem() { + if(this.spawner.getItem() == null) { + return null; + } + return new BukkitMCItemStack(this.spawner.getItem()); + } + + @Override + public void setItem(MCItemStack item) { + if(item == null) { + this.spawner.setItem(null); + } else { + this.spawner.setItem((ItemStack) item.getHandle()); + } + } + + @Override + public long getDelay() { + return this.spawner.getSpawnItemAfterTicks(); + } + + @Override + public void setDelay(long delay) { + this.spawner.setSpawnItemAfterTicks(delay); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPainting.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCPainting.java similarity index 56% rename from src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPainting.java rename to src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCPainting.java index ebd25795fb..87a76cbf1e 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPainting.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCPainting.java @@ -1,25 +1,24 @@ -package com.laytonsmith.abstraction.bukkit; +package com.laytonsmith.abstraction.bukkit.entities; -import com.laytonsmith.abstraction.MCPainting; +import com.laytonsmith.abstraction.entities.MCPainting; import com.laytonsmith.abstraction.enums.MCArt; import com.laytonsmith.abstraction.enums.bukkit.BukkitMCArt; +import org.bukkit.Art; +import org.bukkit.entity.Entity; import org.bukkit.entity.Painting; -/** - * - */ public class BukkitMCPainting extends BukkitMCHanging implements MCPainting { - + Painting p; - - public BukkitMCPainting(Painting painting){ + + public BukkitMCPainting(Entity painting) { super(painting); - this.p = painting; + this.p = (Painting) painting; } @Override public MCArt getArt() { - return BukkitMCArt.getConvertor().getAbstractedEnum(p.getArt()); + return BukkitMCArt.valueOfConcrete(p.getArt()); } @Override @@ -29,7 +28,7 @@ public boolean setArt(MCArt art) { @Override public boolean setArt(MCArt art, boolean force) { - return p.setArt(BukkitMCArt.getConvertor().getConcreteEnum(art), force); + return p.setArt((Art) art.getConcrete(), force); } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCPanda.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCPanda.java new file mode 100644 index 0000000000..16c9d7dd03 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCPanda.java @@ -0,0 +1,111 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.Implementation; +import com.laytonsmith.abstraction.entities.MCPanda; +import com.laytonsmith.abstraction.enums.EnumConvertor; +import com.laytonsmith.annotations.abstractionenum; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Panda; + +public class BukkitMCPanda extends BukkitMCAnimal implements MCPanda { + + private Panda p; + + public BukkitMCPanda(Entity be) { + super(be); + p = (Panda) be; + } + + @Override + public Gene getMainGene() { + return MCPanda.Gene.valueOf(p.getMainGene().name()); + } + + @Override + public void setMainGene(Gene gene) { + p.setMainGene(Panda.Gene.valueOf(gene.name())); + } + + @Override + public Gene getHiddenGene() { + return MCPanda.Gene.valueOf(p.getHiddenGene().name()); + } + + @Override + public void setHiddenGene(Gene gene) { + p.setHiddenGene(Panda.Gene.valueOf(gene.name())); + } + + @Override + public boolean isRolling() { + return p.isRolling(); + } + + @Override + public void setRolling(boolean rolling) { + try { + p.setRolling(rolling); + } catch(NoSuchMethodError ex) { + // probably before 1.19 + } + } + + @Override + public boolean isSneezing() { + return p.isSneezing(); + } + + @Override + public void setSneezing(boolean sneezing) { + try { + p.setSneezing(sneezing); + } catch(NoSuchMethodError ex) { + // probably before 1.19 + } + } + + @Override + public boolean isEating() { + return p.isEating(); + } + + @Override + public void setEating(boolean eating) { + try { + p.setEating(eating); + } catch(NoSuchMethodError ex) { + // probably before 1.19 + } + } + + @Override + public boolean isOnBack() { + return p.isOnBack(); + } + + @Override + public void setOnBack(boolean onBack) { + try { + p.setOnBack(onBack); + } catch(NoSuchMethodError ex) { + // probably before 1.19 + } + } + + @abstractionenum( + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCPanda.Gene.class, + forConcreteEnum = Panda.Gene.class + ) + public static class BukkitMCPandaGene extends EnumConvertor { + + private static BukkitMCPandaGene instance; + + public static BukkitMCPandaGene getConvertor() { + if(instance == null) { + instance = new BukkitMCPandaGene(); + } + return instance; + } + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCParrot.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCParrot.java new file mode 100644 index 0000000000..02c753e1b1 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCParrot.java @@ -0,0 +1,37 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCParrot; +import com.laytonsmith.abstraction.enums.MCParrotType; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Parrot; + +public class BukkitMCParrot extends BukkitMCTameable implements MCParrot { + + Parrot p; + + public BukkitMCParrot(Entity be) { + super(be); + this.p = (Parrot) be; + } + + @Override + public boolean isSitting() { + return p.isSitting(); + } + + @Override + public void setSitting(boolean sitting) { + p.setSitting(sitting); + } + + @Override + public MCParrotType getVariant() { + return MCParrotType.valueOf(p.getVariant().name()); + } + + @Override + public void setVariant(MCParrotType variant) { + p.setVariant(Parrot.Variant.valueOf(variant.name())); + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCPhantom.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCPhantom.java new file mode 100644 index 0000000000..89d71eb50c --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCPhantom.java @@ -0,0 +1,25 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCPhantom; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Phantom; + +public class BukkitMCPhantom extends BukkitMCLivingEntity implements MCPhantom { + + Phantom p; + + public BukkitMCPhantom(Entity be) { + super(be); + p = (Phantom) be; + } + + @Override + public int getSize() { + return p.getSize(); + } + + @Override + public void setSize(int size) { + p.setSize(size); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPig.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCPig.java similarity index 56% rename from src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPig.java rename to src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCPig.java index 95d9d022f6..bb87b4cdca 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCPig.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCPig.java @@ -1,18 +1,16 @@ -package com.laytonsmith.abstraction.bukkit; +package com.laytonsmith.abstraction.bukkit.entities; import com.laytonsmith.abstraction.entities.MCPig; +import org.bukkit.entity.Entity; import org.bukkit.entity.Pig; -/** - * - * @author jb_aero - */ -public class BukkitMCPig extends BukkitMCAgeable implements MCPig { +public class BukkitMCPig extends BukkitMCAnimal implements MCPig { Pig p; - public BukkitMCPig(Pig be) { + + public BukkitMCPig(Entity be) { super(be); - p = be; + p = (Pig) be; } @Override diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCPigZombie.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCPigZombie.java index 00366c5b22..2ac1d5cfe4 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCPigZombie.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCPigZombie.java @@ -2,15 +2,12 @@ import com.laytonsmith.abstraction.AbstractionObject; import com.laytonsmith.abstraction.entities.MCPigZombie; +import org.bukkit.entity.Entity; import org.bukkit.entity.PigZombie; -/** - * - * @author Hekta - */ public class BukkitMCPigZombie extends BukkitMCZombie implements MCPigZombie { - public BukkitMCPigZombie(PigZombie zombie) { + public BukkitMCPigZombie(Entity zombie) { super(zombie); } @@ -20,7 +17,7 @@ public BukkitMCPigZombie(AbstractionObject ao) { @Override public PigZombie getHandle() { - return (PigZombie)super.getHandle(); + return (PigZombie) super.getHandle(); } @Override @@ -42,4 +39,4 @@ public boolean isAngry() { public void setAngry(boolean isAngry) { getHandle().setAngry(isAngry); } -} \ No newline at end of file +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCPiglin.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCPiglin.java new file mode 100644 index 0000000000..fa91070dd9 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCPiglin.java @@ -0,0 +1,27 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCPiglin; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Piglin; + +public class BukkitMCPiglin extends BukkitMCAgeable implements MCPiglin { + + public BukkitMCPiglin(Entity ent) { + super(ent); + } + + @Override + public Piglin getHandle() { + return (Piglin) super.getHandle(); + } + + @Override + public boolean isImmuneToZombification() { + return getHandle().isImmuneToZombification(); + } + + @Override + public void setImmuneToZombification(boolean immune) { + getHandle().setImmuneToZombification(immune); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCPiglinBrute.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCPiglinBrute.java new file mode 100644 index 0000000000..f85056eeb5 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCPiglinBrute.java @@ -0,0 +1,23 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.AbstractionObject; +import com.laytonsmith.abstraction.entities.MCPiglinBrute; +import org.bukkit.entity.Entity; +import org.bukkit.entity.PiglinBrute; + +public class BukkitMCPiglinBrute extends BukkitMCLivingEntity implements MCPiglinBrute { + + public BukkitMCPiglinBrute(Entity ent) { + super(ent); + } + + public BukkitMCPiglinBrute(AbstractionObject ao) { + this((PiglinBrute) ao.getHandle()); + } + + @Override + public PiglinBrute getHandle() { + return (PiglinBrute) super.getHandle(); + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCPillager.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCPillager.java new file mode 100644 index 0000000000..06acbb4609 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCPillager.java @@ -0,0 +1,27 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.MCInventory; +import com.laytonsmith.abstraction.bukkit.BukkitMCInventory; +import com.laytonsmith.abstraction.entities.MCPillager; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Pillager; + +public class BukkitMCPillager extends BukkitMCLivingEntity implements MCPillager { + + Pillager p; + + public BukkitMCPillager(Entity pillager) { + super(pillager); + this.p = (Pillager) pillager; + } + + @Override + public Pillager getHandle() { + return p; + } + + @Override + public MCInventory getInventory() { + return new BukkitMCInventory(getHandle().getInventory()); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCPlayer.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCPlayer.java new file mode 100644 index 0000000000..4248ec2302 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCPlayer.java @@ -0,0 +1,964 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.PureUtilities.Common.ReflectionUtils; +import com.laytonsmith.abstraction.MCCommandSender; +import com.laytonsmith.abstraction.MCEntity; +import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.MCLivingEntity; +import com.laytonsmith.abstraction.MCLocation; +import com.laytonsmith.abstraction.MCNote; +import com.laytonsmith.abstraction.MCOfflinePlayer; +import com.laytonsmith.abstraction.MCPlayer; +import com.laytonsmith.abstraction.MCPlayerInput; +import com.laytonsmith.abstraction.MCPlayerInventory; +import com.laytonsmith.abstraction.MCScoreboard; +import com.laytonsmith.abstraction.MCWorldBorder; +import com.laytonsmith.abstraction.StaticLayer; +import com.laytonsmith.abstraction.blocks.MCBlockData; +import com.laytonsmith.abstraction.blocks.MCMaterial; +import com.laytonsmith.abstraction.blocks.MCSign; +import com.laytonsmith.abstraction.bukkit.BukkitConvertor; +import com.laytonsmith.abstraction.bukkit.BukkitMCItemStack; +import com.laytonsmith.abstraction.bukkit.BukkitMCLocation; +import com.laytonsmith.abstraction.bukkit.BukkitMCPlayerInput; +import com.laytonsmith.abstraction.bukkit.BukkitMCPlayerInventory; +import com.laytonsmith.abstraction.bukkit.BukkitMCScoreboard; +import com.laytonsmith.abstraction.bukkit.BukkitMCServer; +import com.laytonsmith.abstraction.bukkit.BukkitMCWorldBorder; +import com.laytonsmith.abstraction.enums.MCEntityType; +import com.laytonsmith.abstraction.enums.MCEquipmentSlot; +import com.laytonsmith.abstraction.enums.MCInstrument; +import com.laytonsmith.abstraction.enums.MCParticle; +import com.laytonsmith.abstraction.enums.MCPlayerStatistic; +import com.laytonsmith.abstraction.enums.MCPotionEffectType; +import com.laytonsmith.abstraction.enums.MCSound; +import com.laytonsmith.abstraction.enums.MCSoundCategory; +import com.laytonsmith.abstraction.enums.MCVersion; +import com.laytonsmith.abstraction.enums.MCWeather; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCInstrument; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCParticle; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCPlayerStatistic; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCSound; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCSoundCategory; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCWeather; +import com.laytonsmith.commandhelper.CommandHelperPlugin; +import com.laytonsmith.core.Static; +import net.md_5.bungee.api.chat.BaseComponent; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Note; +import org.bukkit.Particle; +import org.bukkit.SoundCategory; +import org.bukkit.WorldBorder; +import org.bukkit.block.Sign; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.permissions.PermissionAttachmentInfo; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public class BukkitMCPlayer extends BukkitMCHumanEntity implements MCPlayer, MCCommandSender, MCOfflinePlayer { + + Player p; + + public BukkitMCPlayer(Entity player) { + super(player); + this.p = (Player) player; + } + + public Player _Player() { + return p; + } + + @Override + public void setSleepingIgnored(boolean value) { + p.setSleepingIgnored(value); + } + + @Override + public boolean isSleepingIgnored() { + return p.isSleepingIgnored(); + } + + @Override + public boolean canSee(MCPlayer p) { + return this.p.canSee(((BukkitMCPlayer) p)._Player()); + } + + @Override + public void chat(String chat) { + p.chat(chat); + } + + @Override + public InetSocketAddress getAddress() { + return p.getAddress(); + } + + @Override + public boolean getAllowFlight() { + return p.getAllowFlight(); + } + + @Override + public MCLocation getCompassTarget() { + return new BukkitMCLocation(p.getCompassTarget()); + } + + @Override + public String getDisplayName() { + return p.getDisplayName(); + } + + @Override + public float getExp() { + return p.getExp(); + } + + @Override + public long getFirstPlayed() { + return p.getFirstPlayed(); + } + + @Override + public MCPlayerInventory getInventory() { + return new BukkitMCPlayerInventory(p.getInventory()); + } + + @Override + public MCItemStack getItemAt(Integer slot) { + ItemStack is = null; + if(slot == null) { + is = p.getInventory().getItemInMainHand(); + // Empty slots should return null, but unlike other PlayerInventory methods, + // getItemInMainHand() never returns null. + if(is.getType() == Material.AIR) { + return null; + } + } else if(slot == 100) { + is = p.getInventory().getBoots(); + } else if(slot == 101) { + is = p.getInventory().getLeggings(); + } else if(slot == 102) { + is = p.getInventory().getChestplate(); + } else if(slot == 103) { + is = p.getInventory().getHelmet(); + } else if(slot == -106) { + is = p.getInventory().getItemInOffHand(); + } else if(slot >= 0 && slot <= 35) { + is = p.getInventory().getItem(slot); + } + if(is == null) { + return null; + } + return new BukkitMCItemStack(is); + } + + @Override + public long getLastPlayed() { + return p.getLastPlayed(); + } + + @Override + public int getLevel() { + return p.getLevel(); + } + + @Override + public MCPlayer getPlayer() { + return new BukkitMCPlayer(p); + } + + @Override + public long getPlayerTime() { + return p.getPlayerTime(); + } + + @Override + public MCWeather getPlayerWeather() { + return BukkitMCWeather.getConvertor().getAbstractedEnum(p.getPlayerWeather()); + } + + @Override + public int getRemainingFireTicks() { + return p.getFireTicks(); + } + + @Override + public int getTotalExperience() { + return p.getTotalExperience(); + } + + @Override + public int getExpToLevel() { + return p.getExpToLevel(); + } + + @Override + public int getExpAtLevel() { + int level = p.getLevel(); + if(level > 30) { + return (int) (4.5 * Math.pow(level, 2) - 162.5 * level + 2220); + } + if(level > 15) { + return (int) (2.5 * Math.pow(level, 2) - 40.5 * level + 360); + } + return (int) (Math.pow(level, 2) + 6 * level); + } + + @Override + public void setFlySpeed(float speed) { + p.setFlySpeed(speed); + } + + @Override + public float getFlySpeed() { + return p.getFlySpeed(); + } + + @Override + public void setWalkSpeed(float speed) { + p.setWalkSpeed(speed); + } + + @Override + public float getWalkSpeed() { + return p.getWalkSpeed(); + } + + @Override + public void giveExp(int xp) { + p.giveExp(xp); + } + + @Override + public boolean hasPlayedBefore() { + return p.hasPlayedBefore(); + } + + @Override + public boolean isBanned() { + return p.isBanned(); + } + + @Override + public boolean isOnline() { + return p.isOnline(); + } + + @Override + public boolean isOp() { + return p.isOp(); + } + + @Override + public boolean hasPermission(String perm) { + return p.hasPermission(perm); + } + + @Override + public boolean isPermissionSet(String perm) { + return p.isPermissionSet(perm); + } + + @Override + public List getGroups() { + // As in https://github.com/sk89q/WorldEdit/blob/master/ + // worldedit-bukkit/src/main/java/com/sk89q/wepif/DinnerPermsResolver.java#L112-L126 + List groupNames = new ArrayList(); + for(PermissionAttachmentInfo permAttach : p.getEffectivePermissions()) { + String perm = permAttach.getPermission(); + if(!(perm.startsWith(Static.GROUP_PREFIX) && permAttach.getValue())) { + continue; + } + groupNames.add(perm.substring(Static.GROUP_PREFIX.length(), perm.length())); + } + return groupNames; + } + + @Override + public boolean inGroup(String groupName) { + return p.hasPermission(Static.GROUP_PREFIX + groupName); + } + + @Override + public void setOp(boolean bln) { + p.setOp(bln); + } + + @Override + public boolean isSneaking() { + return p.isSneaking(); + } + + @Override + public boolean isSprinting() { + return p.isSprinting(); + } + + @Override + public boolean isWhitelisted() { + return p.isWhitelisted(); + } + + @Override + public void kickPlayer(String message) { + p.kickPlayer(message); + } + + @Override + public boolean removeEffect(MCPotionEffectType type) { + PotionEffectType t = (PotionEffectType) type.getConcrete(); + boolean hasIt = false; + for(PotionEffect pe : p.getActivePotionEffects()) { + if(pe.getType() == t) { + hasIt = true; + break; + } + } + p.removePotionEffect(t); + return hasIt; + } + + @Override + public void resetPlayerTime() { + p.resetPlayerTime(); + } + + @Override + public void resetPlayerWeather() { + p.resetPlayerWeather(); + } + + @Override + public void sendMessage(String string) { + //The client doesn't like tabs + string = string.replace("\t", " "); + p.sendMessage(string); + } + + @Override + public void sendResourcePack(String url) { + p.setResourcePack(url); + } + + @Override + public void sendTitle(String title, String subtitle, int fadein, int stay, int fadeout) { + if(title == null || title.isEmpty()) { + // If the title is null or empty the subtitle won't be displayed. This is unintuitive. + title = " "; + } + p.sendTitle(title, subtitle, fadein, stay, fadeout); + } + + @Override + public void sendActionMessage(String message) { + BaseComponent txt = net.md_5.bungee.api.chat.TextComponent.fromLegacy(message); + p.spigot().sendMessage(net.md_5.bungee.api.ChatMessageType.ACTION_BAR, txt); + } + + @Override + public void setAllowFlight(boolean flight) { + p.setAllowFlight(flight); + } + + @Override + public void setCompassTarget(MCLocation l) { + p.setCompassTarget(((BukkitMCLocation) l)._Location()); + } + + @Override + public void setDisplayName(String name) { + p.setDisplayName(name); + } + + @Override + public void setExp(float i) { + p.setExp(i); + } + + @Override + public void setFlying(boolean flight) { + p.setFlying(flight); + } + + @Override + public void setLevel(int xp) { + p.setLevel(xp); + } + + @Override + public void setPlayerTime(Long time, boolean relative) { + p.setPlayerTime(time, relative); + } + + @Override + public void setPlayerWeather(MCWeather type) { + p.setPlayerWeather(BukkitMCWeather.getConvertor().getConcreteEnum(type)); + } + + @Override + public void setRemainingFireTicks(int i) { + p.setFireTicks(i); + } + + @Override + public void setSpectatorTarget(MCEntity entity) { + if(entity == null) { + p.setSpectatorTarget(null); + return; + } + p.setSpectatorTarget((Entity) entity.getHandle()); + } + + @Override + public MCEntity getSpectatorTarget() { + return BukkitConvertor.BukkitGetCorrectEntity(p.getSpectatorTarget()); + } + + private static Class gameProfileClass = null; + private static Class opListEntryClass = null; + private static Class nameAndIdClass = null; + private static Class levelBasedPermissionSetClass = null; + private static Object opPermissionSet = null; + private static Map opMap = null; + + private static void SetupTempOp() throws ClassNotFoundException { + if(gameProfileClass != null) { + return; + } + boolean isPaper = ((BukkitMCServer) Static.getServer()).isPaper(); + // Get some version specific mappings + String nms = "net.minecraft.server"; + String playersPackage = nms + ".players"; + + String ops = "ops"; + String getPlayerList = "getPlayerList"; + String map = "map"; + String serverOpListEntry = ".ServerOpListEntry"; + String owner = "OWNER"; + if(!isPaper) { + ops = "o"; + getPlayerList = "aj"; + map = "e"; + serverOpListEntry = ".OpListEntry"; + owner = "e"; + } + + MCVersion mcversion = Static.getServer().getMinecraftVersion(); + if(mcversion.lt(MCVersion.MC1_21_11)) { + getPlayerList = isPaper ? "getPlayerList" : "am"; + if(mcversion.lt(MCVersion.MC1_21_9)) { + ops = isPaper ? "ops" : "p"; + getPlayerList = isPaper ? "getPlayerList" : "ag"; + map = "d"; + serverOpListEntry = ".OpListEntry"; + if(mcversion.lt(MCVersion.MC1_21_3)) { + getPlayerList = isPaper ? "getPlayerList" : "ah"; + if(mcversion.lt(MCVersion.MC1_20_6)) { + getPlayerList = "ae"; + if(mcversion.lt(MCVersion.MC1_20_4)) { + getPlayerList = "ac"; + if(mcversion.lt(MCVersion.MC1_20_2)) { + ops = "o"; + if(mcversion.equals(MCVersion.MC1_19_3)) { + getPlayerList = "ab"; + } else if(mcversion.lt(MCVersion.MC1_19_1)) { + ops = "n"; + if(mcversion.lt(MCVersion.MC1_18)) { + getPlayerList = "getPlayerList"; + if(mcversion.lt(MCVersion.MC1_17)) { + String version = ((BukkitMCServer) Static.getServer()).getCraftBukkitPackage().split("\\.")[3]; + nms = "net.minecraft.server." + version; + playersPackage = nms; + ops = "operators"; + } + } + } + } + } + } + } + } + } + + Class nmsMinecraftServerClass = Class.forName(nms + ".MinecraftServer"); + /*n.m.s.MinecraftServer*/ Object nmsServer = ReflectionUtils.invokeMethod(nmsMinecraftServerClass, null, "getServer"); + /*n.m.s.players.PlayerList*/ Object nmsPlayerList = ReflectionUtils.invokeMethod(nmsServer, getPlayerList); + /*n.m.s.players.OpList*/ Object opSet = ReflectionUtils.get(Class.forName(playersPackage + ".PlayerList"), nmsPlayerList, ops); + //opSet.getClass().getSuperclass() == n.m.s.players.JsonList + /*Map*/ opMap = (Map) ReflectionUtils.get(opSet.getClass().getSuperclass(), opSet, map); + /*n.m.s.players.OpListEntry*/ opListEntryClass = Class.forName(playersPackage + serverOpListEntry); + /*com.mojang.authlib.GameProfile*/ gameProfileClass = Class.forName("com.mojang.authlib.GameProfile"); + if(mcversion.gte(MCVersion.MC1_21_9)) { + nameAndIdClass = Class.forName(playersPackage + ".NameAndId"); + if(mcversion.gte(MCVersion.MC1_21_11)) { + levelBasedPermissionSetClass = Class.forName(nms + ".permissions.LevelBasedPermissionSet"); + opPermissionSet = ReflectionUtils.get(levelBasedPermissionSetClass, null, owner); + } + } + } + + @Override + public void setTempOp(Boolean value) throws ClassNotFoundException { + SetupTempOp(); + if(value) { + Object gameProfile = ReflectionUtils.invokeMethod(p, "getProfile"); + Object opListEntry; + if(nameAndIdClass != null) { + Object nameAndId = ReflectionUtils.newInstance(nameAndIdClass, + new Class[]{gameProfileClass}, + new Object[]{gameProfile}); + if(opPermissionSet != null) { + opListEntry = ReflectionUtils.newInstance(opListEntryClass, + new Class[]{nameAndIdClass, levelBasedPermissionSetClass, boolean.class}, + new Object[]{nameAndId, opPermissionSet, false}); + } else { + opListEntry = ReflectionUtils.newInstance(opListEntryClass, + new Class[]{nameAndIdClass, int.class, boolean.class}, + new Object[]{nameAndId, 4, false}); + } + } else { + opListEntry = ReflectionUtils.newInstance(opListEntryClass, + new Class[]{gameProfileClass, int.class, boolean.class}, + new Object[]{gameProfile, 4, false}); + } + opMap.put(p.getUniqueId().toString(), opListEntry); + } else { + opMap.remove(p.getUniqueId().toString()); + } + p.recalculatePermissions(); + } + + @Override + public void setTotalExperience(int total) { + p.setTotalExperience(total); + } + + @Override + public void setVanished(boolean set, MCPlayer to) { + if(!set) { + p.showPlayer(CommandHelperPlugin.self, ((BukkitMCPlayer) to)._Player()); + } else { + p.hidePlayer(CommandHelperPlugin.self, ((BukkitMCPlayer) to)._Player()); + } + } + + @Override + public void hideEntity(MCEntity entity) { + try { + p.hideEntity(CommandHelperPlugin.self, (Entity) entity.getHandle()); + } catch(NoSuchMethodError ex) { + // probably before 1.18 + } + } + + @Override + public void showEntity(MCEntity entity) { + try { + p.showEntity(CommandHelperPlugin.self, (Entity) entity.getHandle()); + } catch(NoSuchMethodError ex) { + // probably before 1.18 + } + } + + @Override + public boolean canSeeEntity(MCEntity entity) { + try { + return p.canSee((Entity) entity.getHandle()); + } catch(NoSuchMethodError ex) { + // probably before 1.18 + return true; + } + } + + @Override + public void setWhitelisted(boolean value) { + p.setWhitelisted(value); + } + + @Override + public void setPlayerListName(String listName) { + p.setPlayerListName(listName); + } + + @Override + public String getPlayerListName() { + return p.getPlayerListName(); + } + + @Override + public void setPlayerListHeader(String header) { + p.setPlayerListHeader(header); + } + + @Override + public String getPlayerListHeader() { + return p.getPlayerListHeader(); + } + + @Override + public void setPlayerListFooter(String footer) { + p.setPlayerListFooter(footer); + } + + @Override + public String getPlayerListFooter() { + return p.getPlayerListFooter(); + } + + @Override + public boolean isNewPlayer() { + //Note the reversed logic here. If they have NOT played before, they are + //a new player. + return !p.hasPlayedBefore(); + } + + @Override + public String getHost() { + return Static.GetHost(this); + } + + @Override + public void sendBlockChange(MCLocation loc, MCBlockData data) { + p.sendBlockChange(((Location) loc.getHandle()), (BlockData) data.getHandle()); + } + + @Override + public void sendBlockDamage(MCLocation loc, float progress, MCEntity entity) { + Location location = (Location) loc.getHandle(); + try { + if(entity == null) { + // Using a block position hashCode as the sourceId allows independent control of each block's state. + int sourceId = (location.getBlockY() + location.getBlockZ() * 31) * 31 + location.getBlockX(); + p.sendBlockDamage(location, progress, sourceId); + } else { + p.sendBlockDamage(location, progress, ((Entity) entity.getHandle()).getEntityId()); + } + } catch (NoSuchMethodError er) { + // probably prior to 1.19.2 on Paper or 1.19.4 on Spigot + p.sendBlockDamage(location, progress); + } + } + + @Override + public void sendSignTextChange(MCLocation loc, String[] lines) { + p.sendSignChange(((Location) loc.getHandle()), lines); + } + + @Override + public void sendSignTextChange(MCSign sign) { + Sign s = (Sign) sign.getHandle(); + try { + p.sendBlockUpdate(s.getLocation(), s); + } catch (NoSuchMethodError noBlockUpdate) { + // probably before 1.20.1 + try { + p.sendSignChange(s.getLocation(), s.getLines(), s.getColor(), s.isGlowingText()); + } catch (NoSuchMethodError noGlowingText) { + // probably before 1.17.1 + p.sendSignChange(s.getLocation(), s.getLines(), s.getColor()); + } + } + } + + @Override + public void playNote(MCLocation loc, MCInstrument instrument, MCNote note) { + p.playNote((Location) loc.getHandle(), BukkitMCInstrument.getConvertor().getConcreteEnum(instrument), (Note) note.getHandle()); + } + + @Override + public void playSound(MCLocation l, MCSound sound, MCSoundCategory category, float volume, float pitch, Long seed) { + SoundCategory cat = BukkitMCSoundCategory.getConvertor().getConcreteEnum(category); + if(cat == null) { + cat = SoundCategory.MASTER; + } + if(seed == null) { + p.playSound((Location) l.getHandle(), ((BukkitMCSound) sound).getConcrete(), cat, volume, pitch); + } else { + p.playSound((Location) l.getHandle(), ((BukkitMCSound) sound).getConcrete(), cat, volume, pitch, seed); + } + } + + @Override + public void playSound(MCEntity ent, MCSound sound, MCSoundCategory category, float volume, float pitch, Long seed) { + SoundCategory cat = BukkitMCSoundCategory.getConvertor().getConcreteEnum(category); + if(cat == null) { + cat = SoundCategory.MASTER; + } + if(category == null) { + p.playSound((Entity) ent.getHandle(), ((BukkitMCSound) sound).getConcrete(), cat, volume, pitch); + } else { + p.playSound((Entity) ent.getHandle(), ((BukkitMCSound) sound).getConcrete(), cat, volume, pitch, seed); + } + } + + @Override + public void playSound(MCLocation l, String sound, MCSoundCategory category, float volume, float pitch, Long seed) { + SoundCategory cat = BukkitMCSoundCategory.getConvertor().getConcreteEnum(category); + if(cat == null) { + cat = SoundCategory.MASTER; + } + if(seed == null) { + p.playSound((Location) l.getHandle(), sound, cat, volume, pitch); + } else { + p.playSound((Location) l.getHandle(), sound, cat, volume, pitch, seed); + } + } + + @Override + public void playSound(MCEntity ent, String sound, MCSoundCategory category, float volume, float pitch, Long seed) { + SoundCategory cat = BukkitMCSoundCategory.getConvertor().getConcreteEnum(category); + if(cat == null) { + cat = SoundCategory.MASTER; + } + if(seed == null) { + p.playSound((Entity) ent.getHandle(), sound, cat, volume, pitch); + } else { + p.playSound((Entity) ent.getHandle(), sound, cat, volume, pitch, seed); + } + } + + @Override + public void stopSound(MCSound sound, MCSoundCategory category) { + p.stopSound(((BukkitMCSound) sound).getConcrete(), + BukkitMCSoundCategory.getConvertor().getConcreteEnum(category)); + } + + @Override + public void stopSound(String sound, MCSoundCategory category) { + p.stopSound(sound, BukkitMCSoundCategory.getConvertor().getConcreteEnum(category)); + } + + @Override + public void stopSound(MCSoundCategory category) { + try { + p.stopSound(BukkitMCSoundCategory.getConvertor().getConcreteEnum(category)); + } catch (NoSuchMethodError ex) { + // probably before 1.19.0 + } + } + + @Override + public void spawnParticle(MCLocation l, MCParticle pa, int count, double offsetX, double offsetY, double offsetZ, double velocity, boolean force, Object data) { + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_20_6)) { + p.spawnParticle((Particle) pa.getConcrete(), (Location) l.getHandle(), count, offsetX, offsetY, offsetZ, + velocity, ((BukkitMCParticle) pa).getParticleData(l, data), force); + } else { + p.spawnParticle((Particle) pa.getConcrete(), (Location) l.getHandle(), count, offsetX, offsetY, offsetZ, + velocity, ((BukkitMCParticle) pa).getParticleData(l, data)); + } + } + + @Override + public int getFoodLevel() { + return p.getFoodLevel(); + } + + @Override + public void setFoodLevel(int f) { + p.setFoodLevel(f); + } + + @Override + public float getSaturation() { + return p.getSaturation(); + } + + @Override + public void setSaturation(float s) { + p.setSaturation(s); + } + + @Override + public float getExhaustion() { + return p.getExhaustion(); + } + + @Override + public void setExhaustion(float e) { + p.setExhaustion(e); + } + + @Override + public MCLocation getBedSpawnLocation() { + Location loc = p.getBedSpawnLocation(); + if(loc == null) { + return null; + } + return new BukkitMCLocation(loc); + } + + @Override + public UUID getUniqueID() { + return p.getUniqueId(); + } + + @Override + public void setBedSpawnLocation(MCLocation l, boolean forced) { + Location loc = (Location) l.getHandle(); + loc.setYaw(Location.normalizeYaw(loc.getYaw())); + p.setBedSpawnLocation(loc, forced); + } + + @Override + public MCEntity getVehicle() { + return new BukkitMCEntity(p.getVehicle()); + } + + @Override + public void sendPluginMessage(String channel, byte[] message) { + StaticLayer.GetConvertor().GetPluginMeta().openOutgoingChannel(channel); + p.sendPluginMessage(CommandHelperPlugin.self, channel, message); + } + + @Override + public boolean isFlying() { + return p.isFlying(); + } + + @Override + public void updateInventory() { + p.updateInventory(); + } + + @Override + public int getStatistic(MCPlayerStatistic stat) { + return p.getStatistic(BukkitMCPlayerStatistic.getConvertor().getConcreteEnum(stat)); + } + + @Override + public int getStatistic(MCPlayerStatistic stat, MCEntityType type) { + EntityType bukkitType = (EntityType) type.getConcrete(); + return p.getStatistic(BukkitMCPlayerStatistic.getConvertor().getConcreteEnum(stat), bukkitType); + } + + @Override + public int getStatistic(MCPlayerStatistic stat, MCMaterial type) { + Material bukkitType = (Material) type.getHandle(); + return p.getStatistic(BukkitMCPlayerStatistic.getConvertor().getConcreteEnum(stat), bukkitType); + } + + @Override + public void setStatistic(MCPlayerStatistic stat, int amount) { + p.setStatistic(BukkitMCPlayerStatistic.getConvertor().getConcreteEnum(stat), amount); + } + + @Override + public void setStatistic(MCPlayerStatistic stat, MCEntityType type, int amount) { + EntityType bukkitType = (EntityType) type.getConcrete(); + p.setStatistic(BukkitMCPlayerStatistic.getConvertor().getConcreteEnum(stat), bukkitType, amount); + } + + @Override + public void setStatistic(MCPlayerStatistic stat, MCMaterial type, int amount) { + Material bukkitType = (Material) type.getHandle(); + p.setStatistic(BukkitMCPlayerStatistic.getConvertor().getConcreteEnum(stat), bukkitType, amount); + } + + @Override + public MCWorldBorder getWorldBorder() { + try { + WorldBorder wb = p.getWorldBorder(); + if(wb == null) { + return null; + } + return new BukkitMCWorldBorder(wb); + } catch (NoSuchMethodError ex) { + // probably before 1.18.2 + return null; + } + } + + @Override + public void setWorldBorder(MCWorldBorder border) { + try { + if(border == null) { + p.setWorldBorder(null); + } else { + p.setWorldBorder((WorldBorder) border.getHandle()); + } + } catch (NoSuchMethodError ex) { + // probably before 1.18.2 + } + } + + @Override + public String getLocale() { + return p.getLocale(); + } + + @Override + public MCScoreboard getScoreboard() { + return new BukkitMCScoreboard(p.getScoreboard()); + } + + @Override + public void setScoreboard(MCScoreboard board) { + p.setScoreboard(((BukkitMCScoreboard) board)._scoreboard()); + } + + @Override + public void respawn() { + p.spigot().respawn(); + } + + @Override + public void sendEquipmentChange(MCLivingEntity entity, MCEquipmentSlot slot, MCItemStack item) { + LivingEntity le = (LivingEntity) entity.getHandle(); + ItemStack is; + if(item == null) { + if(Static.getServer().getMinecraftVersion().lt(MCVersion.MC1_19_3)) { + // null isn't supported prior to 1.19.3 + is = new ItemStack(Material.AIR); + } else { + is = null; + } + } else { + is = (ItemStack) item.getHandle(); + } + try { + switch(slot) { + case WEAPON -> p.sendEquipmentChange(le, EquipmentSlot.HAND, is); + case OFF_HAND -> p.sendEquipmentChange(le, EquipmentSlot.OFF_HAND, is); + case BOOTS -> p.sendEquipmentChange(le, EquipmentSlot.FEET, is); + case LEGGINGS -> p.sendEquipmentChange(le, EquipmentSlot.LEGS, is); + case CHESTPLATE -> p.sendEquipmentChange(le, EquipmentSlot.CHEST, is); + case HELMET -> p.sendEquipmentChange(le, EquipmentSlot.HEAD, is); + case BODY -> { + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_20_6)) { + p.sendEquipmentChange(le, EquipmentSlot.BODY, is); + } + } + case SADDLE -> { + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_21_5)) { + p.sendEquipmentChange(le, EquipmentSlot.SADDLE, is); + } + } + } + } catch(NoSuchMethodError ex) { + // probably before 1.18, which is unsupported + } + } + + @Override + public int getPing() { + return p.getPing(); + } + + @Override + public MCPlayerInput getCurrentInput() { + return new BukkitMCPlayerInput(p.getCurrentInput()); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCProjectile.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCProjectile.java new file mode 100644 index 0000000000..58b5218da3 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCProjectile.java @@ -0,0 +1,67 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.MCEntity; +import com.laytonsmith.abstraction.entities.MCProjectile; +import com.laytonsmith.abstraction.MCProjectileSource; +import com.laytonsmith.abstraction.blocks.MCBlockProjectileSource; +import com.laytonsmith.abstraction.bukkit.BukkitConvertor; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCBlockProjectileSource; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Projectile; +import org.bukkit.projectiles.BlockProjectileSource; +import org.bukkit.projectiles.ProjectileSource; + +public class BukkitMCProjectile extends BukkitMCEntity implements MCProjectile { + + public BukkitMCProjectile(Entity e) { + super(e); + } + + @Override + public boolean doesBounce() { + // Some entities (like fireworks prior to 1.16) may not be treated as projectiles on this server implementation + if(getHandle() instanceof Projectile) { + return ((Projectile) getHandle()).doesBounce(); + } + return false; + } + + @Override + public MCProjectileSource getShooter() { + if(getHandle() instanceof Projectile) { + ProjectileSource source = ((Projectile) getHandle()).getShooter(); + + if(source instanceof BlockProjectileSource) { + return new BukkitMCBlockProjectileSource((BlockProjectileSource) source); + } + + if(source instanceof Entity) { + MCEntity e = BukkitConvertor.BukkitGetCorrectEntity((Entity) source); + if(e instanceof MCProjectileSource) { + return (MCProjectileSource) e; + } + } + } + return null; + } + + @Override + public void setBounce(boolean doesBounce) { + if(getHandle() instanceof Projectile) { + ((Projectile) getHandle()).setBounce(doesBounce); + } + } + + @Override + public void setShooter(MCProjectileSource shooter) { + if(getHandle() instanceof Projectile) { + if(shooter == null) { + ((Projectile) getHandle()).setShooter(null); + } else if(shooter instanceof MCBlockProjectileSource) { + ((Projectile) getHandle()).setShooter((BlockProjectileSource) shooter.getHandle()); + } else { + ((Projectile) getHandle()).setShooter((ProjectileSource) shooter.getHandle()); + } + } + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCPufferfish.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCPufferfish.java new file mode 100644 index 0000000000..eedae86939 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCPufferfish.java @@ -0,0 +1,25 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCPufferfish; +import org.bukkit.entity.Entity; +import org.bukkit.entity.PufferFish; + +public class BukkitMCPufferfish extends BukkitMCLivingEntity implements MCPufferfish { + + private final PufferFish p; + + public BukkitMCPufferfish(Entity be) { + super(be); + this.p = (PufferFish) be; + } + + @Override + public int getPuffState() { + return this.p.getPuffState(); + } + + @Override + public void setPuffState(int state) { + this.p.setPuffState(state); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCRabbit.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCRabbit.java new file mode 100644 index 0000000000..76f60f122d --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCRabbit.java @@ -0,0 +1,33 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.AbstractionObject; +import com.laytonsmith.abstraction.entities.MCRabbit; +import com.laytonsmith.abstraction.enums.MCRabbitType; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Rabbit; + +public class BukkitMCRabbit extends BukkitMCAnimal implements MCRabbit { + + Rabbit r; + + public BukkitMCRabbit(Entity be) { + super(be); + this.r = (Rabbit) be; + } + + public BukkitMCRabbit(AbstractionObject ao) { + super((LivingEntity) ao.getHandle()); + this.r = (Rabbit) ao.getHandle(); + } + + @Override + public MCRabbitType getRabbitType() { + return MCRabbitType.valueOf(r.getRabbitType().name()); + } + + @Override + public void setRabbitType(MCRabbitType type) { + r.setRabbitType(Rabbit.Type.valueOf(type.name())); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCSalmon.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCSalmon.java new file mode 100644 index 0000000000..4e47d1776a --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCSalmon.java @@ -0,0 +1,25 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCSalmon; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Salmon; + +public class BukkitMCSalmon extends BukkitMCLivingEntity implements MCSalmon { + + Salmon entity; + + public BukkitMCSalmon(Entity be) { + super(be); + this.entity = (Salmon) be; + } + + @Override + public Variant getVariant() { + return Variant.valueOf(this.entity.getVariant().name()); + } + + @Override + public void setVariant(Variant size) { + this.entity.setVariant(Salmon.Variant.valueOf(size.name())); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCSheep.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCSheep.java index 9d6a35c026..6e517f9e22 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCSheep.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCSheep.java @@ -1,17 +1,18 @@ package com.laytonsmith.abstraction.bukkit.entities; -import com.laytonsmith.abstraction.bukkit.BukkitMCAgeable; import com.laytonsmith.abstraction.entities.MCSheep; import com.laytonsmith.abstraction.enums.MCDyeColor; import com.laytonsmith.abstraction.enums.bukkit.BukkitMCDyeColor; +import org.bukkit.entity.Entity; import org.bukkit.entity.Sheep; -public class BukkitMCSheep extends BukkitMCAgeable implements MCSheep { +public class BukkitMCSheep extends BukkitMCAnimal implements MCSheep { Sheep s; - public BukkitMCSheep(Sheep be) { + + public BukkitMCSheep(Entity be) { super(be); - this.s = be; + this.s = (Sheep) be; } @Override diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCShulker.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCShulker.java new file mode 100644 index 0000000000..f9c04a12e5 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCShulker.java @@ -0,0 +1,27 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCShulker; +import com.laytonsmith.abstraction.enums.MCDyeColor; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCDyeColor; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Shulker; + +public class BukkitMCShulker extends BukkitMCLivingEntity implements MCShulker { + + private Shulker sh; + + public BukkitMCShulker(Entity be) { + super(be); + this.sh = (Shulker) be; + } + + @Override + public MCDyeColor getColor() { + return BukkitMCDyeColor.getConvertor().getAbstractedEnum(sh.getColor()); + } + + @Override + public void setColor(MCDyeColor color) { + sh.setColor(BukkitMCDyeColor.getConvertor().getConcreteEnum(color)); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCShulkerBullet.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCShulkerBullet.java new file mode 100644 index 0000000000..ed54a48ff0 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCShulkerBullet.java @@ -0,0 +1,34 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.MCEntity; +import com.laytonsmith.abstraction.entities.MCShulkerBullet; +import org.bukkit.entity.Entity; +import org.bukkit.entity.ShulkerBullet; + +public class BukkitMCShulkerBullet extends BukkitMCProjectile implements MCShulkerBullet { + + private final ShulkerBullet sb; + + public BukkitMCShulkerBullet(Entity be) { + super(be); + this.sb = (ShulkerBullet) be; + } + + @Override + public void setTarget(MCEntity entity) { + if(entity == null) { + sb.setTarget(null); + } else { + sb.setTarget((Entity) entity.getHandle()); + } + } + + @Override + public MCEntity getTarget() { + Entity e = sb.getTarget(); + if(e == null) { + return null; + } + return new BukkitMCEntity(sb.getTarget()); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCSizedFireball.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCSizedFireball.java new file mode 100644 index 0000000000..ddc3730740 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCSizedFireball.java @@ -0,0 +1,25 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.bukkit.BukkitMCItemStack; +import com.laytonsmith.abstraction.entities.MCItemProjectile; +import org.bukkit.entity.Entity; +import org.bukkit.entity.SizedFireball; +import org.bukkit.inventory.ItemStack; + +public class BukkitMCSizedFireball extends BukkitMCFireball implements MCItemProjectile { + + public BukkitMCSizedFireball(Entity be) { + super(be); + } + + @Override + public MCItemStack getItem() { + return new BukkitMCItemStack(((SizedFireball) getHandle()).getDisplayItem()); + } + + @Override + public void setItem(MCItemStack item) { + ((SizedFireball) getHandle()).setDisplayItem((ItemStack) item.getHandle()); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCSkeleton.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCSkeleton.java index a7fbab2908..ea549b3627 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCSkeleton.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCSkeleton.java @@ -1,33 +1,17 @@ package com.laytonsmith.abstraction.bukkit.entities; import com.laytonsmith.abstraction.AbstractionObject; -import com.laytonsmith.abstraction.bukkit.BukkitMCLivingEntity; import com.laytonsmith.abstraction.entities.MCSkeleton; -import com.laytonsmith.abstraction.enums.MCSkeletonType; -import com.laytonsmith.abstraction.enums.bukkit.BukkitMCSkeletonType; +import org.bukkit.entity.Entity; import org.bukkit.entity.Skeleton; -/** - * - * @author Hekta - */ public class BukkitMCSkeleton extends BukkitMCLivingEntity implements MCSkeleton { - public BukkitMCSkeleton(Skeleton skeleton) { + public BukkitMCSkeleton(Entity skeleton) { super(skeleton); } public BukkitMCSkeleton(AbstractionObject ao) { this((Skeleton) ao.getHandle()); } - - @Override - public MCSkeletonType getSkeletonType() { - return BukkitMCSkeletonType.getConvertor().getAbstractedEnum(((Skeleton)getHandle()).getSkeletonType()); - } - - @Override - public void setSkeletonType(MCSkeletonType type) { - ((Skeleton)getHandle()).setSkeletonType(BukkitMCSkeletonType.getConvertor().getConcreteEnum(type)); - } -} \ No newline at end of file +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCSkeletonHorse.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCSkeletonHorse.java new file mode 100644 index 0000000000..5af4e46aef --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCSkeletonHorse.java @@ -0,0 +1,12 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCSkeletonHorse; +import org.bukkit.entity.Entity; + +public class BukkitMCSkeletonHorse extends BukkitMCAbstractHorse implements MCSkeletonHorse { + + public BukkitMCSkeletonHorse(Entity t) { + super(t); + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCSlime.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCSlime.java index 0b1ad2c2ca..355bb2aaf5 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCSlime.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCSlime.java @@ -1,17 +1,13 @@ package com.laytonsmith.abstraction.bukkit.entities; import com.laytonsmith.abstraction.AbstractionObject; -import com.laytonsmith.abstraction.bukkit.BukkitMCLivingEntity; import com.laytonsmith.abstraction.entities.MCSlime; +import org.bukkit.entity.Entity; import org.bukkit.entity.Slime; -/** - * - * @author Hekta - */ public class BukkitMCSlime extends BukkitMCLivingEntity implements MCSlime { - public BukkitMCSlime(Slime slime) { + public BukkitMCSlime(Entity slime) { super(slime); } @@ -21,11 +17,11 @@ public BukkitMCSlime(AbstractionObject ao) { @Override public int getSize() { - return ((Slime)getHandle()).getSize(); + return ((Slime) getHandle()).getSize(); } @Override public void setSize(int size) { - ((Slime)getHandle()).setSize(size); + ((Slime) getHandle()).setSize(size); } -} \ No newline at end of file +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCSniffer.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCSniffer.java new file mode 100644 index 0000000000..b33edd4605 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCSniffer.java @@ -0,0 +1,16 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCSniffer; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Sniffer; + +public class BukkitMCSniffer extends BukkitMCAnimal implements MCSniffer { + + Sniffer s; + + public BukkitMCSniffer(Entity e) { + super(e); + this.s = (Sniffer) e; + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCSnowman.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCSnowman.java new file mode 100644 index 0000000000..7a4711f699 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCSnowman.java @@ -0,0 +1,29 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Snowman; + +import com.laytonsmith.abstraction.AbstractionObject; +import com.laytonsmith.abstraction.entities.MCSnowman; + +public class BukkitMCSnowman extends BukkitMCLivingEntity implements MCSnowman { + + public BukkitMCSnowman(Entity snowman) { + super(snowman); + } + + public BukkitMCSnowman(AbstractionObject ao) { + this((Snowman) ao.getHandle()); + } + + @Override + public void setDerp(boolean derp) { + ((Snowman) getHandle()).setDerp(derp); + } + + @Override + public boolean isDerp() { + return ((Snowman) getHandle()).isDerp(); + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCSpectralArrow.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCSpectralArrow.java new file mode 100644 index 0000000000..7a3a65edb0 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCSpectralArrow.java @@ -0,0 +1,55 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCSpectralArrow; +import org.bukkit.entity.Entity; +import org.bukkit.entity.SpectralArrow; + +public class BukkitMCSpectralArrow extends BukkitMCProjectile implements MCSpectralArrow { + + private final SpectralArrow spectral; + + public BukkitMCSpectralArrow(Entity arrow) { + super(arrow); + this.spectral = (SpectralArrow) arrow; + } + + @Override + public int getKnockbackStrength() { + return this.spectral.getKnockbackStrength(); + } + + @Override + public void setKnockbackStrength(int strength) { + this.spectral.setKnockbackStrength(strength); + } + + @Override + public boolean isCritical() { + return this.spectral.isCritical(); + } + + @Override + public void setCritical(boolean critical) { + this.spectral.setCritical(critical); + } + + @Override + public double getDamage() { + return this.spectral.getDamage(); + } + + @Override + public void setDamage(double damage) { + this.spectral.setDamage(damage); + } + + @Override + public int getGlowingTicks() { + return spectral.getGlowingTicks(); + } + + @Override + public void setGlowingTicks(int ticks) { + spectral.setGlowingTicks(ticks); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCStorageMinecart.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCStorageMinecart.java new file mode 100644 index 0000000000..25859fd6e3 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCStorageMinecart.java @@ -0,0 +1,22 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.MCInventory; +import com.laytonsmith.abstraction.bukkit.BukkitMCInventory; +import com.laytonsmith.abstraction.entities.MCStorageMinecart; +import org.bukkit.entity.Entity; +import org.bukkit.entity.minecart.StorageMinecart; + +public class BukkitMCStorageMinecart extends BukkitMCMinecart implements MCStorageMinecart { + + StorageMinecart sm; + + public BukkitMCStorageMinecart(Entity e) { + super(e); + this.sm = (StorageMinecart) e; + } + + @Override + public MCInventory getInventory() { + return new BukkitMCInventory(sm.getInventory()); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCStray.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCStray.java new file mode 100644 index 0000000000..b7dfe44f02 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCStray.java @@ -0,0 +1,12 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCStray; +import org.bukkit.entity.Entity; + +public class BukkitMCStray extends BukkitMCSkeleton implements MCStray { + + public BukkitMCStray(Entity skeleton) { + super(skeleton); + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCStrider.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCStrider.java new file mode 100644 index 0000000000..892c5f7fd2 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCStrider.java @@ -0,0 +1,25 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCStrider; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Strider; + +public class BukkitMCStrider extends BukkitMCAnimal implements MCStrider { + + private final Strider s; + + public BukkitMCStrider(Entity be) { + super(be); + s = (Strider) be; + } + + @Override + public boolean isSaddled() { + return s.hasSaddle(); + } + + @Override + public void setSaddled(boolean saddled) { + s.setSaddle(saddled); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCTNT.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCTNT.java similarity index 50% rename from src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCTNT.java rename to src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCTNT.java index 1901cdaccf..24f64b2d31 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCTNT.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCTNT.java @@ -1,26 +1,36 @@ - -package com.laytonsmith.abstraction.bukkit; +package com.laytonsmith.abstraction.bukkit.entities; import com.laytonsmith.abstraction.MCEntity; -import com.laytonsmith.abstraction.MCTNT; +import com.laytonsmith.abstraction.entities.MCTNT; +import org.bukkit.entity.Entity; import org.bukkit.entity.TNTPrimed; -/** - * - * - */ public class BukkitMCTNT extends BukkitMCEntity implements MCTNT { + TNTPrimed tnt; - public BukkitMCTNT(TNTPrimed e) { + + public BukkitMCTNT(Entity e) { super(e); - this.tnt = e; - } + this.tnt = (TNTPrimed) e; + } @Override public MCEntity getSource() { + if(tnt.getSource() == null) { + return null; + } return new BukkitMCEntity(tnt.getSource()); } + @Override + public void setSource(MCEntity source) { + if(source == null) { + tnt.setSource(null); + } else { + tnt.setSource((Entity) source.getHandle()); + } + } + @Override public int getFuseTicks() { return tnt.getFuseTicks(); diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCTadpole.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCTadpole.java new file mode 100644 index 0000000000..f933d1df4f --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCTadpole.java @@ -0,0 +1,20 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCTadpole; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Tadpole; + +public class BukkitMCTadpole extends BukkitMCLivingEntity implements MCTadpole { + + Tadpole t; + + public BukkitMCTadpole(Entity entity) { + super(entity); + this.t = (Tadpole) entity; + } + + @Override + public Tadpole getHandle() { + return t; + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCTameable.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCTameable.java new file mode 100644 index 0000000000..85297aab53 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCTameable.java @@ -0,0 +1,59 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.AbstractionObject; +import com.laytonsmith.abstraction.MCAnimalTamer; +import com.laytonsmith.abstraction.entities.MCTameable; +import com.laytonsmith.abstraction.bukkit.BukkitMCAnimalTamer; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Tameable; + +public class BukkitMCTameable extends BukkitMCAnimal implements MCTameable { + + Tameable t; + + public BukkitMCTameable(Entity t) { + super(t); + // sometimes an entity was previously tameable in older versions + if(t instanceof Tameable) { + this.t = (Tameable) t; + } + } + + public BukkitMCTameable(AbstractionObject a) { + super((LivingEntity) a.getHandle()); + this.t = ((Tameable) a.getHandle()); + } + + @Override + public boolean isTameable() { + return t != null; + } + + @Override + public boolean isTamed() { + return t.isTamed(); + } + + @Override + public void setTamed(boolean bln) { + t.setTamed(bln); + } + + @Override + public MCAnimalTamer getOwner() { + if(t.getOwner() == null) { + return null; + } + return new BukkitMCAnimalTamer(t.getOwner()); + } + + @Override + public void setOwner(MCAnimalTamer at) { + if(at == null) { + t.setOwner(null); + } else { + t.setOwner(((BukkitMCAnimalTamer) at)._tamer()); + } + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCTextDisplay.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCTextDisplay.java new file mode 100644 index 0000000000..0d1492cee7 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCTextDisplay.java @@ -0,0 +1,102 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.MCColor; +import com.laytonsmith.abstraction.bukkit.BukkitMCColor; +import com.laytonsmith.abstraction.entities.MCTextDisplay; +import org.bukkit.Color; +import org.bukkit.entity.Entity; +import org.bukkit.entity.TextDisplay; +import org.bukkit.entity.TextDisplay.TextAlignment; + +public class BukkitMCTextDisplay extends BukkitMCDisplay implements MCTextDisplay { + + TextDisplay td; + + public BukkitMCTextDisplay(Entity e) { + super(e); + this.td = (TextDisplay) e; + } + + @Override + public MCTextDisplay.Alignment getAlignment() { + return MCTextDisplay.Alignment.valueOf(td.getAlignment().name()); + } + + @Override + public void setAlignment(MCTextDisplay.Alignment alignment) { + td.setAlignment(TextAlignment.valueOf(alignment.name())); + } + + @Override + public MCColor getBackgroundColor() { + if(td.isDefaultBackground()) { + return null; + } + Color color = td.getBackgroundColor(); + if(color == null) { + return null; + } + return BukkitMCColor.GetMCColor(color); + } + + @Override + public void setBackgroundColor(MCColor color) { + if(color == null) { + td.setDefaultBackground(true); + td.setBackgroundColor(null); + } else { + td.setDefaultBackground(false); + td.setBackgroundColor(BukkitMCColor.GetColor(color)); + } + } + + @Override + public int getLineWidth() { + return td.getLineWidth(); + } + + @Override + public void setLineWidth(int width) { + td.setLineWidth(width); + } + + @Override + public boolean isVisibleThroughBlocks() { + return td.isSeeThrough(); + } + + @Override + public void setVisibleThroughBlocks(boolean visible) { + td.setSeeThrough(visible); + } + + @Override + public boolean hasShadow() { + return td.isShadowed(); + } + + @Override + public void setHasShadow(boolean hasShadow) { + td.setShadowed(hasShadow); + } + + @Override + public String getText() { + return td.getText(); + } + + @Override + public void setText(String text) { + td.setText(text); + } + + @Override + public byte getOpacity() { + return td.getTextOpacity(); + } + + @Override + public void setOpacity(byte opacity) { + td.setTextOpacity(opacity); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCThrownPotion.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCThrownPotion.java index 93f6e6dfbd..c399e1a749 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCThrownPotion.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCThrownPotion.java @@ -3,18 +3,14 @@ import com.laytonsmith.abstraction.AbstractionObject; import com.laytonsmith.abstraction.MCItemStack; import com.laytonsmith.abstraction.bukkit.BukkitMCItemStack; -import com.laytonsmith.abstraction.bukkit.BukkitMCProjectile; import com.laytonsmith.abstraction.entities.MCThrownPotion; +import org.bukkit.entity.Entity; import org.bukkit.entity.ThrownPotion; import org.bukkit.inventory.ItemStack; -/** - * - * @author Hekta - */ public class BukkitMCThrownPotion extends BukkitMCProjectile implements MCThrownPotion { - public BukkitMCThrownPotion(ThrownPotion potion) { + public BukkitMCThrownPotion(Entity potion) { super(potion); } @@ -24,7 +20,7 @@ public BukkitMCThrownPotion(AbstractionObject ao) { @Override public ThrownPotion getHandle() { - return (ThrownPotion)super.getHandle(); + return (ThrownPotion) super.getHandle(); } @Override @@ -36,4 +32,4 @@ public MCItemStack getItem() { public void setItem(MCItemStack item) { getHandle().setItem((ItemStack) item.getHandle()); } -} \ No newline at end of file +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCTrader.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCTrader.java new file mode 100644 index 0000000000..1a7d9ff0e4 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCTrader.java @@ -0,0 +1,11 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCTrader; +import org.bukkit.entity.Entity; + +public abstract class BukkitMCTrader extends BukkitMCBreedable implements MCTrader { + + public BukkitMCTrader(Entity be) { + super(be); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCTransformation.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCTransformation.java new file mode 100644 index 0000000000..0fabfd0ad0 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCTransformation.java @@ -0,0 +1,38 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCTransformation; +import org.bukkit.util.Transformation; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +/** + * + */ +public class BukkitMCTransformation implements MCTransformation { + + Transformation transformation; + + public BukkitMCTransformation(Transformation transformation) { + this.transformation = transformation; + } + + @Override + public Vector3f getTranslation() { + return transformation.getTranslation(); + } + + @Override + public Quaternionf getLeftRotation() { + return transformation.getLeftRotation(); + } + + @Override + public Vector3f getScale() { + return transformation.getScale(); + } + + @Override + public Quaternionf getRightRotation() { + return transformation.getRightRotation(); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCTrident.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCTrident.java new file mode 100644 index 0000000000..9a0071a3c2 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCTrident.java @@ -0,0 +1,45 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCTrident; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Trident; + +public class BukkitMCTrident extends BukkitMCProjectile implements MCTrident { + + private final Trident trident; + + public BukkitMCTrident(Entity trident) { + super(trident); + this.trident = (Trident) trident; + } + + @Override + public int getKnockbackStrength() { + return this.trident.getKnockbackStrength(); + } + + @Override + public void setKnockbackStrength(int strength) { + this.trident.setKnockbackStrength(strength); + } + + @Override + public boolean isCritical() { + return this.trident.isCritical(); + } + + @Override + public void setCritical(boolean critical) { + this.trident.setCritical(critical); + } + + @Override + public double getDamage() { + return this.trident.getDamage(); + } + + @Override + public void setDamage(double damage) { + this.trident.setDamage(damage); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCTropicalFish.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCTropicalFish.java new file mode 100644 index 0000000000..08fa44e31e --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCTropicalFish.java @@ -0,0 +1,67 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.Implementation; +import com.laytonsmith.abstraction.entities.MCTropicalFish; +import com.laytonsmith.abstraction.enums.EnumConvertor; +import com.laytonsmith.abstraction.enums.MCDyeColor; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCDyeColor; +import com.laytonsmith.annotations.abstractionenum; +import org.bukkit.entity.Entity; +import org.bukkit.entity.TropicalFish; + +public class BukkitMCTropicalFish extends BukkitMCLivingEntity implements MCTropicalFish { + + TropicalFish entity; + + public BukkitMCTropicalFish(Entity be) { + super(be); + this.entity = (TropicalFish) be; + } + + @Override + public MCDyeColor getPatternColor() { + return BukkitMCDyeColor.getConvertor().getAbstractedEnum(entity.getPatternColor()); + } + + @Override + public void setPatternColor(MCDyeColor color) { + entity.setPatternColor(BukkitMCDyeColor.getConvertor().getConcreteEnum(color)); + } + + @Override + public MCDyeColor getBodyColor() { + return BukkitMCDyeColor.getConvertor().getAbstractedEnum(entity.getBodyColor()); + } + + @Override + public void setBodyColor(MCDyeColor color) { + entity.setBodyColor(BukkitMCDyeColor.getConvertor().getConcreteEnum(color)); + } + + @Override + public MCPattern getPattern() { + return BukkitMCPattern.getConvertor().getAbstractedEnum(entity.getPattern()); + } + + @Override + public void setPattern(MCPattern pattern) { + entity.setPattern(BukkitMCPattern.getConvertor().getConcreteEnum(pattern)); + } + + @abstractionenum( + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCPattern.class, + forConcreteEnum = TropicalFish.Pattern.class + ) + public static class BukkitMCPattern extends EnumConvertor { + + private static BukkitMCPattern instance; + + public static BukkitMCPattern getConvertor() { + if(instance == null) { + instance = new BukkitMCPattern(); + } + return instance; + } + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCVehicle.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCVehicle.java similarity index 66% rename from src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCVehicle.java rename to src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCVehicle.java index 7697140683..cbf79de43c 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/BukkitMCVehicle.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCVehicle.java @@ -1,16 +1,13 @@ -package com.laytonsmith.abstraction.bukkit; +package com.laytonsmith.abstraction.bukkit.entities; -import com.laytonsmith.abstraction.MCVehicle; +import com.laytonsmith.abstraction.entities.MCVehicle; import org.bukkit.entity.Entity; import org.bukkit.entity.Vehicle; -/** - * - * @author jb_aero - */ public class BukkitMCVehicle extends BukkitMCEntity implements MCVehicle { Vehicle v; + public BukkitMCVehicle(Entity e) { super(e); this.v = (Vehicle) e; diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCVex.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCVex.java new file mode 100644 index 0000000000..90f12aaa3c --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCVex.java @@ -0,0 +1,22 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCVex; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Vex; + +public class BukkitMCVex extends BukkitMCLivingEntity implements MCVex { + + public BukkitMCVex(Entity ent) { + super(ent); + } + + @Override + public boolean isCharging() { + return ((Vex) getHandle()).isCharging(); + } + + @Override + public void setCharging(boolean charging) { + ((Vex) getHandle()).setCharging(charging); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCVillager.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCVillager.java index 6b68922270..6901d36811 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCVillager.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCVillager.java @@ -1,19 +1,19 @@ package com.laytonsmith.abstraction.bukkit.entities; import com.laytonsmith.abstraction.AbstractionObject; -import com.laytonsmith.abstraction.bukkit.BukkitMCAgeable; +import com.laytonsmith.abstraction.MCInventory; +import com.laytonsmith.abstraction.MCMerchant; +import com.laytonsmith.abstraction.bukkit.BukkitMCInventory; +import com.laytonsmith.abstraction.bukkit.BukkitMCMerchant; import com.laytonsmith.abstraction.entities.MCVillager; import com.laytonsmith.abstraction.enums.MCProfession; import com.laytonsmith.abstraction.enums.bukkit.BukkitMCProfession; +import org.bukkit.entity.Entity; import org.bukkit.entity.Villager; -/** - * - * @author Hekta - */ -public class BukkitMCVillager extends BukkitMCAgeable implements MCVillager { +public class BukkitMCVillager extends BukkitMCTrader implements MCVillager { - public BukkitMCVillager(Villager villager) { + public BukkitMCVillager(Entity villager) { super(villager); } @@ -23,16 +23,48 @@ public BukkitMCVillager(AbstractionObject ao) { @Override public Villager getHandle() { - return (Villager)super.getHandle(); + return (Villager) super.getHandle(); } @Override public MCProfession getProfession() { - return BukkitMCProfession.getConvertor().getAbstractedEnum(getHandle().getProfession()); + return BukkitMCProfession.valueOfConcrete(getHandle().getProfession()); } @Override public void setProfession(MCProfession profession) { - getHandle().setProfession(BukkitMCProfession.getConvertor().getConcreteEnum(profession)); + getHandle().setProfession((Villager.Profession) profession.getConcrete()); } -} \ No newline at end of file + + @Override + public int getLevel() { + return getHandle().getVillagerLevel(); + } + + @Override + public void setLevel(int level) { + getHandle().setVillagerLevel(level); + } + + @Override + public int getExperience() { + return getHandle().getVillagerExperience(); + } + + @Override + public void setExperience(int exp) { + getHandle().setVillagerExperience(exp); + } + + @Override + public MCMerchant asMerchant() { + Villager villager = getHandle(); + String title = villager.getCustomName() == null ? getHandle().getProfession().name() : villager.getCustomName(); + return new BukkitMCMerchant(villager, title); + } + + @Override + public MCInventory getInventory() { + return new BukkitMCInventory(getHandle().getInventory()); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCVindicator.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCVindicator.java new file mode 100644 index 0000000000..db8120d69d --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCVindicator.java @@ -0,0 +1,12 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCVindicator; +import org.bukkit.entity.Entity; + +public class BukkitMCVindicator extends BukkitMCLivingEntity implements MCVindicator { + + public BukkitMCVindicator(Entity ent) { + super(ent); + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCWanderingTrader.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCWanderingTrader.java new file mode 100644 index 0000000000..60ef31bee0 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCWanderingTrader.java @@ -0,0 +1,34 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.MCInventory; +import com.laytonsmith.abstraction.MCMerchant; +import com.laytonsmith.abstraction.bukkit.BukkitMCInventory; +import com.laytonsmith.abstraction.bukkit.BukkitMCMerchant; +import com.laytonsmith.abstraction.entities.MCWanderingTrader; +import org.bukkit.entity.AbstractVillager; +import org.bukkit.entity.Entity; +import org.bukkit.entity.WanderingTrader; + +public class BukkitMCWanderingTrader extends BukkitMCTrader implements MCWanderingTrader { + + public BukkitMCWanderingTrader(Entity wanderingtrader) { + super(wanderingtrader); + } + + @Override + public WanderingTrader getHandle() { + return (WanderingTrader) super.getHandle(); + } + + @Override + public MCMerchant asMerchant() { + AbstractVillager villager = getHandle(); + String title = getHandle().getCustomName() == null ? villager.getType().name() : villager.getCustomName(); + return new BukkitMCMerchant(villager, title); + } + + @Override + public MCInventory getInventory() { + return new BukkitMCInventory(getHandle().getInventory()); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCWarden.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCWarden.java new file mode 100644 index 0000000000..2984ec789b --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCWarden.java @@ -0,0 +1,20 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCWarden; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Warden; + +public class BukkitMCWarden extends BukkitMCLivingEntity implements MCWarden { + + Warden w; + + public BukkitMCWarden(Entity entity) { + super(entity); + this.w = (Warden) entity; + } + + @Override + public Warden getHandle() { + return this.w; + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCWitch.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCWitch.java new file mode 100644 index 0000000000..4790d88cc6 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCWitch.java @@ -0,0 +1,12 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCWitch; +import org.bukkit.entity.Entity; + +public class BukkitMCWitch extends BukkitMCLivingEntity implements MCWitch { + + public BukkitMCWitch(Entity ent) { + super(ent); + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCWitherSkeleton.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCWitherSkeleton.java new file mode 100644 index 0000000000..0dbb86d3cb --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCWitherSkeleton.java @@ -0,0 +1,12 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCWitherSkeleton; +import org.bukkit.entity.Entity; + +public class BukkitMCWitherSkeleton extends BukkitMCSkeleton implements MCWitherSkeleton { + + public BukkitMCWitherSkeleton(Entity skeleton) { + super(skeleton); + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCWitherSkull.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCWitherSkull.java index d6f6cb807a..93fa3235a6 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCWitherSkull.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCWitherSkull.java @@ -1,35 +1,30 @@ package com.laytonsmith.abstraction.bukkit.entities; -import org.bukkit.entity.WitherSkull; - -import com.laytonsmith.abstraction.bukkit.BukkitMCFireball; import com.laytonsmith.abstraction.entities.MCWitherSkull; +import org.bukkit.entity.Entity; +import org.bukkit.entity.WitherSkull; -/** - * - * @author Veyyn - */ public class BukkitMCWitherSkull extends BukkitMCFireball implements MCWitherSkull { - private final WitherSkull _skull; + private final WitherSkull skull; - public BukkitMCWitherSkull(WitherSkull skull) { + public BukkitMCWitherSkull(Entity skull) { super(skull); - _skull = skull; + this.skull = (WitherSkull) skull; } @Override public WitherSkull getHandle() { - return _skull; + return this.skull; } @Override public boolean isCharged() { - return _skull.isCharged(); + return this.skull.isCharged(); } @Override public void setCharged(boolean charged) { - _skull.setCharged(charged); + this.skull.setCharged(charged); } -} \ No newline at end of file +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCWolf.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCWolf.java index 470e39fb83..308a9fa705 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCWolf.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCWolf.java @@ -1,54 +1,79 @@ package com.laytonsmith.abstraction.bukkit.entities; import com.laytonsmith.abstraction.AbstractionObject; -import com.laytonsmith.abstraction.bukkit.BukkitMCTameable; import com.laytonsmith.abstraction.entities.MCWolf; import com.laytonsmith.abstraction.enums.MCDyeColor; import org.bukkit.DyeColor; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Wolf; -/** - * - * @author jb_aero - */ public class BukkitMCWolf extends BukkitMCTameable implements MCWolf { - + Wolf w; + public BukkitMCWolf(Entity be) { super(be); this.w = (Wolf) be; } - - public BukkitMCWolf(AbstractionObject ao){ - super((LivingEntity)ao.getHandle()); - this.w = (Wolf) ao.getHandle(); - } - + + public BukkitMCWolf(AbstractionObject ao) { + super((LivingEntity) ao.getHandle()); + this.w = (Wolf) ao.getHandle(); + } + @Override public MCDyeColor getCollarColor() { return MCDyeColor.valueOf(w.getCollarColor().name()); } + @Override public boolean isAngry() { return w.isAngry(); } + @Override public boolean isSitting() { return w.isSitting(); } + @Override public void setAngry(boolean angry) { w.setAngry(angry); } + @Override public void setSitting(boolean sitting) { w.setSitting(sitting); } + @Override public void setCollarColor(MCDyeColor color) { w.setCollarColor(DyeColor.valueOf(color.name())); } - + + @Override + public boolean isInterested() { + return w.isInterested(); + } + + @Override + public void setInterested(boolean interested) { + w.setInterested(interested); + } + + @Override + public Variant getWolfVariant() { + return Variant.valueOf(w.getVariant().getKey().getKey().toUpperCase()); + } + + @Override + public void setWolfVariant(Variant variant) { + Wolf.Variant v = Registry.WOLF_VARIANT.get(NamespacedKey.minecraft(variant.name().toLowerCase())); + if(v != null) { + w.setVariant(v); + } + } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCZoglin.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCZoglin.java new file mode 100644 index 0000000000..b529c5bbe3 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCZoglin.java @@ -0,0 +1,17 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCZoglin; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Zoglin; + +public class BukkitMCZoglin extends BukkitMCAgeable implements MCZoglin { + + public BukkitMCZoglin(Entity zombie) { + super(zombie); + } + + @Override + public Zoglin getHandle() { + return (Zoglin) super.getHandle(); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCZombie.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCZombie.java index d7ca03d9a9..29e3b3012e 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCZombie.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCZombie.java @@ -1,46 +1,31 @@ package com.laytonsmith.abstraction.bukkit.entities; -import com.laytonsmith.abstraction.AbstractionObject; -import com.laytonsmith.abstraction.bukkit.BukkitMCLivingEntity; import com.laytonsmith.abstraction.entities.MCZombie; +import org.bukkit.entity.Entity; import org.bukkit.entity.Zombie; -/** - * - * @author Hekta - */ -public class BukkitMCZombie extends BukkitMCLivingEntity implements MCZombie { +public class BukkitMCZombie extends BukkitMCAgeable implements MCZombie { - public BukkitMCZombie(Zombie zombie) { + public BukkitMCZombie(Entity zombie) { super(zombie); } - public BukkitMCZombie(AbstractionObject ao) { - this((Zombie) ao.getHandle()); - } - @Override public Zombie getHandle() { - return (Zombie)super.asLivingEntity(); - } - - @Override - public boolean isBaby() { - return getHandle().isBaby(); - } - - @Override - public void setBaby(boolean isBaby) { - getHandle().setBaby(isBaby); + return (Zombie) super.asLivingEntity(); } @Override - public boolean isVillager() { - return getHandle().isVillager(); + public boolean canBreakDoors() { + return getHandle().canBreakDoors(); } @Override - public void setVillager(boolean isVillager) { - getHandle().setVillager(isVillager); + public void setCanBreakDoors(boolean canBreakDoors) { + try { + getHandle().setCanBreakDoors(canBreakDoors); + } catch(NoSuchMethodError ex) { + // probably before 1.19 + } } -} \ No newline at end of file +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCZombieHorse.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCZombieHorse.java new file mode 100644 index 0000000000..5b977340dd --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCZombieHorse.java @@ -0,0 +1,12 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCZombieHorse; +import org.bukkit.entity.Entity; + +public class BukkitMCZombieHorse extends BukkitMCAbstractHorse implements MCZombieHorse { + + public BukkitMCZombieHorse(Entity t) { + super(t); + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCZombieVillager.java b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCZombieVillager.java new file mode 100644 index 0000000000..70f639e1c2 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/entities/BukkitMCZombieVillager.java @@ -0,0 +1,29 @@ +package com.laytonsmith.abstraction.bukkit.entities; + +import com.laytonsmith.abstraction.entities.MCZombieVillager; +import com.laytonsmith.abstraction.enums.MCProfession; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCProfession; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Villager; +import org.bukkit.entity.ZombieVillager; + +public class BukkitMCZombieVillager extends BukkitMCZombie implements MCZombieVillager { + + ZombieVillager zv; + + public BukkitMCZombieVillager(Entity ent) { + super(ent); + zv = (ZombieVillager) ent; + } + + @Override + public MCProfession getProfession() { + return BukkitMCProfession.valueOfConcrete(zv.getVillagerProfession()); + } + + @Override + public void setProfession(MCProfession profession) { + zv.setVillagerProfession((Villager.Profession) profession.getConcrete()); + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitAbstractEventMixin.java b/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitAbstractEventMixin.java index 4581abcff6..525495d832 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitAbstractEventMixin.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitAbstractEventMixin.java @@ -1,16 +1,14 @@ - - package com.laytonsmith.abstraction.bukkit.events; import com.laytonsmith.abstraction.bukkit.BukkitMCServer; import com.laytonsmith.core.Static; import com.laytonsmith.core.constructs.CString; -import com.laytonsmith.core.constructs.Construct; import com.laytonsmith.core.constructs.Target; -import com.laytonsmith.core.events.AbstractEvent; +import com.laytonsmith.core.events.AbstractGenericEvent; import com.laytonsmith.core.events.BindableEvent; import com.laytonsmith.core.events.EventMixinInterface; import com.laytonsmith.core.exceptions.EventException; +import com.laytonsmith.core.natives.interfaces.Mixed; import java.util.HashMap; import java.util.Map; import org.bukkit.entity.Entity; @@ -19,8 +17,6 @@ import org.bukkit.event.block.BlockEvent; import org.bukkit.event.entity.EntityEvent; import org.bukkit.event.hanging.HangingEvent; -import org.bukkit.event.inventory.FurnaceBurnEvent; -import org.bukkit.event.inventory.FurnaceSmeltEvent; import org.bukkit.event.inventory.InventoryEvent; import org.bukkit.event.player.PlayerEvent; import org.bukkit.event.server.ServerEvent; @@ -28,80 +24,86 @@ import org.bukkit.event.weather.WeatherEvent; import org.bukkit.event.world.WorldEvent; -/** - * - * - */ -public class BukkitAbstractEventMixin implements EventMixinInterface{ - - AbstractEvent mySuper; - - public BukkitAbstractEventMixin(AbstractEvent mySuper){ - this.mySuper = mySuper; - } +public class BukkitAbstractEventMixin implements EventMixinInterface { + + AbstractGenericEvent mySuper; + + public BukkitAbstractEventMixin(AbstractGenericEvent mySuper) { + this.mySuper = mySuper; + } + + @Override + public void cancel(BindableEvent e, boolean state) { + if(e._GetObject() instanceof Cancellable) { + ((Cancellable) e._GetObject()).setCancelled(state); + } + } + + @Override + public boolean shouldFire(BindableEvent event) { + Object e = event._GetObject(); + if(e instanceof PlayerEvent) { + return !((PlayerEvent) e).getPlayer().hasMetadata("NPC"); + } else if(e instanceof EntityEvent && ((EntityEvent) e).getEntity() instanceof Player) { + return !((EntityEvent) e).getEntity().hasMetadata("NPC"); + } + return true; + } + + @Override + public Map evaluate_helper(BindableEvent event) throws EventException { + Map map = new HashMap<>(); + map.put("event_type", new CString(mySuper.getName(), Target.UNKNOWN)); + String macro; + Object e = event._GetObject(); + if(e instanceof BlockEvent) { + macro = "block"; + } else if(e instanceof EntityEvent) { + macro = "entity"; + if(((EntityEvent) e).getEntity() instanceof Player) { + Entity entity = ((EntityEvent) e).getEntity(); + map.put("player", new CString(entity.getName(), Target.UNKNOWN)); + } + } else if(e instanceof HangingEvent) { + macro = "entity"; + } else if(e instanceof InventoryEvent) { + macro = "inventory"; + } else if(e instanceof PlayerEvent) { + map.put("player", new CString(((PlayerEvent) e).getPlayer().getName(), Target.UNKNOWN)); + macro = "player"; + } else if(e instanceof ServerEvent) { + macro = "server"; + } else if(e instanceof VehicleEvent) { + macro = "vehicle"; + } else if(e instanceof WeatherEvent) { + macro = "weather"; + } else if(e instanceof WorldEvent) { + macro = "world"; + } else { + macro = "custom"; + } + map.put("macrotype", new CString(macro, Target.UNKNOWN)); + return map; + } - @Override - public void cancel(BindableEvent e, boolean state){ - if (e._GetObject() instanceof Cancellable) { - ((Cancellable) e._GetObject()).setCancelled(state); - } - } - - @Override - public Map evaluate_helper(BindableEvent event) throws EventException{ - Map map = new HashMap(); - map.put("event_type", new CString(mySuper.getName(), Target.UNKNOWN)); - String macro; - Object e = event._GetObject(); - if(e instanceof BlockEvent){ - macro = "block"; - } else if(e instanceof EntityEvent){ - macro = "entity"; - if(((EntityEvent)e).getEntity() instanceof Player){ - Entity entity = ((EntityEvent)e).getEntity(); - map.put("player", new CString(((Player)entity).getName(), Target.UNKNOWN)); - } - } else if (e instanceof HangingEvent) { - macro = "entity"; - } else if(e instanceof InventoryEvent || e instanceof FurnaceBurnEvent || e instanceof FurnaceSmeltEvent){ - macro = "inventory"; - } else if(e instanceof PlayerEvent){ - map.put("player", new CString(((PlayerEvent)e).getPlayer().getName(), Target.UNKNOWN)); - macro = "player"; - } else if(e instanceof ServerEvent){ - macro = "server"; - } else if(e instanceof VehicleEvent){ - macro = "vehicle"; - } else if(e instanceof WeatherEvent){ - macro = "weather"; - } else if(e instanceof WorldEvent){ - macro = "world"; - } else { - macro = "custom"; - } - map.put("macrotype", new CString(macro, Target.UNKNOWN)); - return map; - } - - @Override - public void manualTrigger(BindableEvent e){ - if(e._GetObject() instanceof org.bukkit.event.Event){ - ((BukkitMCServer)Static.getServer()).__Server().getPluginManager().callEvent((org.bukkit.event.Event)e._GetObject()); - } - } + @Override + public void manualTrigger(BindableEvent e) { + if(e._GetObject() instanceof org.bukkit.event.Event) { + ((BukkitMCServer) Static.getServer()).__Server().getPluginManager().callEvent((org.bukkit.event.Event) e._GetObject()); + } + } - @Override - public boolean isCancellable(BindableEvent o) { - return (o._GetObject() instanceof Cancellable); - } + @Override + public boolean isCancellable(BindableEvent o) { + return (o._GetObject() instanceof Cancellable); + } @Override - public boolean isCancelled(BindableEvent o) { - if(o._GetObject() instanceof Cancellable){ - return ((Cancellable)o._GetObject()).isCancelled(); - } else { - return false; - } - } - + public boolean isCancelled(BindableEvent o) { + if(o._GetObject() instanceof Cancellable) { + return ((Cancellable) o._GetObject()).isCancelled(); + } else { + return false; + } + } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitBlockEvents.java b/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitBlockEvents.java index 22fe08decd..bf78a93a58 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitBlockEvents.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitBlockEvents.java @@ -1,72 +1,177 @@ - - package com.laytonsmith.abstraction.bukkit.events; +import com.laytonsmith.PureUtilities.Vector3D; import com.laytonsmith.abstraction.Implementation; import com.laytonsmith.abstraction.MCEntity; import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.MCNote; import com.laytonsmith.abstraction.MCPlayer; -import com.laytonsmith.abstraction.Velocity; import com.laytonsmith.abstraction.blocks.MCBlock; +import com.laytonsmith.abstraction.blocks.MCBlockFace; import com.laytonsmith.abstraction.blocks.MCBlockState; -import com.laytonsmith.abstraction.bukkit.BukkitMCEntity; +import com.laytonsmith.abstraction.blocks.MCSign; import com.laytonsmith.abstraction.bukkit.BukkitMCItemStack; -import com.laytonsmith.abstraction.bukkit.BukkitMCPlayer; +import com.laytonsmith.abstraction.bukkit.BukkitMCNote; import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCBlock; import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCBlockState; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCEntity; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCPlayer; +import com.laytonsmith.abstraction.enums.MCEquipmentSlot; import com.laytonsmith.abstraction.enums.MCIgniteCause; +import com.laytonsmith.abstraction.enums.MCInstrument; +import com.laytonsmith.abstraction.enums.MCVersion; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCBlockFace; import com.laytonsmith.abstraction.enums.bukkit.BukkitMCIgniteCause; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCInstrument; import com.laytonsmith.abstraction.events.MCBlockBreakEvent; import com.laytonsmith.abstraction.events.MCBlockBurnEvent; import com.laytonsmith.abstraction.events.MCBlockDispenseEvent; import com.laytonsmith.abstraction.events.MCBlockEvent; +import com.laytonsmith.abstraction.events.MCBlockExplodeEvent; +import com.laytonsmith.abstraction.events.MCBlockFadeEvent; +import com.laytonsmith.abstraction.events.MCBlockFromToEvent; import com.laytonsmith.abstraction.events.MCBlockGrowEvent; import com.laytonsmith.abstraction.events.MCBlockIgniteEvent; +import com.laytonsmith.abstraction.events.MCBlockPistonEvent; +import com.laytonsmith.abstraction.events.MCBlockPistonExtendEvent; +import com.laytonsmith.abstraction.events.MCBlockPistonRetractEvent; import com.laytonsmith.abstraction.events.MCBlockPlaceEvent; +import com.laytonsmith.abstraction.events.MCNotePlayEvent; import com.laytonsmith.abstraction.events.MCSignChangeEvent; +import com.laytonsmith.abstraction.events.MCBlockFormEvent; import com.laytonsmith.annotations.abstraction; +import com.laytonsmith.core.Static; import com.laytonsmith.core.constructs.CArray; import com.laytonsmith.core.constructs.CString; import com.laytonsmith.core.constructs.Target; +import org.bukkit.block.Block; import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.block.BlockBurnEvent; import org.bukkit.event.block.BlockDispenseEvent; import org.bukkit.event.block.BlockEvent; +import org.bukkit.event.block.BlockExplodeEvent; +import org.bukkit.event.block.BlockFadeEvent; +import org.bukkit.event.block.BlockFromToEvent; import org.bukkit.event.block.BlockGrowEvent; import org.bukkit.event.block.BlockIgniteEvent; +import org.bukkit.event.block.BlockPistonEvent; +import org.bukkit.event.block.BlockPistonExtendEvent; +import org.bukkit.event.block.BlockPistonRetractEvent; import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.block.NotePlayEvent; import org.bukkit.event.block.SignChangeEvent; +import org.bukkit.event.block.BlockFormEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; import org.bukkit.util.Vector; -/** - * - * @author EntityReborn - */ +import java.util.ArrayList; +import java.util.List; + public class BukkitBlockEvents { - @abstraction(type = Implementation.Type.BUKKIT) - public static class BukkitMCBlockBreakEvent implements MCBlockBreakEvent { + // Stub for actual events below. + public static class BukkitMCBlockPistonEvent implements MCBlockPistonEvent { + + BlockPistonEvent event; + + public BukkitMCBlockPistonEvent(BlockPistonEvent e) { + event = e; + } + + @Override + public Object _GetObject() { + return event; + } + + @Override + public MCBlockFace getDirection() { + return BukkitMCBlockFace.getConvertor().getAbstractedEnum(event.getDirection()); + } + + @Override + public MCBlock getBlock() { + return new BukkitMCBlock(event.getBlock()); + } + + @Override + public boolean isSticky() { + return event.isSticky(); + } + + @Override + public List getAffectedBlocks() { + List bukkitBlocks; + if(event instanceof BlockPistonExtendEvent) { + bukkitBlocks = ((BlockPistonExtendEvent) event).getBlocks(); + } else if(event instanceof BlockPistonRetractEvent) { + bukkitBlocks = ((BlockPistonRetractEvent) event).getBlocks(); + } else { + throw new Error("Unsupported piston event: " + event.getClass().getName()); + } + + List blocks = new ArrayList<>(bukkitBlocks.size()); + for(Block b : bukkitBlocks) { + blocks.add(new BukkitMCBlock(b)); + } + return blocks; + } + + @Override + public boolean isCancelled() { + return event.isCancelled(); + } + + @Override + public void setCancelled(boolean cancelled) { + event.setCancelled(cancelled); + } + } - BlockBreakEvent event; + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCBlockPistonExtendEvent extends BukkitMCBlockPistonEvent implements MCBlockPistonExtendEvent { - public BukkitMCBlockBreakEvent(BlockBreakEvent e) { - event = e; - } + BlockPistonExtendEvent event; + + public BukkitMCBlockPistonExtendEvent(BlockPistonExtendEvent e) { + super(e); + + event = e; + } + } + + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCBlockPistonRetractEvent extends BukkitMCBlockPistonEvent implements MCBlockPistonRetractEvent { + public BukkitMCBlockPistonRetractEvent(BlockPistonRetractEvent e) { + super(e); + } + } + + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCBlockBreakEvent implements MCBlockBreakEvent { + + private final BlockBreakEvent event; + private boolean dropsModified = false; + private List drops; + + public BukkitMCBlockBreakEvent(BlockBreakEvent e) { + event = e; + } @Override - public Object _GetObject() { - return event; - } + public Object _GetObject() { + return event; + } @Override - public MCPlayer getPlayer() { - return new BukkitMCPlayer(event.getPlayer()); - } + public MCPlayer getPlayer() { + return new BukkitMCPlayer(event.getPlayer()); + } @Override - public MCBlock getBlock() { - return new BukkitMCBlock(event.getBlock()); - } + public MCBlock getBlock() { + return new BukkitMCBlock(event.getBlock()); + } @Override public int getExpToDrop() { @@ -77,76 +182,113 @@ public int getExpToDrop() { public void setExpToDrop(int exp) { event.setExpToDrop(exp); } - } - @abstraction(type = Implementation.Type.BUKKIT) - public static class BukkitMCBlockPlaceEvent implements MCBlockPlaceEvent { + @Override + public List getDrops() { + if(drops == null) { + drops = new ArrayList<>(); + for(ItemStack item : event.getBlock().getDrops(event.getPlayer().getInventory().getItemInMainHand())) { + drops.add(new BukkitMCItemStack(item)); + } + } + return drops; + } - BlockPlaceEvent event; + @Override + public void setDrops(List drops) { + dropsModified = true; + this.drops = drops; + } - public BukkitMCBlockPlaceEvent(BlockPlaceEvent e) { - event = e; - } + @Override + public boolean isModified() { + return dropsModified; + } + } + + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCBlockPlaceEvent implements MCBlockPlaceEvent { + + BlockPlaceEvent event; + + public BukkitMCBlockPlaceEvent(BlockPlaceEvent e) { + event = e; + } @Override - public Object _GetObject() { - return event; - } + public Object _GetObject() { + return event; + } @Override - public MCPlayer getPlayer() { - return new BukkitMCPlayer(event.getPlayer()); - } + public MCPlayer getPlayer() { + return new BukkitMCPlayer(event.getPlayer()); + } + + @Override + public MCBlock getBlock() { + return new BukkitMCBlock(event.getBlock()); + } @Override - public MCBlock getBlock() { - return new BukkitMCBlock(event.getBlock()); - } + public MCBlock getBlockAgainst() { + return new BukkitMCBlock(event.getBlockAgainst()); + } @Override - public MCBlock getBlockAgainst() { - return new BukkitMCBlock(event.getBlockAgainst()); - } + public MCItemStack getItemInHand() { + return new BukkitMCItemStack(event.getItemInHand()); + } @Override - public MCItemStack getItemInHand() { - return new BukkitMCItemStack(event.getItemInHand()); - } + public MCEquipmentSlot getHand() { + if(event.getHand() == EquipmentSlot.HAND) { + return MCEquipmentSlot.WEAPON; + } + return MCEquipmentSlot.OFF_HAND; + } @Override - public boolean canBuild() { - return event.canBuild(); - } + public boolean canBuild() { + return event.canBuild(); + } @Override - public MCBlockState getBlockReplacedState() { - return new BukkitMCBlockState(event.getBlockReplacedState()); - } - } + public MCBlockState getBlockReplacedState() { + return new BukkitMCBlockState(event.getBlockReplacedState()); + } + } @abstraction(type = Implementation.Type.BUKKIT) - public static class BukkitMCBlockBurnEvent implements MCBlockBurnEvent { + public static class BukkitMCBlockBurnEvent implements MCBlockBurnEvent { - BlockBurnEvent event; + BlockBurnEvent event; - public BukkitMCBlockBurnEvent(BlockBurnEvent e) { - event = e; - } + public BukkitMCBlockBurnEvent(BlockBurnEvent e) { + event = e; + } @Override - public Object _GetObject() { - return event; - } + public Object _GetObject() { + return event; + } @Override - public MCBlock getBlock() { - return new BukkitMCBlock(event.getBlock()); - } - } + public MCBlock getBlock() { + return new BukkitMCBlock(event.getBlock()); + } + + @Override + public MCBlock getFireBlock() { + if(event.getIgnitingBlock() == null) { + return null; + } + return new BukkitMCBlock(event.getIgnitingBlock()); + } + } @abstraction(type = Implementation.Type.BUKKIT) - public static class BukkitMCBlockIgniteEvent extends BukkitMCBlockEvent - implements MCBlockIgniteEvent { + public static class BukkitMCBlockIgniteEvent extends BukkitMCBlockEvent implements MCBlockIgniteEvent { BlockIgniteEvent event; @@ -162,7 +304,7 @@ public MCIgniteCause getCause() { @Override public MCEntity getIgnitingEntity() { - if (event.getIgnitingEntity() != null) { + if(event.getIgnitingEntity() != null) { return new BukkitMCEntity(event.getIgnitingEntity()); } @@ -171,7 +313,7 @@ public MCEntity getIgnitingEntity() { @Override public MCBlock getIgnitingBlock() { - if (event.getIgnitingBlock() != null) { + if(event.getIgnitingBlock() != null) { return new BukkitMCBlock(event.getIgnitingBlock()); } @@ -180,7 +322,7 @@ public MCBlock getIgnitingBlock() { @Override public MCPlayer getPlayer() { - if (event.getPlayer() != null) { + if(event.getPlayer() != null) { return new BukkitMCPlayer(event.getPlayer()); } @@ -188,67 +330,115 @@ public MCPlayer getPlayer() { } } - @abstraction(type = Implementation.Type.BUKKIT) - public static class BukkitMCSignChangeEvent implements MCSignChangeEvent { + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCBlockFromToEvent implements MCBlockFromToEvent { - SignChangeEvent pie; + BlockFromToEvent event; - public BukkitMCSignChangeEvent(SignChangeEvent e) { - pie = e; - } + public BukkitMCBlockFromToEvent(BlockFromToEvent e) { + event = e; + } - public static BukkitMCSignChangeEvent _instantiate(MCBlock sign, MCPlayer player, CArray signtext) { - String[] text = new String[4]; - for (int i = 0; i < signtext.size(); i++) { - text[i] = signtext.get(i, Target.UNKNOWN).toString(); - } - return new BukkitMCSignChangeEvent(new SignChangeEvent(( (BukkitMCBlock) sign ).__Block(), ( (BukkitMCPlayer) player )._Player(), - text)); - } + @Override + public Object _GetObject() { + return event; + } @Override - public MCPlayer getPlayer() { - return new BukkitMCPlayer(pie.getPlayer()); - } + public MCBlock getBlock() { + return new BukkitMCBlock(event.getBlock()); + } @Override - public CString getLine(int index) { - return new CString(pie.getLine(index), Target.UNKNOWN); - } + public MCBlock getToBlock() { + return new BukkitMCBlock(event.getToBlock()); + } @Override - public CArray getLines() { - CArray retn = new CArray(Target.UNKNOWN); + public MCBlockFace getBlockFace() { + return BukkitMCBlockFace.getConvertor().getAbstractedEnum(event.getFace()); + } - for (int i = 0; i < 4; i++) { - retn.push(new CString(pie.getLine(i), Target.UNKNOWN)); - } + @Override + public boolean isCancelled() { + return event.isCancelled(); + } - return retn; - } + @Override + public void setCancelled(boolean cancelled) { + event.setCancelled(cancelled); + } + } + + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCSignChangeEvent implements MCSignChangeEvent { + + SignChangeEvent pie; + + public BukkitMCSignChangeEvent(SignChangeEvent e) { + pie = e; + } + + public static BukkitMCSignChangeEvent _instantiate(MCBlock sign, MCPlayer player, CArray signtext) { + String[] text = new String[4]; + for(int i = 0; i < signtext.size(); i++) { + text[i] = signtext.get(i, Target.UNKNOWN).toString(); + } + return new BukkitMCSignChangeEvent(new SignChangeEvent(((BukkitMCBlock) sign).__Block(), ((BukkitMCPlayer) player)._Player(), + text)); + } @Override - public void setLine(int index, String text) { - pie.setLine(index, text); - } + public MCPlayer getPlayer() { + return new BukkitMCPlayer(pie.getPlayer()); + } @Override - public void setLines(String[] text) { - for (int i = 0; i < 4; i++) { - pie.setLine(i, text[i]); - } - } + public CString getLine(int index) { + return new CString(pie.getLine(index), Target.UNKNOWN); + } @Override - public MCBlock getBlock() { - return new BukkitMCBlock(pie.getBlock()); - } + public CArray getLines() { + CArray retn = new CArray(Target.UNKNOWN); + + for(int i = 0; i < 4; i++) { + retn.push(new CString(pie.getLine(i), Target.UNKNOWN), Target.UNKNOWN); + } + + return retn; + } + + @Override + public MCSign.Side getSide() { + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_20)) { + return pie.getSide() == org.bukkit.block.sign.Side.FRONT ? MCSign.Side.FRONT : MCSign.Side.BACK; + } + return MCSign.Side.FRONT; + } + + @Override + public void setLine(int index, String text) { + pie.setLine(index, text); + } + + @Override + public void setLines(String[] text) { + for(int i = 0; i < 4; i++) { + pie.setLine(i, text[i]); + } + } + + @Override + public MCBlock getBlock() { + return new BukkitMCBlock(pie.getBlock()); + } @Override - public Object _GetObject() { - return pie; - } - } + public Object _GetObject() { + return pie; + } + } @abstraction(type = Implementation.Type.BUKKIT) public static class BukkitMCBlockEvent implements MCBlockEvent { @@ -271,8 +461,7 @@ public Object _GetObject() { } @abstraction(type = Implementation.Type.BUKKIT) - public static class BukkitMCBlockDispenseEvent extends BukkitMCBlockEvent - implements MCBlockDispenseEvent { + public static class BukkitMCBlockDispenseEvent extends BukkitMCBlockEvent implements MCBlockDispenseEvent { BlockDispenseEvent bde; @@ -292,14 +481,14 @@ public void setItem(MCItemStack item) { } @Override - public Velocity getVelocity() { + public Vector3D getVelocity() { Vector v = bde.getVelocity(); - return new Velocity(v.length(), v.getX(), v.getY(), v.getZ()); + return new Vector3D(v.getX(), v.getY(), v.getZ()); } @Override - public void setVelocity(Velocity vel) { - Vector v = new Vector(vel.x, vel.y, vel.z); + public void setVelocity(Vector3D vel) { + Vector v = new Vector(vel.X(), vel.Y(), vel.Z()); bde.setVelocity(v); } @@ -338,4 +527,130 @@ public MCBlockState getNewState() { return new BukkitMCBlockState(bge.getNewState()); } } + + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCNotePlayEvent implements MCNotePlayEvent { + + NotePlayEvent npe; + + public BukkitMCNotePlayEvent(NotePlayEvent event) { + npe = event; + } + + @Override + public Object _GetObject() { + return npe; + } + + @Override + public MCBlock getBlock() { + return new BukkitMCBlock(npe.getBlock()); + } + + @Override + public MCNote getNote() { + return new BukkitMCNote(npe.getNote()); + } + + @Override + public MCInstrument getInstrument() { + return BukkitMCInstrument.getConvertor().getAbstractedEnum(npe.getInstrument()); + } + } + + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCBlockFadeEvent implements MCBlockFadeEvent { + + BlockFadeEvent bfe; + + public BukkitMCBlockFadeEvent(BlockFadeEvent bfe) { + this.bfe = bfe; + } + + @Override + public MCBlock getBlock() { + return new BukkitMCBlock(bfe.getBlock()); + } + + @Override + public MCBlockState getNewState() { + return new BukkitMCBlockState(bfe.getNewState()); + } + + @Override + public Object _GetObject() { + return bfe; + } + } + + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCBlockExplodeEvent implements MCBlockExplodeEvent { + + BlockExplodeEvent event; + + public BukkitMCBlockExplodeEvent(BlockExplodeEvent event) { + this.event = event; + } + + @Override + public List getBlocks() { + List ret = new ArrayList<>(); + for(Block b : event.blockList()) { + ret.add(new BukkitMCBlock(b)); + } + return ret; + } + + @Override + public void setBlocks(List blocks) { + event.blockList().clear(); + for(MCBlock b : blocks) { + event.blockList().add((Block) b.getHandle()); + } + } + + @Override + public float getYield() { + return event.getYield(); + } + + @Override + public void setYield(float power) { + event.setYield(power); + } + + @Override + public MCBlock getBlock() { + return new BukkitMCBlock(event.getBlock()); + } + + @Override + public Object _GetObject() { + return event; + } + } + + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitBlockFormEventEvent implements MCBlockFormEvent { + BlockFormEvent event; + + public BukkitBlockFormEventEvent(BlockFormEvent event) { + this.event = event; + } + + @Override + public MCBlock getBlock() { + return new BukkitMCBlock(event.getBlock()); + } + + @Override + public MCBlockState getNewState() { + return new BukkitMCBlockState(event.getNewState()); + } + + @Override + public Object _GetObject() { + return event; + } + } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitEntityEvents.java b/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitEntityEvents.java index 1dd8b27224..d5ec808a79 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitEntityEvents.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitEntityEvents.java @@ -1,37 +1,55 @@ package com.laytonsmith.abstraction.bukkit.events; +import com.laytonsmith.PureUtilities.Vector3D; import com.laytonsmith.abstraction.Implementation; import com.laytonsmith.abstraction.MCEntity; -import com.laytonsmith.abstraction.MCHanging; -import com.laytonsmith.abstraction.MCItem; +import com.laytonsmith.abstraction.blocks.MCBlockData; +import com.laytonsmith.abstraction.blocks.MCBlockFace; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCBlockData; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCThrownPotion; +import com.laytonsmith.abstraction.entities.MCHanging; +import com.laytonsmith.abstraction.entities.MCItem; import com.laytonsmith.abstraction.MCItemStack; import com.laytonsmith.abstraction.MCLivingEntity; import com.laytonsmith.abstraction.MCLocation; import com.laytonsmith.abstraction.MCPlayer; -import com.laytonsmith.abstraction.MCProjectile; +import com.laytonsmith.abstraction.entities.MCProjectile; import com.laytonsmith.abstraction.blocks.MCBlock; import com.laytonsmith.abstraction.blocks.MCMaterial; import com.laytonsmith.abstraction.bukkit.BukkitConvertor; -import com.laytonsmith.abstraction.bukkit.BukkitMCEntity; -import com.laytonsmith.abstraction.bukkit.BukkitMCHanging; -import com.laytonsmith.abstraction.bukkit.BukkitMCItem; import com.laytonsmith.abstraction.bukkit.BukkitMCItemStack; -import com.laytonsmith.abstraction.bukkit.BukkitMCLivingEntity; import com.laytonsmith.abstraction.bukkit.BukkitMCLocation; -import com.laytonsmith.abstraction.bukkit.BukkitMCPlayer; -import com.laytonsmith.abstraction.bukkit.BukkitMCProjectile; import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCBlock; import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCMaterial; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCEntity; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCFirework; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCHanging; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCItem; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCLivingEntity; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCPlayer; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCProjectile; +import com.laytonsmith.abstraction.entities.MCFirework; +import com.laytonsmith.abstraction.entities.MCThrownPotion; import com.laytonsmith.abstraction.enums.MCDamageCause; import com.laytonsmith.abstraction.enums.MCEntityType; -import com.laytonsmith.abstraction.enums.MCMobs; +import com.laytonsmith.abstraction.enums.MCEquipmentSlot; +import com.laytonsmith.abstraction.enums.MCPotionAction; +import com.laytonsmith.abstraction.enums.MCPotionCause; +import com.laytonsmith.abstraction.enums.MCRegainReason; import com.laytonsmith.abstraction.enums.MCRemoveCause; import com.laytonsmith.abstraction.enums.MCSpawnReason; import com.laytonsmith.abstraction.enums.MCTargetReason; +import com.laytonsmith.abstraction.enums.MCUnleashReason; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCBlockFace; import com.laytonsmith.abstraction.enums.bukkit.BukkitMCDamageCause; import com.laytonsmith.abstraction.enums.bukkit.BukkitMCEntityType; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCRegainReason; import com.laytonsmith.abstraction.enums.bukkit.BukkitMCRemoveCause; import com.laytonsmith.abstraction.enums.bukkit.BukkitMCSpawnReason; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCUnleashReason; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCPotionAction; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCPotionCause; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCPotionEffectType; import com.laytonsmith.abstraction.events.MCCreatureSpawnEvent; import com.laytonsmith.abstraction.events.MCEntityChangeBlockEvent; import com.laytonsmith.abstraction.events.MCEntityDamageByEntityEvent; @@ -39,57 +57,113 @@ import com.laytonsmith.abstraction.events.MCEntityDeathEvent; import com.laytonsmith.abstraction.events.MCEntityEnterPortalEvent; import com.laytonsmith.abstraction.events.MCEntityExplodeEvent; +import com.laytonsmith.abstraction.events.MCEntityInteractEvent; +import com.laytonsmith.abstraction.events.MCEntityPortalEvent; +import com.laytonsmith.abstraction.events.MCEntityRegainHealthEvent; import com.laytonsmith.abstraction.events.MCEntityTargetEvent; +import com.laytonsmith.abstraction.events.MCEntityToggleGlideEvent; +import com.laytonsmith.abstraction.events.MCEntityToggleSwimEvent; +import com.laytonsmith.abstraction.events.MCEntityUnleashEvent; +import com.laytonsmith.abstraction.events.MCEntityPotionEffectEvent; +import com.laytonsmith.abstraction.events.MCFireworkExplodeEvent; import com.laytonsmith.abstraction.events.MCHangingBreakEvent; +import com.laytonsmith.abstraction.events.MCHangingPlaceEvent; +import com.laytonsmith.abstraction.events.MCItemDespawnEvent; import com.laytonsmith.abstraction.events.MCItemSpawnEvent; import com.laytonsmith.abstraction.events.MCPlayerDropItemEvent; +import com.laytonsmith.abstraction.events.MCPlayerInteractAtEntityEvent; import com.laytonsmith.abstraction.events.MCPlayerInteractEntityEvent; import com.laytonsmith.abstraction.events.MCPlayerPickupItemEvent; import com.laytonsmith.abstraction.events.MCPotionSplashEvent; import com.laytonsmith.abstraction.events.MCProjectileHitEvent; import com.laytonsmith.abstraction.events.MCProjectileLaunchEvent; import com.laytonsmith.annotations.abstraction; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import org.bukkit.Location; +import org.bukkit.Material; import org.bukkit.Sound; +import org.bukkit.World; import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.event.Event; import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.event.entity.EntityChangeBlockEvent; import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.event.entity.EntityExplodeEvent; +import org.bukkit.event.entity.EntityInteractEvent; +import org.bukkit.event.entity.EntityPickupItemEvent; import org.bukkit.event.entity.EntityPortalEnterEvent; +import org.bukkit.event.entity.EntityPortalEvent; +import org.bukkit.event.entity.EntityRegainHealthEvent; import org.bukkit.event.entity.EntityTargetEvent; +import org.bukkit.event.entity.EntityToggleGlideEvent; +import org.bukkit.event.entity.EntityToggleSwimEvent; +import org.bukkit.event.entity.EntityPotionEffectEvent; +import org.bukkit.event.entity.FireworkExplodeEvent; +import org.bukkit.event.entity.ItemDespawnEvent; import org.bukkit.event.entity.ItemSpawnEvent; import org.bukkit.event.entity.PotionSplashEvent; import org.bukkit.event.entity.ProjectileHitEvent; import org.bukkit.event.entity.ProjectileLaunchEvent; import org.bukkit.event.hanging.HangingBreakByEntityEvent; import org.bukkit.event.hanging.HangingBreakEvent; +import org.bukkit.event.hanging.HangingPlaceEvent; import org.bukkit.event.player.PlayerDropItemEvent; +import org.bukkit.event.player.PlayerInteractAtEntityEvent; import org.bukkit.event.player.PlayerInteractEntityEvent; -import org.bukkit.event.player.PlayerPickupItemEvent; +import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.bukkit.util.Vector; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.bukkit.event.entity.EntityUnleashEvent; -/** - * - * @author EntityReborn - */ public class BukkitEntityEvents { - + + public static class BukkitMCItemDespawnEvent implements MCItemDespawnEvent { + + ItemDespawnEvent ide; + + public BukkitMCItemDespawnEvent(Event event) { + ide = (ItemDespawnEvent) event; + } + + @Override + public Object _GetObject() { + return ide; + } + + @Override + public MCItem getEntity() { + return new BukkitMCItem(ide.getEntity()); + } + + @Override + public MCLocation getLocation() { + return new BukkitMCLocation(ide.getLocation()); + } + } + public static class BukkitMCItemSpawnEvent implements MCItemSpawnEvent { ItemSpawnEvent ise; - public BukkitMCItemSpawnEvent(ItemSpawnEvent event) { - ise = event; + + public BukkitMCItemSpawnEvent(Event event) { + ise = (ItemSpawnEvent) event; } - + @Override public Object _GetObject() { return ise; @@ -105,14 +179,15 @@ public MCLocation getLocation() { return new BukkitMCLocation(ise.getLocation()); } } - + public static class BukkitMCEntityExplodeEvent implements MCEntityExplodeEvent { EntityExplodeEvent e; - public BukkitMCEntityExplodeEvent(EntityExplodeEvent event) { - e = event; + + public BukkitMCEntityExplodeEvent(Event event) { + e = (EntityExplodeEvent) event; } - + @Override public Object _GetObject() { return e; @@ -120,25 +195,22 @@ public Object _GetObject() { @Override public MCEntity getEntity() { - if (e.getEntity() != null) { - return BukkitConvertor.BukkitGetCorrectEntity(e.getEntity()); - } - return null; + return BukkitConvertor.BukkitGetCorrectEntity(e.getEntity()); } @Override public List getBlocks() { - List ret = new ArrayList(); - for (Block b : e.blockList()) { + List ret = new ArrayList<>(); + for(Block b : e.blockList()) { ret.add(new BukkitMCBlock(b)); } return ret; } - + @Override public void setBlocks(List blocks) { e.blockList().clear(); - for (MCBlock b : blocks) { + for(MCBlock b : blocks) { e.blockList().add(((BukkitMCBlock) b).__Block()); } } @@ -161,17 +233,18 @@ public void setYield(float power) { @abstraction(type = Implementation.Type.BUKKIT) public static class BukkitMCProjectileHitEvent implements MCProjectileHitEvent { - + ProjectileHitEvent phe; - public BukkitMCProjectileHitEvent(ProjectileHitEvent event) { - phe = event; + + public BukkitMCProjectileHitEvent(Event event) { + phe = (ProjectileHitEvent) event; } @Override public Object _GetObject() { return phe; } - + @Override public MCProjectile getEntity() { return new BukkitMCProjectile(phe.getEntity()); @@ -179,15 +252,37 @@ public MCProjectile getEntity() { @Override public MCEntityType getEntityType() { - return BukkitMCEntityType.getConvertor().getAbstractedEnum(phe.getEntityType()); + return BukkitMCEntityType.valueOfConcrete(phe.getEntityType()); + } + + @Override + public MCEntity getHitEntity() { + return BukkitConvertor.BukkitGetCorrectEntity(phe.getHitEntity()); } - + + @Override + public MCBlock getHitBlock() { + Block blk = phe.getHitBlock(); + if(blk == null) { + return null; + } + return new BukkitMCBlock(blk); + } + + @Override + public MCBlockFace getHitFace() { + BlockFace bf = phe.getHitBlockFace(); + if(bf == null) { + return null; + } + return BukkitMCBlockFace.getConvertor().getAbstractedEnum(phe.getHitBlockFace()); + } + public static BukkitMCProjectileHitEvent _instantiate(MCProjectile p) { return new BukkitMCProjectileHitEvent( - new ProjectileHitEvent( - ((BukkitMCProjectile) p).asProjectile())); + new ProjectileHitEvent(((Projectile) p.getHandle()))); } - + } @abstraction(type = Implementation.Type.BUKKIT) @@ -195,8 +290,8 @@ public static class BukkitMCProjectileLaunchEvent implements MCProjectileLaunchE ProjectileLaunchEvent ple; - public BukkitMCProjectileLaunchEvent(ProjectileLaunchEvent event) { - ple = event; + public BukkitMCProjectileLaunchEvent(Event event) { + ple = (ProjectileLaunchEvent) event; } @Override @@ -206,23 +301,22 @@ public Object _GetObject() { @Override public MCProjectile getEntity() { - return new BukkitMCProjectile(ple.getEntity()); + return (MCProjectile) BukkitConvertor.BukkitGetCorrectEntity(ple.getEntity()); } @Override public MCEntityType getEntityType() { - return BukkitMCEntityType.getConvertor().getAbstractedEnum(ple.getEntityType()); + return BukkitMCEntityType.valueOfConcrete(ple.getEntityType()); } } @abstraction(type = Implementation.Type.BUKKIT) - public static class BukkitMCPotionSplashEvent extends BukkitMCProjectileHitEvent - implements MCPotionSplashEvent { - + public static class BukkitMCPotionSplashEvent implements MCPotionSplashEvent { + PotionSplashEvent pse; - public BukkitMCPotionSplashEvent(PotionSplashEvent event) { - super(event); - pse = event; + + public BukkitMCPotionSplashEvent(Event event) { + pse = (PotionSplashEvent) event; } @Override @@ -231,32 +325,32 @@ public Object _GetObject() { } @Override - public Set getAffectedEntities() { - Set ret = new HashSet(); - for (LivingEntity le : pse.getAffectedEntities()) { - ret.add((MCLivingEntity) BukkitConvertor.BukkitGetCorrectEntity(le)); - } - return ret; + public MCThrownPotion getEntity() { + return new BukkitMCThrownPotion(pse.getEntity()); } @Override - public double getIntensity(MCLivingEntity le) { - return pse.getIntensity(((BukkitMCLivingEntity) le).asLivingEntity()); + public Map getAffectedEntities() { + Map ret = new HashMap<>(); + for(LivingEntity le : pse.getAffectedEntities()) { + ret.put(new BukkitMCLivingEntity(le), pse.getIntensity(le)); + } + return ret; } @Override public void setIntensity(MCLivingEntity le, double intensity) { pse.setIntensity(((BukkitMCLivingEntity) le).asLivingEntity(), intensity); } - } - + @abstraction(type = Implementation.Type.BUKKIT) public static class BukkitMCEntityDeathEvent implements MCEntityDeathEvent { EntityDeathEvent e; - public BukkitMCEntityDeathEvent(EntityDeathEvent e) { - this.e = e; + + public BukkitMCEntityDeathEvent(Event e) { + this.e = (EntityDeathEvent) e; } @Override @@ -271,25 +365,25 @@ public int getDroppedExp() { @Override public List getDrops() { - List islist = e.getDrops(); - List drops = new ArrayList(); - - for(ItemStack is : islist){ - drops.add(new BukkitMCItemStack(is)); - } - - return drops; - } - - @Override - public void clearDrops() { - e.getDrops().clear(); - } - - @Override - public void addDrop(MCItemStack is){ - e.getDrops().add(((BukkitMCItemStack)is).__ItemStack()); - } + List islist = e.getDrops(); + List drops = new ArrayList<>(); + + for(ItemStack is : islist) { + drops.add(new BukkitMCItemStack(is)); + } + + return drops; + } + + @Override + public void clearDrops() { + e.getDrops().clear(); + } + + @Override + public void addDrop(MCItemStack is) { + e.getDrops().add(((BukkitMCItemStack) is).__ItemStack()); + } @Override public MCLivingEntity getEntity() { @@ -300,17 +394,17 @@ public MCLivingEntity getEntity() { public void setDroppedExp(int exp) { e.setDroppedExp(exp); } - } - + @abstraction(type = Implementation.Type.BUKKIT) public static class BukkitMCCreatureSpawnEvent implements MCCreatureSpawnEvent { CreatureSpawnEvent e; - public BukkitMCCreatureSpawnEvent(CreatureSpawnEvent event) { - this.e = event; + + public BukkitMCCreatureSpawnEvent(Event event) { + this.e = ((CreatureSpawnEvent) event); } - + @Override public Object _GetObject() { return e; @@ -330,23 +424,23 @@ public MCLocation getLocation() { public MCSpawnReason getSpawnReason() { return BukkitMCSpawnReason.getConvertor().getAbstractedEnum(e.getSpawnReason()); } - + @Override - public void setType(MCMobs type) { + public void setType(MCEntityType type) { e.setCancelled(true); e.getLocation().getWorld().spawnEntity(e.getLocation(), EntityType.valueOf(type.name())); } - } - + @abstraction(type = Implementation.Type.BUKKIT) public static class BukkitMCPlayerInteractEntityEvent implements MCPlayerInteractEntityEvent { PlayerInteractEntityEvent e; - public BukkitMCPlayerInteractEntityEvent(PlayerInteractEntityEvent event) { - this.e = event; + + public BukkitMCPlayerInteractEntityEvent(Event event) { + this.e = (PlayerInteractEntityEvent) event; } - + @Override public Object _GetObject() { return e; @@ -371,60 +465,87 @@ public void setCancelled(boolean cancelled) { public MCPlayer getPlayer() { return new BukkitMCPlayer(e.getPlayer()); } - + + @Override + public MCEquipmentSlot getHand() { + if(e.getHand() == EquipmentSlot.HAND) { + return MCEquipmentSlot.WEAPON; + } + return MCEquipmentSlot.OFF_HAND; + } } - - @abstraction(type = Implementation.Type.BUKKIT) - public static class BukkitMCPlayerDropItemEvent implements MCPlayerDropItemEvent { - PlayerDropItemEvent e; - - public BukkitMCPlayerDropItemEvent(PlayerDropItemEvent e) { - this.e = e; - } - - @Override - public MCItem getItemDrop() { - return new BukkitMCItem(e.getItemDrop()); - } - - @Override - public void setItemStack(MCItemStack stack) { - BukkitMCItemStack s = (BukkitMCItemStack) stack; - if(s.getTypeId() == 0) { - e.getItemDrop().remove(); - } else { - e.getItemDrop().setItemStack(s.__ItemStack()); - } - } - - @Override - public boolean isCancelled() { - return e.isCancelled(); - } - - @Override - public void setCancelled(boolean cancelled) { - e.setCancelled(cancelled); - } - - @Override - public MCPlayer getPlayer() { - return new BukkitMCPlayer(e.getPlayer()); - } - - @Override - public Object _GetObject() { - return e; - } - } - + + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCPlayerInteractAtEntityEvent extends BukkitMCPlayerInteractEntityEvent implements MCPlayerInteractAtEntityEvent { + + PlayerInteractAtEntityEvent e; + + public BukkitMCPlayerInteractAtEntityEvent(Event event) { + super(event); + this.e = (PlayerInteractAtEntityEvent) event; + } + + @Override + public Vector3D getClickedPosition() { + Vector v = e.getClickedPosition(); + return new Vector3D(v.getX(), v.getY(), v.getZ()); + } + } + + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCPlayerDropItemEvent implements MCPlayerDropItemEvent { + + PlayerDropItemEvent e; + + public BukkitMCPlayerDropItemEvent(Event e) { + this.e = (PlayerDropItemEvent) e; + } + + @Override + public MCItem getItemDrop() { + return new BukkitMCItem(e.getItemDrop()); + } + + @Override + public void setItemStack(MCItemStack stack) { + ItemStack is = (ItemStack) stack.getHandle(); + if(is == null || is.getType().equals(Material.AIR)) { + e.getItemDrop().remove(); + } else { + e.getItemDrop().setItemStack(is); + } + } + + @Override + public boolean isCancelled() { + return e.isCancelled(); + } + + @Override + public void setCancelled(boolean cancelled) { + e.setCancelled(cancelled); + } + + @Override + public MCPlayer getPlayer() { + return new BukkitMCPlayer(e.getPlayer()); + } + + @Override + public Object _GetObject() { + return e; + } + } + @abstraction(type = Implementation.Type.BUKKIT) public static class BukkitMCPlayerPickupItemEvent implements MCPlayerPickupItemEvent { - PlayerPickupItemEvent e; - - public BukkitMCPlayerPickupItemEvent(PlayerPickupItemEvent e) { - this.e = e; + + EntityPickupItemEvent e; + + public BukkitMCPlayerPickupItemEvent(Event e) { + this.e = (EntityPickupItemEvent) e; } + @Override public int getRemaining() { return e.getRemaining(); @@ -437,15 +558,14 @@ public MCItem getItem() { @Override public void setItemStack(MCItemStack stack) { - BukkitMCItemStack s = (BukkitMCItemStack)stack; - e.setCancelled(true); - e.getItem().remove(); - if(s.getTypeId() == 0) { - return; - } else { - e.getPlayer().getInventory().addItem(s.asItemStack()); + ItemStack is = (ItemStack) stack.getHandle(); + e.setCancelled(true); + e.getItem().remove(); + if(is != null && !is.getType().equals(Material.AIR)) { + ((Player) e.getEntity()).getInventory().addItem(is); //and for added realism :) - e.getPlayer().getWorld().playSound(e.getItem().getLocation(), Sound.ITEM_PICKUP, 1, 2); + e.getEntity().getWorld().playSound(e.getItem().getLocation(), + Sound.ENTITY_ITEM_PICKUP, 1, 2); } } @@ -461,7 +581,7 @@ public void setCancelled(boolean cancelled) { @Override public MCPlayer getPlayer() { - return new BukkitMCPlayer(e.getPlayer()); + return new BukkitMCPlayer(e.getEntity()); } @Override @@ -469,123 +589,130 @@ public Object _GetObject() { return e; } } - + @abstraction(type = Implementation.Type.BUKKIT) - public static class BukkitMCEntityDamageEvent implements MCEntityDamageEvent { + public static class BukkitMCEntityDamageEvent implements MCEntityDamageEvent { - EntityDamageEvent event; + EntityDamageEvent event; - public BukkitMCEntityDamageEvent(EntityDamageEvent e) { - event = e; - } + public BukkitMCEntityDamageEvent(Event e) { + event = (EntityDamageEvent) e; + } @Override - public Object _GetObject() { - return event; - } + public Object _GetObject() { + return event; + } @Override - public MCDamageCause getCause() { - return BukkitMCDamageCause.getConvertor().getAbstractedEnum(event.getCause()); - } + public MCDamageCause getCause() { + return BukkitMCDamageCause.getConvertor().getAbstractedEnum(event.getCause()); + } @Override - public MCEntity getEntity() { - return BukkitConvertor.BukkitGetCorrectEntity(event.getEntity()); - } + public MCEntity getEntity() { + return BukkitConvertor.BukkitGetCorrectEntity(event.getEntity()); + } @Override - public double getDamage() { - return event.getDamage(); - } + public double getFinalDamage() { + return event.getFinalDamage(); + } + + @Override + public double getDamage() { + return event.getDamage(); + } @Override - public void setDamage(double damage) { - event.setDamage(damage); - } - } + public void setDamage(double damage) { + event.setDamage(damage); + } + } @abstraction(type = Implementation.Type.BUKKIT) - public static class BukkitMCEntityDamageByEntityEvent extends BukkitMCEntityDamageEvent implements MCEntityDamageByEntityEvent { + public static class BukkitMCEntityDamageByEntityEvent extends BukkitMCEntityDamageEvent implements MCEntityDamageByEntityEvent { - EntityDamageByEntityEvent event; + EntityDamageByEntityEvent event; - public BukkitMCEntityDamageByEntityEvent(EntityDamageByEntityEvent e) { - super(e); - event = e; - } + public BukkitMCEntityDamageByEntityEvent(Event e) { + super(e); + event = (EntityDamageByEntityEvent) e; + } @Override - public MCEntity getDamager() { - return BukkitConvertor.BukkitGetCorrectEntity(event.getDamager()); - } - } + public MCEntity getDamager() { + return BukkitConvertor.BukkitGetCorrectEntity(event.getDamager()); + } + } - @abstraction(type = Implementation.Type.BUKKIT) - public static class BukkitMCTargetEvent implements MCEntityTargetEvent { + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCTargetEvent implements MCEntityTargetEvent { - EntityTargetEvent pie; + EntityTargetEvent pie; - public BukkitMCTargetEvent(EntityTargetEvent e) { - pie = e; - } + public BukkitMCTargetEvent(Event e) { + pie = (EntityTargetEvent) e; + } - public static BukkitMCTargetEvent _instantiate(Entity entity, LivingEntity target, EntityTargetEvent.TargetReason reason) { - return new BukkitMCTargetEvent(new EntityTargetEvent(( (BukkitMCEntity) entity ).getHandle(), - (LivingEntity) ( (BukkitMCLivingEntity) target ).getLivingEntity(), reason)); - } + public static BukkitMCTargetEvent _instantiate(Entity entity, LivingEntity target, EntityTargetEvent.TargetReason reason) { + return new BukkitMCTargetEvent(new EntityTargetEvent(((BukkitMCEntity) entity).getHandle(), + (LivingEntity) ((BukkitMCLivingEntity) target).getLivingEntity(), reason)); + } @Override - public Object _GetObject() { - return pie; - } + public Object _GetObject() { + return pie; + } @Override - public MCEntity getTarget() { - return BukkitConvertor.BukkitGetCorrectEntity(pie.getTarget()); - } + public MCEntity getTarget() { + return BukkitConvertor.BukkitGetCorrectEntity(pie.getTarget()); + } @Override - public void setTarget(MCEntity target) { - if (target == null) { - pie.setTarget(null); - } else { - pie.setTarget(((BukkitMCEntity)target).getHandle()); - } - } + public void setTarget(MCEntity target) { + if(target == null) { + pie.setTarget(null); + } else { + pie.setTarget(((BukkitMCEntity) target).getHandle()); + } + } @Override - public MCEntity getEntity() { - return BukkitConvertor.BukkitGetCorrectEntity(pie.getEntity()); - } + public MCEntity getEntity() { + return BukkitConvertor.BukkitGetCorrectEntity(pie.getEntity()); + } @Override - public MCEntityType getEntityType() { - return BukkitConvertor.BukkitGetCorrectEntity(pie.getEntity()).getType(); - } + public MCEntityType getEntityType() { + return BukkitConvertor.BukkitGetCorrectEntity(pie.getEntity()).getType(); + } - public MCTargetReason getReason() { - return MCTargetReason.valueOf(pie.getReason().name()); - } - } + @Override + public MCTargetReason getReason() { + return MCTargetReason.valueOf(pie.getReason().name()); + } + } public static class BukkitMCEntityEnterPortalEvent implements MCEntityEnterPortalEvent { - + EntityPortalEnterEvent epe; - public BukkitMCEntityEnterPortalEvent(EntityPortalEnterEvent event) { - epe = event; + + public BukkitMCEntityEnterPortalEvent(Event event) { + epe = (EntityPortalEnterEvent) event; } - + @Override public Object _GetObject() { return epe; } - + @Override public MCEntity getEntity() { return new BukkitMCEntity(epe.getEntity()); } - + @Override public MCLocation getLocation() { return new BukkitMCLocation(epe.getLocation()); @@ -596,8 +723,8 @@ public static class BukkitMCEntityChangeBlockEvent implements MCEntityChangeBloc EntityChangeBlockEvent ecb; - public BukkitMCEntityChangeBlockEvent(EntityChangeBlockEvent event) { - ecb = event; + public BukkitMCEntityChangeBlockEvent(Event event) { + ecb = (EntityChangeBlockEvent) event; } @Override @@ -612,12 +739,12 @@ public MCBlock getBlock() { @Override public MCMaterial getTo() { - return new BukkitMCMaterial(ecb.getTo()); + return BukkitMCMaterial.valueOfConcrete(ecb.getTo()); } @Override - public byte getData() { - return ecb.getData(); + public MCBlockData getBlockData() { + return new BukkitMCBlockData(ecb.getBlockData()); } @Override @@ -636,13 +763,48 @@ public Object _GetObject() { } } + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCEntityInteractEvent implements MCEntityInteractEvent { + + EntityInteractEvent eie; + + public BukkitMCEntityInteractEvent(Event event) { + eie = (EntityInteractEvent) event; + } + + @Override + public Object _GetObject() { + return eie; + } + + @Override + public MCEntity getEntity() { + return BukkitConvertor.BukkitGetCorrectEntity(eie.getEntity()); + } + + @Override + public MCBlock getBlock() { + return new BukkitMCBlock(eie.getBlock()); + } + + @Override + public boolean isCancelled() { + return eie.isCancelled(); + } + + @Override + public void setCancelled(boolean cancelled) { + eie.setCancelled(cancelled); + } + } + @abstraction(type = Implementation.Type.BUKKIT) public static class BukkitMCHangingBreakEvent implements MCHangingBreakEvent { HangingBreakEvent hbe; - public BukkitMCHangingBreakEvent(HangingBreakEvent event) { - hbe = event; + public BukkitMCHangingBreakEvent(Event event) { + hbe = (HangingBreakEvent) event; } @Override @@ -662,11 +824,318 @@ public MCRemoveCause getCause() { @Override public MCEntity getRemover() { - if (hbe instanceof HangingBreakByEntityEvent) { + if(hbe instanceof HangingBreakByEntityEvent) { return BukkitConvertor.BukkitGetCorrectEntity(((HangingBreakByEntityEvent) hbe).getRemover()); } else { return null; } } } + + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCHangingPlaceEvent implements MCHangingPlaceEvent { + + HangingPlaceEvent hpe; + + public BukkitMCHangingPlaceEvent(Event event) { + hpe = (HangingPlaceEvent) event; + } + + @Override + public Object _GetObject() { + return hpe; + } + + @Override + public MCHanging getEntity() { + return new BukkitMCHanging(hpe.getEntity()); + } + + @Override + public MCPlayer getPlayer() { + Player hanger = hpe.getPlayer(); + if(hanger == null) { + return null; + } + return new BukkitMCPlayer(hanger); + } + + @Override + public MCBlock getBlock() { + return new BukkitMCBlock(hpe.getBlock()); + } + + @Override + public MCBlockFace getBlockFace() { + return BukkitMCBlockFace.getConvertor().getAbstractedEnum(hpe.getBlockFace()); + } + + @Override + public MCItemStack getItem() { + if(hpe.getItemStack() == null) { + return null; + } + return new BukkitMCItemStack(hpe.getItemStack()); + } + + @Override + public MCEquipmentSlot getHand() { + if(hpe.getHand() == null) { + return null; + } else if(hpe.getHand() == EquipmentSlot.HAND) { + return MCEquipmentSlot.WEAPON; + } + return MCEquipmentSlot.OFF_HAND; + } + } + + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCEntityToggleGlideEvent implements MCEntityToggleGlideEvent { + + EntityToggleGlideEvent e; + + public BukkitMCEntityToggleGlideEvent(Event e) { + this.e = (EntityToggleGlideEvent) e; + } + + @Override + public Object _GetObject() { + return e; + } + + @Override + public boolean isGliding() { + return e.isGliding(); + } + + @Override + public MCEntity getEntity() { + return BukkitConvertor.BukkitGetCorrectEntity(e.getEntity()); + } + + @Override + public MCEntityType getEntityType() { + return BukkitConvertor.BukkitGetCorrectEntity(e.getEntity()).getType(); + } + } + + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCEntityToggleSwimEvent implements MCEntityToggleSwimEvent { + + EntityToggleSwimEvent e; + + public BukkitMCEntityToggleSwimEvent(Event e) { + this.e = (EntityToggleSwimEvent) e; + } + + @Override + public Object _GetObject() { + return e; + } + + @Override + public boolean isSwimming() { + return e.isSwimming(); + } + + @Override + public MCEntity getEntity() { + return BukkitConvertor.BukkitGetCorrectEntity(e.getEntity()); + } + + @Override + public MCEntityType getEntityType() { + return BukkitConvertor.BukkitGetCorrectEntity(e.getEntity()).getType(); + } + } + + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCFireworkExplodeEvent implements MCFireworkExplodeEvent { + + FireworkExplodeEvent e; + + public BukkitMCFireworkExplodeEvent(Event e) { + this.e = (FireworkExplodeEvent) e; + } + + @Override + public Object _GetObject() { + return e; + } + + @Override + public MCFirework getEntity() { + return new BukkitMCFirework(e.getEntity()); + } + } + + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCEntityRegainHealthEvent implements MCEntityRegainHealthEvent { + + EntityRegainHealthEvent e; + + public BukkitMCEntityRegainHealthEvent(Event e) { + this.e = (EntityRegainHealthEvent) e; + } + + @Override + public Object _GetObject() { + return e; + } + + @Override + public double getAmount() { + return e.getAmount(); + } + + @Override + public void setAmount(double amount) { + e.setAmount(amount); + } + + @Override + public MCEntity getEntity() { + return BukkitConvertor.BukkitGetCorrectEntity(e.getEntity()); + } + + @Override + public MCRegainReason getRegainReason() { + return BukkitMCRegainReason.getConvertor().getAbstractedEnum(e.getRegainReason()); + } + } + + public static class BukkitMCEntityPortalEvent implements MCEntityPortalEvent { + + EntityPortalEvent epe; + + public BukkitMCEntityPortalEvent(Event event) { + epe = (EntityPortalEvent) event; + } + + @Override + public Object _GetObject() { + return epe; + } + + @Override + public MCEntity getEntity() { + return new BukkitMCEntity(epe.getEntity()); + } + + @Override + public void setTo(MCLocation newloc) { + World w = (World) newloc.getWorld().getHandle(); + Location loc = new Location(w, newloc.getX(), newloc.getY(), newloc.getZ()); + epe.setTo(loc); + } + + @Override + public MCLocation getFrom() { + return new BukkitMCLocation(epe.getFrom()); + } + + @Override + public MCLocation getTo() { + if(epe.getTo() == null) { + return null; + } + return new BukkitMCLocation(epe.getTo()); + } + + @Override + public void setCancelled(boolean state) { + epe.setCancelled(state); + } + + @Override + public boolean isCancelled() { + return epe.isCancelled(); + } + + @Override + public int getSearchRadius() { + return epe.getSearchRadius(); + } + + @Override + public void setSearchRadius(int radius) { + epe.setSearchRadius(radius); + } + } + + public static class BukkitMCEntityUnleashEvent implements MCEntityUnleashEvent { + + EntityUnleashEvent ide; + + public BukkitMCEntityUnleashEvent(Event event) { + ide = (EntityUnleashEvent) event; + } + + @Override + public Object _GetObject() { + return ide; + } + + @Override + public MCEntity getEntity() { + return new BukkitMCEntity(ide.getEntity()); + } + + @Override + public MCUnleashReason getReason() { + return BukkitMCUnleashReason.getConvertor().getAbstractedEnum(ide.getReason()); + } + } + + public static class BukkitEntityPotionEffectEvent implements MCEntityPotionEffectEvent { + + EntityPotionEffectEvent e; + + public BukkitEntityPotionEffectEvent(Event e) { + this.e = (EntityPotionEffectEvent) e; + } + + @Override + public MCLivingEntity getEntity() { + return new BukkitMCLivingEntity(e.getEntity()); + } + + @Override + public MCPotionAction getAction() { + return BukkitMCPotionAction.getConvertor().getAbstractedEnum(e.getAction()); + } + + @Override + public MCPotionCause getCause() { + return BukkitMCPotionCause.getConvertor().getAbstractedEnum(e.getCause()); + } + + @Override + public Optional getNewEffect() { + PotionEffect pe = e.getNewEffect(); + return getEffect(pe); + } + + @Override + public Optional getOldEffect() { + PotionEffect pe = e.getOldEffect(); + return getEffect(pe); + } + + private Optional getEffect(PotionEffect pe) { + return pe == null + ? Optional.empty() + : Optional.of( + new MCLivingEntity.MCEffect( + BukkitMCPotionEffectType.valueOfConcrete( + pe.getType() + ), pe.getAmplifier(), pe.getDuration(), pe.isAmbient(), pe.hasParticles(), pe.hasIcon() + )); + } + + @Override + public Object _GetObject() { + return e; + } + } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitInventoryEvents.java b/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitInventoryEvents.java index 9dc3361a9d..f9f342f194 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitInventoryEvents.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitInventoryEvents.java @@ -1,29 +1,37 @@ package com.laytonsmith.abstraction.bukkit.events; +import com.laytonsmith.abstraction.MCAnvilInventory; import com.laytonsmith.abstraction.MCCraftingInventory; -import com.laytonsmith.abstraction.MCEnchantment; +import com.laytonsmith.abstraction.MCEnchantmentOffer; +import com.laytonsmith.abstraction.MCGrindstoneInventory; import com.laytonsmith.abstraction.MCHumanEntity; import com.laytonsmith.abstraction.MCInventory; import com.laytonsmith.abstraction.MCInventoryView; import com.laytonsmith.abstraction.MCItemStack; import com.laytonsmith.abstraction.MCPlayer; import com.laytonsmith.abstraction.MCRecipe; +import com.laytonsmith.abstraction.MCSmithingInventory; import com.laytonsmith.abstraction.blocks.MCBlock; import com.laytonsmith.abstraction.bukkit.BukkitConvertor; +import com.laytonsmith.abstraction.bukkit.BukkitMCAnvilInventory; import com.laytonsmith.abstraction.bukkit.BukkitMCCraftingInventory; -import com.laytonsmith.abstraction.bukkit.BukkitMCEnchantment; -import com.laytonsmith.abstraction.bukkit.BukkitMCHumanEntity; +import com.laytonsmith.abstraction.bukkit.BukkitMCEnchantmentOffer; +import com.laytonsmith.abstraction.bukkit.BukkitMCGrindstoneInventory; import com.laytonsmith.abstraction.bukkit.BukkitMCInventory; import com.laytonsmith.abstraction.bukkit.BukkitMCInventoryView; import com.laytonsmith.abstraction.bukkit.BukkitMCItemStack; -import com.laytonsmith.abstraction.bukkit.BukkitMCPlayer; +import com.laytonsmith.abstraction.bukkit.BukkitMCSmithingInventory; import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCBlock; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCHumanEntity; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCPlayer; import com.laytonsmith.abstraction.enums.MCClickType; import com.laytonsmith.abstraction.enums.MCDragType; +import com.laytonsmith.abstraction.enums.MCEnchantment; import com.laytonsmith.abstraction.enums.MCInventoryAction; import com.laytonsmith.abstraction.enums.MCResult; import com.laytonsmith.abstraction.enums.MCSlotType; import com.laytonsmith.abstraction.enums.bukkit.BukkitMCClickType; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCEnchantment; import com.laytonsmith.abstraction.enums.bukkit.BukkitMCInventoryAction; import com.laytonsmith.abstraction.enums.bukkit.BukkitMCResult; import com.laytonsmith.abstraction.events.MCEnchantItemEvent; @@ -34,16 +42,16 @@ import com.laytonsmith.abstraction.events.MCInventoryInteractEvent; import com.laytonsmith.abstraction.events.MCInventoryOpenEvent; import com.laytonsmith.abstraction.events.MCItemHeldEvent; +import com.laytonsmith.abstraction.events.MCItemSwapEvent; +import com.laytonsmith.abstraction.events.MCPrepareAnvilEvent; +import com.laytonsmith.abstraction.events.MCPrepareGrindstoneEvent; import com.laytonsmith.abstraction.events.MCPrepareItemCraftEvent; import com.laytonsmith.abstraction.events.MCPrepareItemEnchantEvent; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import com.laytonsmith.abstraction.events.MCPrepareSmithingEvent; import org.bukkit.enchantments.Enchantment; +import org.bukkit.enchantments.EnchantmentOffer; import org.bukkit.entity.HumanEntity; +import org.bukkit.event.Event; import org.bukkit.event.Event.Result; import org.bukkit.event.enchantment.EnchantItemEvent; import org.bukkit.event.enchantment.PrepareItemEnchantEvent; @@ -53,16 +61,24 @@ import org.bukkit.event.inventory.InventoryEvent; import org.bukkit.event.inventory.InventoryInteractEvent; import org.bukkit.event.inventory.InventoryOpenEvent; +import org.bukkit.event.inventory.PrepareAnvilEvent; +import org.bukkit.event.inventory.PrepareGrindstoneEvent; import org.bukkit.event.inventory.PrepareItemCraftEvent; +import org.bukkit.event.inventory.PrepareSmithingEvent; import org.bukkit.event.player.PlayerItemHeldEvent; +import org.bukkit.event.player.PlayerSwapHandItemsEvent; import org.bukkit.inventory.ItemStack; -/** - * - * @author jb_aero - */ +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + public class BukkitInventoryEvents { + public static class BukkitMCInventoryEvent implements MCInventoryEvent { + InventoryEvent event; public BukkitMCInventoryEvent(InventoryEvent e) { @@ -71,12 +87,10 @@ public BukkitMCInventoryEvent(InventoryEvent e) { @Override public List getViewers() { - List viewers = new ArrayList(); - - for (HumanEntity viewer : event.getViewers()) { + List viewers = new ArrayList<>(); + for(HumanEntity viewer : event.getViewers()) { viewers.add(new BukkitMCHumanEntity(viewer)); } - return viewers; } @@ -96,8 +110,8 @@ public Object _GetObject() { } } - public static class BukkitMCInventoryInteractEvent extends BukkitMCInventoryEvent - implements MCInventoryInteractEvent { + public static class BukkitMCInventoryInteractEvent extends BukkitMCInventoryEvent implements MCInventoryInteractEvent { + InventoryInteractEvent iie; public BukkitMCInventoryInteractEvent(InventoryInteractEvent e) { @@ -121,18 +135,18 @@ public MCResult getResult() { } @Override - public boolean isCanceled() { + public boolean isCancelled() { return iie.isCancelled(); } @Override - public void setCancelled(boolean cancelled) { - iie.setCancelled(cancelled); - } + public void setCancelled(boolean cancelled) { + iie.setCancelled(cancelled); + } } - public static class BukkitMCInventoryOpenEvent extends BukkitMCInventoryEvent - implements MCInventoryOpenEvent { + public static class BukkitMCInventoryOpenEvent extends BukkitMCInventoryEvent implements MCInventoryOpenEvent { + InventoryOpenEvent ioe; public BukkitMCInventoryOpenEvent(InventoryOpenEvent e) { @@ -146,8 +160,8 @@ public MCHumanEntity getPlayer() { } } - public static class BukkitMCInventoryCloseEvent extends BukkitMCInventoryEvent - implements MCInventoryCloseEvent { + public static class BukkitMCInventoryCloseEvent extends BukkitMCInventoryEvent implements MCInventoryCloseEvent { + InventoryCloseEvent ice; public BukkitMCInventoryCloseEvent(InventoryCloseEvent e) { @@ -161,10 +175,10 @@ public MCHumanEntity getPlayer() { } } - public static class BukkitMCInventoryClickEvent extends BukkitMCInventoryInteractEvent - implements MCInventoryClickEvent { + public static class BukkitMCInventoryClickEvent extends BukkitMCInventoryInteractEvent implements MCInventoryClickEvent { InventoryClickEvent ic; + public BukkitMCInventoryClickEvent(InventoryClickEvent e) { super(e); this.ic = e; @@ -190,6 +204,11 @@ public int getRawSlot() { return ic.getRawSlot(); } + @Override + public int getHotbarButton() { + return ic.getHotbarButton(); + } + @Override public MCSlotType getSlotType() { return MCSlotType.valueOf(ic.getSlotType().name()); @@ -214,7 +233,7 @@ public boolean isShiftClick() { public boolean isCreativeClick() { return ic.getClick().isCreativeAction(); } - + @Override public boolean isKeyboardClick() { return ic.getClick().isKeyboardClick(); @@ -222,7 +241,7 @@ public boolean isKeyboardClick() { @Override public void setCurrentItem(MCItemStack slot) { - if (slot != null) { + if(slot != null) { ic.setCurrentItem(((BukkitMCItemStack) slot).asItemStack()); } else { ic.setCurrentItem(null); @@ -231,6 +250,7 @@ public void setCurrentItem(MCItemStack slot) { @Override public void setCursor(MCItemStack cursor) { + // deprecated in 1.5 because it can create client/server desync ic.setCursor(((BukkitMCItemStack) cursor).asItemStack()); } @@ -245,10 +265,10 @@ public MCClickType getClickType() { } } - public static class BukkitMCInventoryDragEvent extends BukkitMCInventoryInteractEvent - implements MCInventoryDragEvent { + public static class BukkitMCInventoryDragEvent extends BukkitMCInventoryInteractEvent implements MCInventoryDragEvent { InventoryDragEvent id; + public BukkitMCInventoryDragEvent(InventoryDragEvent e) { super(e); this.id = e; @@ -256,37 +276,24 @@ public BukkitMCInventoryDragEvent(InventoryDragEvent e) { @Override public Map getNewItems() { - Map ret = new HashMap(); + Map ret = new HashMap<>(); - for (Map.Entry ni : id.getNewItems().entrySet()) { + for(Map.Entry ni : id.getNewItems().entrySet()) { Integer key = ni.getKey(); ItemStack value = ni.getValue(); ret.put(key, new BukkitMCItemStack(value)); } - return ret; } @Override public Set getRawSlots() { - Set ret = new HashSet(); - - for (Integer rs : id.getRawSlots()) { - ret.add(rs); - } - - return ret; + return id.getRawSlots(); } @Override public Set getInventorySlots() { - Set ret = new HashSet(); - - for (Integer is : id.getInventorySlots()) { - ret.add(is); - } - - return ret; + return id.getInventorySlots(); } @Override @@ -309,152 +316,161 @@ public MCDragType getType() { return MCDragType.valueOf(id.getType().name()); } } - + public static class BukkitMCEnchantItemEvent extends BukkitMCInventoryEvent implements MCEnchantItemEvent { + EnchantItemEvent ei; public BukkitMCEnchantItemEvent(EnchantItemEvent e) { super(e); this.ei = e; } - + @Override public MCBlock getEnchantBlock() { return new BukkitMCBlock(ei.getEnchantBlock()); } - + @Override public MCPlayer GetEnchanter() { return new BukkitMCPlayer(ei.getEnchanter()); } - + @Override public Map getEnchantsToAdd() { - Map ret = new HashMap(); - - for (Map.Entry ea : ei.getEnchantsToAdd().entrySet()) { + Map ret = new HashMap<>(); + for(Map.Entry ea : ei.getEnchantsToAdd().entrySet()) { Enchantment key = ea.getKey(); Integer value = ea.getValue(); - ret.put(new BukkitMCEnchantment(key), value); + ret.put(BukkitMCEnchantment.valueOfConcrete(key), value); } - return ret; } - + @Override public void setEnchantsToAdd(Map enchants) { Map ret = ei.getEnchantsToAdd(); ret.clear(); - -// for (Map.Entry ea : enchants.entrySet()) { + +// for(Map.Entry ea : enchants.entrySet()) { // MCEnchantment key = ea.getKey(); // Integer value = ea.getValue(); // ret.put(((BukkitMCEnchantment) key).asEnchantment(), value); // } - - Map enchantments = new HashMap(); - - for (Map.Entry ea : enchants.entrySet()) { + Map enchantments = new HashMap<>(); + + for(Map.Entry ea : enchants.entrySet()) { MCEnchantment key = ea.getKey(); Integer value = ea.getValue(); - enchantments.put(((BukkitMCEnchantment) key).asEnchantment(), value); + enchantments.put((Enchantment) key.getConcrete(), value); } - + ItemStack item = ei.getItem(); item.addUnsafeEnchantments(enchantments); } - - - + @Override public MCItemStack getItem() { return new BukkitMCItemStack(ei.getItem()); } - + @Override public void setItem(MCItemStack i) { ItemStack item = ei.getItem(); ItemStack is = ((BukkitMCItemStack) i).asItemStack(); - + item.setAmount(is.getAmount()); - item.setData(is.getData()); - item.setDurability(is.getDurability()); - item.setItemMeta(is.getItemMeta()); item.setType(is.getType()); + item.setItemMeta(is.getItemMeta()); } - + @Override public void setExpLevelCost(int level) { ei.setExpLevelCost(level); } - + @Override public int getExpLevelCost() { return ei.getExpLevelCost(); } - + @Override public int whichButton() { return ei.whichButton(); } + + @Override + public int getLevelHint() { + return ei.getLevelHint(); + } + + @Override + public MCEnchantment getEnchantmentHint() { + return BukkitMCEnchantment.valueOfConcrete(ei.getEnchantmentHint()); + } } - + public static class BukkitMCPrepareItemEnchantEvent extends BukkitMCInventoryEvent implements MCPrepareItemEnchantEvent { + PrepareItemEnchantEvent pie; public BukkitMCPrepareItemEnchantEvent(PrepareItemEnchantEvent e) { super(e); this.pie = e; } - + @Override public MCBlock getEnchantBlock() { return new BukkitMCBlock(pie.getEnchantBlock()); } - + @Override public MCPlayer getEnchanter() { return new BukkitMCPlayer(pie.getEnchanter()); } - + @Override public int getEnchantmentBonus() { return pie.getEnchantmentBonus(); } - + @Override - public int[] getExpLevelCostsOffered() { - return pie.getExpLevelCostsOffered(); + public MCEnchantmentOffer[] getOffers() { + EnchantmentOffer[] offers = pie.getOffers(); + MCEnchantmentOffer[] ret = new MCEnchantmentOffer[offers.length]; + for(int i = 0; i < offers.length; i++) { + ret[i] = new BukkitMCEnchantmentOffer(offers[i]); + } + return ret; } - + @Override public MCItemStack getItem() { return new BukkitMCItemStack(pie.getItem()); } - + @Override public void setItem(MCItemStack i) { ItemStack item = pie.getItem(); ItemStack is = ((BukkitMCItemStack) i).asItemStack(); - + item.setAmount(is.getAmount()); - item.setData(is.getData()); - item.setDurability(is.getDurability()); - item.setItemMeta(is.getItemMeta()); item.setType(is.getType()); + item.setItemMeta(is.getItemMeta()); } } - + public static class BukkitMCItemHeldEvent implements MCItemHeldEvent { PlayerItemHeldEvent ih; + public BukkitMCItemHeldEvent(PlayerItemHeldEvent event) { ih = event; } - + @Override public MCPlayer getPlayer() { - return new com.laytonsmith.abstraction.bukkit.BukkitMCPlayer(ih.getPlayer()); + return new BukkitMCPlayer(ih.getPlayer()); } @Override @@ -472,15 +488,64 @@ public int getPreviousSlot() { return ih.getPreviousSlot(); } } - + + public static class BukkitMCItemSwapEvent implements MCItemSwapEvent { + + PlayerSwapHandItemsEvent is; + + public BukkitMCItemSwapEvent(PlayerSwapHandItemsEvent event) { + is = event; + } + + public BukkitMCItemSwapEvent(Event event) { + is = (PlayerSwapHandItemsEvent) event; + } + + @Override + public MCPlayer getPlayer() { + return new BukkitMCPlayer(is.getPlayer()); + } + + @Override + public Object _GetObject() { + return is; + } + + @Override + public MCItemStack getMainHandItem() { + return new BukkitMCItemStack(is.getMainHandItem()); + } + + @Override + public MCItemStack getOffHandItem() { + return new BukkitMCItemStack(is.getOffHandItem()); + } + + @Override + public void setMainHandItem(MCItemStack item) { + is.setMainHandItem((ItemStack) item.getHandle()); + } + + @Override + public void setOffHandItem(MCItemStack item) { + is.setOffHandItem((ItemStack) item.getHandle()); + } + } + public static class BukkitMCPrepareItemCraftEvent extends BukkitMCInventoryEvent implements MCPrepareItemCraftEvent { PrepareItemCraftEvent e; + public BukkitMCPrepareItemCraftEvent(PrepareItemCraftEvent event) { super(event); e = event; } + @Override + public MCPlayer getPlayer() { + return new BukkitMCPlayer(e.getViewers().get(0)); + } + @Override public MCRecipe getRecipe() { return BukkitConvertor.BukkitGetRecipe(e.getRecipe()); @@ -490,10 +555,82 @@ public MCRecipe getRecipe() { public boolean isRepair() { return e.isRepair(); } - + @Override public MCCraftingInventory getInventory() { return new BukkitMCCraftingInventory(e.getInventory()); } } + + public static class BukkitMCPrepareAnvilEvent extends BukkitMCInventoryEvent implements MCPrepareAnvilEvent { + PrepareAnvilEvent e; + + public BukkitMCPrepareAnvilEvent(PrepareAnvilEvent event) { + super(event); + e = event; + } + + @Override + public void setResult(MCItemStack i) { + e.setResult(((BukkitMCItemStack) i).asItemStack()); + } + + @Override + public MCPlayer getPlayer() { + return new BukkitMCPlayer(e.getViewers().get(0)); + } + + @Override + public MCAnvilInventory getInventory() { + return new BukkitMCAnvilInventory(e.getInventory()); + } + } + + public static class BukkitMCPrepareSmithingEvent extends BukkitMCInventoryEvent implements MCPrepareSmithingEvent { + PrepareSmithingEvent e; + + public BukkitMCPrepareSmithingEvent(PrepareSmithingEvent event) { + super(event); + e = event; + } + + @Override + public MCPlayer getPlayer() { + return new BukkitMCPlayer(e.getViewers().get(0)); + } + + @Override + public void setResult(MCItemStack stack) { + e.setResult(((BukkitMCItemStack) stack).asItemStack()); + } + + @Override + public MCSmithingInventory getInventory() { + return new BukkitMCSmithingInventory(e.getInventory()); + } + } + + public static class BukkitMCPrepareGrindstoneEvent extends BukkitMCInventoryEvent implements MCPrepareGrindstoneEvent { + PrepareGrindstoneEvent e; + + public BukkitMCPrepareGrindstoneEvent(PrepareGrindstoneEvent event) { + super(event); + e = event; + } + + @Override + public MCPlayer getPlayer() { + return new BukkitMCPlayer(e.getViewers().get(0)); + } + + @Override + public void setResult(MCItemStack stack) { + e.setResult(((BukkitMCItemStack) stack).asItemStack()); + } + + @Override + public MCGrindstoneInventory getInventory() { + return new BukkitMCGrindstoneInventory(e.getInventory()); + } + } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitMiscEvents.java b/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitMiscEvents.java index 88426a9be5..de12188c15 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitMiscEvents.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitMiscEvents.java @@ -4,56 +4,20 @@ import com.laytonsmith.abstraction.MCCommandSender; import com.laytonsmith.abstraction.MCPlayer; import com.laytonsmith.abstraction.bukkit.BukkitMCCommand; -import com.laytonsmith.abstraction.bukkit.BukkitMCPlayer; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCPlayer; import com.laytonsmith.abstraction.events.MCCommandTabCompleteEvent; -import com.laytonsmith.abstraction.events.MCConsoleCommandEvent; import com.laytonsmith.abstraction.events.MCPluginIncomingMessageEvent; -import com.laytonsmith.abstraction.events.MCServerPingEvent; -import java.net.InetAddress; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; import org.bukkit.command.Command; import org.bukkit.entity.Player; -import org.bukkit.event.server.ServerCommandEvent; -import org.bukkit.event.server.ServerListPingEvent; +import java.util.List; -/** - * - * - */ public class BukkitMiscEvents { - public static class BukkitMCConsoleCommandEvent implements MCConsoleCommandEvent { - ServerCommandEvent sce; - - public BukkitMCConsoleCommandEvent(ServerCommandEvent sce){ - this.sce = sce; - } - - @Override - public Object _GetObject() { - return sce; - } - @Override - public String getCommand() { - return sce.getCommand(); - } - - @Override - public void setCommand(String command) { - sce.setCommand(command); - } - } - /* * Not an actual event, but making it one. */ public static class BukkitMCPluginIncomingMessageEvent implements MCPluginIncomingMessageEvent { - + Player player; String channel; byte[] bytes; @@ -63,8 +27,7 @@ public BukkitMCPluginIncomingMessageEvent(Player player, String channel, byte[] this.channel = channel; this.bytes = bytes; } - - + @Override public String getChannel() { return channel; @@ -86,74 +49,6 @@ public Object _GetObject() { } } - public static class BukkitMCServerPingEvent implements MCServerPingEvent { - - private final ServerListPingEvent slp; - - public BukkitMCServerPingEvent(ServerListPingEvent event) { - slp = event; - } - - @Override - public Object _GetObject() { - return slp; - } - - @Override - public InetAddress getAddress() { - return slp.getAddress(); - } - - @Override - public int getMaxPlayers() { - return slp.getMaxPlayers(); - } - - @Override - public String getMOTD() { - return slp.getMotd(); - } - - @Override - public int getNumPlayers() { - return slp.getNumPlayers(); - } - - @Override - public void setMaxPlayers(int max) { - slp.setMaxPlayers(max); - } - - @Override - public void setMOTD(String motd) { - slp.setMotd(motd); - } - - @Override - public Set getPlayers() { - Set players = new HashSet<>(); - Iterator iterator = slp.iterator(); - while (iterator.hasNext()) { - players.add(new BukkitMCPlayer(iterator.next())); - } - return players; - } - - @Override - public void setPlayers(Collection players) { - Set ps = new HashSet<>(); - for (MCPlayer player : players) { - ps.add((Player) player.getHandle()); - } - Iterator iterator = slp.iterator(); - while (iterator.hasNext()) { - if (!ps.contains(iterator.next())) { - iterator.remove(); - } - } - } - } - public static class BukkitMCCommandTabCompleteEvent implements MCCommandTabCompleteEvent { List comp; @@ -161,14 +56,15 @@ public static class BukkitMCCommandTabCompleteEvent implements MCCommandTabCompl Command cmd; String alias; String[] args; + public BukkitMCCommandTabCompleteEvent(MCCommandSender sender, Command cmd, String alias, String[] args) { - this.comp = new ArrayList(); + this.comp = null; this.sender = sender; this.cmd = cmd; this.alias = alias; this.args = args; } - + @Override public Object _GetObject() { return comp; @@ -198,5 +94,10 @@ public String[] getArguments() { public List getCompletions() { return comp; } + + @Override + public void setCompletions(List completions) { + this.comp = completions; + } } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitPlayerEvents.java b/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitPlayerEvents.java index 8499cdbd1c..5c6add1489 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitPlayerEvents.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitPlayerEvents.java @@ -1,41 +1,56 @@ - - package com.laytonsmith.abstraction.bukkit.events; +import com.laytonsmith.PureUtilities.Common.ReflectionUtils; +import com.laytonsmith.PureUtilities.Vector3D; import com.laytonsmith.abstraction.Implementation; import com.laytonsmith.abstraction.MCBookMeta; import com.laytonsmith.abstraction.MCEntity; +import com.laytonsmith.abstraction.MCHumanEntity; import com.laytonsmith.abstraction.MCItemStack; import com.laytonsmith.abstraction.MCLocation; +import com.laytonsmith.abstraction.MCNamespacedKey; import com.laytonsmith.abstraction.MCPlayer; -import com.laytonsmith.abstraction.MCTravelAgent; import com.laytonsmith.abstraction.MCWorld; -import com.laytonsmith.abstraction.StaticLayer; import com.laytonsmith.abstraction.blocks.MCBlock; import com.laytonsmith.abstraction.blocks.MCBlockFace; +import com.laytonsmith.abstraction.blocks.MCMaterial; +import com.laytonsmith.abstraction.bukkit.BukkitConvertor; import com.laytonsmith.abstraction.bukkit.BukkitMCBookMeta; -import com.laytonsmith.abstraction.bukkit.BukkitMCEntity; import com.laytonsmith.abstraction.bukkit.BukkitMCItemStack; import com.laytonsmith.abstraction.bukkit.BukkitMCLocation; -import com.laytonsmith.abstraction.bukkit.BukkitMCPlayer; -import com.laytonsmith.abstraction.bukkit.BukkitMCTravelAgent; +import com.laytonsmith.abstraction.bukkit.BukkitMCNamespacedKey; +import com.laytonsmith.abstraction.bukkit.BukkitMCServer; import com.laytonsmith.abstraction.bukkit.BukkitMCWorld; import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCBlock; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCMaterial; import com.laytonsmith.abstraction.bukkit.entities.BukkitMCFishHook; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCHumanEntity; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCPlayer; import com.laytonsmith.abstraction.bukkit.events.BukkitEntityEvents.BukkitMCEntityDeathEvent; import com.laytonsmith.abstraction.entities.MCFishHook; import com.laytonsmith.abstraction.enums.MCAction; +import com.laytonsmith.abstraction.enums.MCEnterBedResult; +import com.laytonsmith.abstraction.enums.MCEquipmentSlot; import com.laytonsmith.abstraction.enums.MCFishingState; import com.laytonsmith.abstraction.enums.MCGameMode; +import com.laytonsmith.abstraction.enums.MCResourcePackStatus; import com.laytonsmith.abstraction.enums.MCTeleportCause; +import com.laytonsmith.abstraction.enums.MCVersion; import com.laytonsmith.abstraction.enums.bukkit.BukkitMCAction; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCEnterBedResult; import com.laytonsmith.abstraction.enums.bukkit.BukkitMCFishingState; import com.laytonsmith.abstraction.enums.bukkit.BukkitMCGameMode; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCResourcePackStatus; import com.laytonsmith.abstraction.enums.bukkit.BukkitMCTeleportCause; -import com.laytonsmith.abstraction.events.MCChatTabCompleteEvent; import com.laytonsmith.abstraction.events.MCExpChangeEvent; +import com.laytonsmith.abstraction.events.MCFoodLevelChangeEvent; import com.laytonsmith.abstraction.events.MCGamemodeChangeEvent; -import com.laytonsmith.abstraction.events.MCPlayerBedEvent; +import com.laytonsmith.abstraction.events.MCPlayerAdvancementDoneEvent; +import com.laytonsmith.abstraction.events.MCPlayerBucketEmptyEvent; +import com.laytonsmith.abstraction.events.MCPlayerBucketEvent; +import com.laytonsmith.abstraction.events.MCPlayerBucketFillEvent; +import com.laytonsmith.abstraction.events.MCPlayerEnterBedEvent; +import com.laytonsmith.abstraction.events.MCPlayerLeaveBedEvent; import com.laytonsmith.abstraction.events.MCPlayerChatEvent; import com.laytonsmith.abstraction.events.MCPlayerCommandEvent; import com.laytonsmith.abstraction.events.MCPlayerDeathEvent; @@ -49,32 +64,38 @@ import com.laytonsmith.abstraction.events.MCPlayerLoginEvent; import com.laytonsmith.abstraction.events.MCPlayerMoveEvent; import com.laytonsmith.abstraction.events.MCPlayerPortalEvent; -import com.laytonsmith.abstraction.events.MCPlayerPreLoginEvent; import com.laytonsmith.abstraction.events.MCPlayerQuitEvent; +import com.laytonsmith.abstraction.events.MCPlayerResourcePackEvent; import com.laytonsmith.abstraction.events.MCPlayerRespawnEvent; +import com.laytonsmith.abstraction.events.MCPlayerStopUsingItemEvent; import com.laytonsmith.abstraction.events.MCPlayerTeleportEvent; import com.laytonsmith.abstraction.events.MCPlayerToggleFlightEvent; import com.laytonsmith.abstraction.events.MCPlayerToggleSneakEvent; import com.laytonsmith.abstraction.events.MCPlayerToggleSprintEvent; import com.laytonsmith.abstraction.events.MCWorldChangedEvent; import com.laytonsmith.annotations.abstraction; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; +import com.laytonsmith.core.Static; +import io.papermc.paper.advancement.AdvancementDisplay; +import io.papermc.paper.event.player.PlayerStopUsingItemEvent; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import org.bukkit.Bukkit; import org.bukkit.Location; -import org.bukkit.TravelAgent; import org.bukkit.World; import org.bukkit.block.BlockFace; +import org.bukkit.damage.DamageSource; +import org.bukkit.damage.DamageType; import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.entity.FoodLevelChangeEvent; import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.event.player.PlayerAdvancementDoneEvent; import org.bukkit.event.player.PlayerBedEnterEvent; import org.bukkit.event.player.PlayerBedLeaveEvent; +import org.bukkit.event.player.PlayerBucketEmptyEvent; +import org.bukkit.event.player.PlayerBucketEvent; +import org.bukkit.event.player.PlayerBucketFillEvent; import org.bukkit.event.player.PlayerChangedWorldEvent; -import org.bukkit.event.player.PlayerChatTabCompleteEvent; import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.event.player.PlayerEditBookEvent; import org.bukkit.event.player.PlayerEvent; @@ -86,24 +107,81 @@ import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerKickEvent; import org.bukkit.event.player.PlayerLoginEvent; +import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerPortalEvent; -import org.bukkit.event.player.PlayerPreLoginEvent; -import org.bukkit.event.player.PlayerPreLoginEvent.Result; import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerResourcePackStatusEvent; import org.bukkit.event.player.PlayerRespawnEvent; import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.event.player.PlayerToggleFlightEvent; import org.bukkit.event.player.PlayerToggleSneakEvent; import org.bukkit.event.player.PlayerToggleSprintEvent; +import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Vector; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.UUID; -/** - * - * - */ public class BukkitPlayerEvents { - @abstraction(type=Implementation.Type.BUKKIT) - public static abstract class BukkitMCPlayerEvent implements MCPlayerEvent { + + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCFoodLevelChangeEvent implements MCFoodLevelChangeEvent { + + FoodLevelChangeEvent event; + + public BukkitMCFoodLevelChangeEvent(FoodLevelChangeEvent event) { + this.event = event; + } + + @Override + public MCHumanEntity getEntity() { + return new BukkitMCHumanEntity(event.getEntity()); + } + + @Override + public int getDifference() { + return event.getEntity().getFoodLevel() - getFoodLevel(); + } + + @Override + public int getFoodLevel() { + return event.getFoodLevel(); + } + + @Override + public void setFoodLevel(int level) { + event.setFoodLevel(level); + } + + @Override + public MCItemStack getItem() { + if(event.getItem() == null) { + return null; + } + return new BukkitMCItemStack(event.getItem()); + } + + @Override + public boolean isCancelled() { + return event.isCancelled(); + } + + @Override + public void setCancelled(boolean cancel) { + event.setCancelled(cancel); + } + + @Override + public Object _GetObject() { + return event; + } + } + + @abstraction(type = Implementation.Type.BUKKIT) + public abstract static class BukkitMCPlayerEvent implements MCPlayerEvent { PlayerEvent pe; @@ -122,8 +200,8 @@ public MCPlayer getPlayer() { } } - public static class BukkitMCPlayerItemConsumeEvent extends BukkitMCPlayerEvent - implements MCPlayerItemConsumeEvent { + public static class BukkitMCPlayerItemConsumeEvent extends BukkitMCPlayerEvent implements MCPlayerItemConsumeEvent { + PlayerItemConsumeEvent pic; public BukkitMCPlayerItemConsumeEvent(PlayerItemConsumeEvent event) { @@ -141,80 +219,98 @@ public void setItem(MCItemStack item) { pic.setItem(((BukkitMCItemStack) item).asItemStack()); } - public static BukkitMCPlayerItemConsumeEvent _instantiate( - MCPlayer player, MCItemStack item) { + @Override + public MCEquipmentSlot getHand() { + if(pic.getHand() == EquipmentSlot.HAND) { + return MCEquipmentSlot.WEAPON; + } + return MCEquipmentSlot.OFF_HAND; + } + + public static BukkitMCPlayerItemConsumeEvent _instantiate(MCPlayer player, MCItemStack item) { return new BukkitMCPlayerItemConsumeEvent( - new PlayerItemConsumeEvent(((BukkitMCPlayer) player)._Player(), - ((BukkitMCItemStack) item).asItemStack())); + new PlayerItemConsumeEvent(((BukkitMCPlayer) player)._Player(), ((BukkitMCItemStack) item).asItemStack())); } } - public static class BukkitMCPlayerBedEvent extends BukkitMCPlayerEvent - implements MCPlayerBedEvent { - MCBlock block; - PlayerEvent event; + public static class BukkitMCPlayerEnterBedEvent extends BukkitMCPlayerEvent implements MCPlayerEnterBedEvent { - public BukkitMCPlayerBedEvent(PlayerBedEnterEvent event) { + PlayerBedEnterEvent event; + + public BukkitMCPlayerEnterBedEvent(PlayerBedEnterEvent event) { super(event); this.event = event; - this.block = new BukkitMCBlock(event.getBed()); } - public BukkitMCPlayerBedEvent(PlayerBedLeaveEvent event) { + @Override + public MCBlock getBed() { + return new BukkitMCBlock(event.getBed()); + } + + @Override + public MCEnterBedResult getResult() { + return BukkitMCEnterBedResult.getConvertor().getAbstractedEnum(event.getBedEnterResult()); + } + } + + public static class BukkitMCPlayerLeaveBedEvent extends BukkitMCPlayerEvent implements MCPlayerLeaveBedEvent { + + PlayerBedLeaveEvent event; + + public BukkitMCPlayerLeaveBedEvent(PlayerBedLeaveEvent event) { super(event); this.event = event; - this.block = new BukkitMCBlock(event.getBed()); } @Override public MCBlock getBed() { - return block; + return new BukkitMCBlock(event.getBed()); } } - public static class BukkitMCPlayerKickEvent extends BukkitMCPlayerEvent - implements MCPlayerKickEvent { - PlayerKickEvent e; + public static class BukkitMCPlayerKickEvent extends BukkitMCPlayerEvent implements MCPlayerKickEvent { + + PlayerKickEvent e; - public BukkitMCPlayerKickEvent(PlayerKickEvent e){ + public BukkitMCPlayerKickEvent(PlayerKickEvent e) { super(e); - this.e = e; - } + this.e = e; + } @Override - public String getMessage() { - return e.getLeaveMessage(); - } + public String getMessage() { + return e.getLeaveMessage(); + } @Override - public void setMessage(String message) { - e.setLeaveMessage(message); - } + public void setMessage(String message) { + e.setLeaveMessage(message); + } @Override - public String getReason() { - return e.getReason(); - } + public String getReason() { + return e.getReason(); + } @Override - public void setReason(String message) { - e.setReason(message); - } + public void setReason(String message) { + e.setReason(message); + } @Override - public boolean isCancelled() { - return e.isCancelled(); - } + public boolean isCancelled() { + return e.isCancelled(); + } @Override - public void setCancelled(boolean cancelled) { - e.setCancelled(cancelled); - } - } + public void setCancelled(boolean cancelled) { + e.setCancelled(cancelled); + } + } + + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCPlayerTeleportEvent extends BukkitMCPlayerEvent implements MCPlayerTeleportEvent { - @abstraction(type=Implementation.Type.BUKKIT) - public static class BukkitMCPlayerTeleportEvent extends BukkitMCPlayerEvent - implements MCPlayerTeleportEvent { PlayerTeleportEvent e; public BukkitMCPlayerTeleportEvent(PlayerTeleportEvent e) { @@ -244,16 +340,15 @@ public void setFrom(MCLocation oldloc) { @Override public void setTo(MCLocation newloc) { - World w = ((BukkitMCWorld)newloc.getWorld()).__World(); + World w = ((BukkitMCWorld) newloc.getWorld()).__World(); Location loc = new Location( - w, - newloc.getX(), - newloc.getY(), - newloc.getZ(), - newloc.getPitch(), - newloc.getYaw() + w, + newloc.getX(), + newloc.getY(), + newloc.getZ(), + newloc.getPitch(), + newloc.getYaw() ); - e.setTo(loc); } @@ -268,100 +363,77 @@ public boolean isCancelled() { } } - @abstraction(type=Implementation.Type.BUKKIT) - public static class BukkitMCPlayerPortalEvent extends BukkitMCPlayerTeleportEvent - implements MCPlayerPortalEvent { + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCPlayerPortalEvent extends BukkitMCPlayerTeleportEvent implements MCPlayerPortalEvent { PlayerPortalEvent p; + public BukkitMCPlayerPortalEvent(PlayerPortalEvent event) { super(event); p = event; } - @Override - public void useTravelAgent(boolean useTravelAgent) { - p.useTravelAgent(useTravelAgent); - } - - @Override - public boolean useTravelAgent() { - return p.useTravelAgent(); - } - - @Override - public MCTravelAgent getPortalTravelAgent() { - return new BukkitMCTravelAgent(p.getPortalTravelAgent()); - } - - @Override - public void setPortalTravelAgent(MCTravelAgent travelAgent) { - p.setPortalTravelAgent((TravelAgent) travelAgent.getHandle()); - } - @Override public MCLocation getTo() { - if (e.getTo() == null) { + if(e.getTo() == null) { return null; } return new BukkitMCLocation(e.getTo()); } - } - - public static class BukkitMCPlayerLoginEvent extends BukkitMCPlayerEvent - implements MCPlayerLoginEvent { - PlayerLoginEvent event; - public BukkitMCPlayerLoginEvent(PlayerLoginEvent e) { - super(e); - event = e; - } + @Override + public void setTo(MCLocation newloc) { + super.setTo(newloc); + } @Override - public String getName() { - return event.getPlayer().getName(); + public int getSearchRadius() { + return p.getSearchRadius(); } @Override - public String getKickMessage() { - return event.getKickMessage(); + public void setSearchRadius(int radius) { + p.setSearchRadius(radius); } @Override - public void setKickMessage(String msg) { - event.setKickMessage(msg); + public int getCreationRadius() { + return p.getCreationRadius(); } @Override - public String getResult() { - return event.getResult().toString(); + public void setCreationRadius(int radius) { + p.setCreationRadius(radius); } @Override - public void setResult(String rst) { - event.setResult(PlayerLoginEvent.Result.valueOf(rst.toUpperCase())); + public boolean canCreatePortal() { + return p.getCanCreatePortal(); } @Override - public String getIP() { - return event.getAddress().getHostAddress(); + public void setCanCreatePortal(boolean canCreate) { + p.setCanCreatePortal(canCreate); } } - public static class BukkitMCPlayerPreLoginEvent implements MCPlayerPreLoginEvent { - PlayerPreLoginEvent event; + public static class BukkitMCPlayerLoginEvent extends BukkitMCPlayerEvent implements MCPlayerLoginEvent { - public BukkitMCPlayerPreLoginEvent(PlayerPreLoginEvent e) { - event = e; - } + PlayerLoginEvent event; - @Override - public Object _GetObject() { - return event; + public BukkitMCPlayerLoginEvent(PlayerLoginEvent e) { + super(e); + event = e; } @Override public String getName() { - return event.getName(); + return event.getPlayer().getName(); + } + + @Override + public String getUniqueId() { + return event.getPlayer().getUniqueId().toString(); } @Override @@ -381,64 +453,68 @@ public String getResult() { @Override public void setResult(String rst) { - event.setResult(Result.valueOf(rst.toUpperCase())); + event.setResult(PlayerLoginEvent.Result.valueOf(rst.toUpperCase())); } @Override public String getIP() { - return event.getAddress().toString(); + return event.getAddress().getHostAddress(); } + @Override + public String getHostname() { + return event.getHostname(); + } } - @abstraction(type=Implementation.Type.BUKKIT) - public static class BukkitMCPlayerChatEvent extends BukkitMCPlayerEvent - implements MCPlayerChatEvent{ - AsyncPlayerChatEvent pce; + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCPlayerChatEvent extends BukkitMCPlayerEvent implements MCPlayerChatEvent { - public BukkitMCPlayerChatEvent(AsyncPlayerChatEvent event) { + AsyncPlayerChatEvent pce; + + public BukkitMCPlayerChatEvent(AsyncPlayerChatEvent event) { super(event); - pce = event; - } + pce = event; + } - public BukkitMCPlayerChatEvent(BukkitMCPlayerChatEvent event) { + public BukkitMCPlayerChatEvent(BukkitMCPlayerChatEvent event) { super(event.pce); - pce = event.pce; - } + pce = event.pce; + } - public static BukkitMCPlayerChatEvent _instantiate(MCPlayer player, String message, String format){ - AsyncPlayerChatEvent apce = new AsyncPlayerChatEvent(false, ((BukkitMCPlayer)player)._Player(), message, - new HashSet<>(Bukkit.getServer().getOnlinePlayers())); + public static BukkitMCPlayerChatEvent _instantiate(MCPlayer player, String message, String format) { + AsyncPlayerChatEvent apce = new AsyncPlayerChatEvent(false, ((BukkitMCPlayer) player)._Player(), message, + new HashSet<>(Bukkit.getServer().getOnlinePlayers())); apce.setFormat(format); - return new BukkitMCPlayerChatEvent(apce); - } + return new BukkitMCPlayerChatEvent(apce); + } @Override - public String getMessage() { - return pce.getMessage(); - } + public String getMessage() { + return pce.getMessage(); + } @Override - public void setMessage(String message) { - pce.setMessage(message); - } + public void setMessage(String message) { + pce.setMessage(message); + } @Override - public List getRecipients() { - List players = new ArrayList<>(); - for(Player p : pce.getRecipients()){ - players.add(new BukkitMCPlayer(p)); - } - return players; - } + public List getRecipients() { + List players = new ArrayList<>(); + for(Player p : pce.getRecipients()) { + players.add(new BukkitMCPlayer(p)); + } + return players; + } @Override - public void setRecipients(List list) { - pce.getRecipients().clear(); - for(MCPlayer p : list){ - pce.getRecipients().add(((BukkitMCPlayer)p)._Player()); - } - } + public void setRecipients(List list) { + pce.getRecipients().clear(); + for(MCPlayer p : list) { + pce.getRecipients().add(((BukkitMCPlayer) p)._Player()); + } + } @Override public String getFormat() { @@ -449,164 +525,201 @@ public String getFormat() { public void setFormat(String format) { pce.setFormat(format); } - } + } - @abstraction(type=Implementation.Type.BUKKIT) - public static class BukkitMCPlayerQuitEvent extends BukkitMCPlayerEvent - implements MCPlayerQuitEvent{ - PlayerQuitEvent pce; + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCPlayerQuitEvent extends BukkitMCPlayerEvent implements MCPlayerQuitEvent { + + PlayerQuitEvent pce; - public BukkitMCPlayerQuitEvent(PlayerQuitEvent event) { + public BukkitMCPlayerQuitEvent(PlayerQuitEvent event) { super(event); - pce = event; - } + pce = event; + } - public static BukkitMCPlayerQuitEvent _instantiate(MCPlayer player, String message){ - return new BukkitMCPlayerQuitEvent(new PlayerQuitEvent(((BukkitMCPlayer)player)._Player(), message)); - } + public static BukkitMCPlayerQuitEvent _instantiate(MCPlayer player, String message) { + return new BukkitMCPlayerQuitEvent(new PlayerQuitEvent(((BukkitMCPlayer) player)._Player(), message)); + } @Override - public String getMessage() { - return pce.getQuitMessage(); - } + public String getMessage() { + return pce.getQuitMessage(); + } @Override - public void setMessage(String message) { - pce.setQuitMessage(message); - } - } + public void setMessage(String message) { + pce.setQuitMessage(message); + } + } + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCPlayerJoinEvent extends BukkitMCPlayerEvent implements MCPlayerJoinEvent { - @abstraction(type=Implementation.Type.BUKKIT) - public static class BukkitMCPlayerJoinEvent extends BukkitMCPlayerEvent - implements MCPlayerJoinEvent{ - PlayerJoinEvent pje; + PlayerJoinEvent pje; - public BukkitMCPlayerJoinEvent(PlayerJoinEvent e){ + public BukkitMCPlayerJoinEvent(PlayerJoinEvent e) { super(e); - pje = e; - } + pje = e; + } @Override - public String getJoinMessage() { - return pje.getJoinMessage(); - } + public String getJoinMessage() { + return pje.getJoinMessage(); + } @Override - public void setJoinMessage(String message) { - pje.setJoinMessage(message); - } + public void setJoinMessage(String message) { + pje.setJoinMessage(message); + } - public static PlayerJoinEvent _instantiate(MCPlayer player, String message) { - return new PlayerJoinEvent(((BukkitMCPlayer)player)._Player(), message); - } - } + public static PlayerJoinEvent _instantiate(MCPlayer player, String message) { + return new PlayerJoinEvent(((BukkitMCPlayer) player)._Player(), message); + } + } + + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCPlayerInteractEvent extends BukkitMCPlayerEvent implements MCPlayerInteractEvent { - @abstraction(type=Implementation.Type.BUKKIT) - public static class BukkitMCPlayerInteractEvent extends BukkitMCPlayerEvent - implements MCPlayerInteractEvent{ - PlayerInteractEvent pie; + PlayerInteractEvent pie; - public BukkitMCPlayerInteractEvent(PlayerInteractEvent e){ + public BukkitMCPlayerInteractEvent(PlayerInteractEvent e) { super(e); - pie = e; - } + pie = e; + } + + public static BukkitMCPlayerInteractEvent _instantiate(MCPlayer player, MCAction action, MCItemStack itemstack, + MCBlock clickedBlock, MCBlockFace clickedFace) { + return new BukkitMCPlayerInteractEvent(new PlayerInteractEvent(((BukkitMCPlayer) player)._Player(), + BukkitMCAction.getConvertor().getConcreteEnum(action), ((BukkitMCItemStack) itemstack).__ItemStack(), + ((BukkitMCBlock) clickedBlock).__Block(), BlockFace.valueOf(clickedFace.name()))); + } - public static BukkitMCPlayerInteractEvent _instantiate(MCPlayer player, MCAction action, MCItemStack itemstack, - MCBlock clickedBlock, MCBlockFace clickedFace){ - return new BukkitMCPlayerInteractEvent(new PlayerInteractEvent(((BukkitMCPlayer)player)._Player(), - BukkitMCAction.getConvertor().getConcreteEnum(action), ((BukkitMCItemStack)itemstack).__ItemStack(), - ((BukkitMCBlock)clickedBlock).__Block(), BlockFace.valueOf(clickedFace.name()))); - } + @Override + public MCAction getAction() { + return BukkitMCAction.getConvertor().getAbstractedEnum(pie.getAction()); + } + + @Override + public MCBlock getClickedBlock() { + return new BukkitMCBlock(pie.getClickedBlock()); + } @Override - public MCAction getAction() { - return BukkitMCAction.getConvertor().getAbstractedEnum(pie.getAction()); - } + public MCBlockFace getBlockFace() { + return MCBlockFace.valueOf(pie.getBlockFace().name()); + } @Override - public MCBlock getClickedBlock() { - return new BukkitMCBlock(pie.getClickedBlock()); - } + public MCItemStack getItem() { + return new BukkitMCItemStack(pie.getItem()); + } @Override - public MCBlockFace getBlockFace() { - return MCBlockFace.valueOf(pie.getBlockFace().name()); - } + public MCEquipmentSlot getHand() { + if(pie.getHand() == EquipmentSlot.HAND) { + return MCEquipmentSlot.WEAPON; + } + return MCEquipmentSlot.OFF_HAND; + } @Override - public MCItemStack getItem() { - return new BukkitMCItemStack(pie.getItem()); - } - } + public Vector3D getClickedPosition() { + Vector v = pie.getClickedPosition(); + if(v == null) { + return Vector3D.ZERO; + } + return new Vector3D(v.getX(), v.getY(), v.getZ()); + } + } + + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCPlayerRespawnEvent extends BukkitMCPlayerEvent implements MCPlayerRespawnEvent { - @abstraction(type=Implementation.Type.BUKKIT) - public static class BukkitMCPlayerRespawnEvent extends BukkitMCPlayerEvent - implements MCPlayerRespawnEvent { - PlayerRespawnEvent pre; + PlayerRespawnEvent pre; - public BukkitMCPlayerRespawnEvent(PlayerRespawnEvent event) { + public BukkitMCPlayerRespawnEvent(PlayerRespawnEvent event) { super(event); - pre = event; - } + pre = event; + } - public static BukkitMCPlayerRespawnEvent _instantiate(MCPlayer player, MCLocation location, boolean isBedSpawn) { - return new BukkitMCPlayerRespawnEvent(new PlayerRespawnEvent(((BukkitMCPlayer)player)._Player(), - ((BukkitMCLocation)location)._Location(), isBedSpawn)); - } + public static BukkitMCPlayerRespawnEvent _instantiate(MCPlayer player, MCLocation location, boolean isBedSpawn) { + return new BukkitMCPlayerRespawnEvent(new PlayerRespawnEvent(((BukkitMCPlayer) player)._Player(), + ((BukkitMCLocation) location)._Location(), isBedSpawn)); + } @Override - public void setRespawnLocation(MCLocation location) { - pre.setRespawnLocation(((BukkitMCLocation)location)._Location()); - } + public void setRespawnLocation(MCLocation location) { + pre.setRespawnLocation(((BukkitMCLocation) location)._Location()); + } + + @Override + public MCLocation getRespawnLocation() { + return new BukkitMCLocation(pre.getRespawnLocation()); + } @Override - public MCLocation getRespawnLocation() { - return new BukkitMCLocation(pre.getRespawnLocation()); - } + public Boolean isBedSpawn() { + return pre.isBedSpawn(); + } @Override - public Boolean isBedSpawn() { - return pre.isBedSpawn(); - } - } + public boolean isAnchorSpawn() { + return pre.isAnchorSpawn(); + } + + @Override + public Reason getReason() { + return Reason.valueOf(pre.getRespawnReason().name()); + } + } + + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCPlayerDeathEvent extends BukkitMCEntityDeathEvent implements MCPlayerDeathEvent { - @abstraction(type=Implementation.Type.BUKKIT) - public static class BukkitMCPlayerDeathEvent extends BukkitMCEntityDeathEvent - implements MCPlayerDeathEvent { - PlayerDeathEvent pde; + PlayerDeathEvent pde; - public BukkitMCPlayerDeathEvent(PlayerDeathEvent event) { - super(event); - pde = event; - } + public BukkitMCPlayerDeathEvent(Event event) { + super(event); + pde = (PlayerDeathEvent) event; + } - public static BukkitMCPlayerDeathEvent _instantiate(MCPlayer entity, List listOfDrops, - int droppedExp, String deathMessage){ - List drops = new ArrayList(); + public static BukkitMCPlayerDeathEvent _instantiate(MCPlayer entity, List listOfDrops, + int droppedExp, String deathMessage) { + List drops = new ArrayList<>(); - return new BukkitMCPlayerDeathEvent(new PlayerDeathEvent(((BukkitMCPlayer)entity)._Player(), drops, droppedExp, deathMessage)); - } + return new BukkitMCPlayerDeathEvent(new PlayerDeathEvent(((BukkitMCPlayer) entity)._Player(), + DamageSource.builder(DamageType.GENERIC).build(), drops, droppedExp, deathMessage)); + } - @Override - public MCPlayer getEntity() { - return new BukkitMCPlayer(pde.getEntity()); - } + @Override + public MCPlayer getEntity() { + return new BukkitMCPlayer(pde.getEntity()); + } @Override public MCEntity getKiller() { - return StaticLayer.GetCorrectEntity(new BukkitMCEntity(pde.getEntity().getKiller())); + return BukkitConvertor.BukkitGetCorrectEntity(pde.getEntity().getKiller()); + } + + @Override + public String getDeathMessage() { + return pde.getDeathMessage(); + } + + @Override + public void setDeathMessage(String nval) { + pde.setDeathMessage(nval); } @Override - public String getDeathMessage() { - return pde.getDeathMessage(); - } + public boolean getKeepInventory() { + return pde.getKeepInventory(); + } @Override - public void setDeathMessage(String nval) { - pde.setDeathMessage(nval); - } + public void setKeepInventory(boolean keepInventory) { + pde.setKeepInventory(keepInventory); + } @Override public boolean getKeepLevel() { @@ -647,118 +760,73 @@ public int getNewTotalExp() { public void setNewTotalExp(int totalExp) { pde.setNewTotalExp(totalExp); } - } + } - @abstraction(type=Implementation.Type.BUKKIT) - public static class BukkitMCPlayerCommandEvent extends BukkitMCPlayerEvent - implements MCPlayerCommandEvent{ - PlayerCommandPreprocessEvent pcpe; - boolean isCancelled = false; + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCPlayerCommandEvent extends BukkitMCPlayerEvent implements MCPlayerCommandEvent { + + PlayerCommandPreprocessEvent pcpe; + boolean isCancelled = false; - public BukkitMCPlayerCommandEvent(PlayerCommandPreprocessEvent pcpe){ + public BukkitMCPlayerCommandEvent(PlayerCommandPreprocessEvent pcpe) { super(pcpe); - this.pcpe = pcpe; - } + this.pcpe = pcpe; + } @Override - public String getCommand(){ - return pcpe.getMessage(); - } + public String getCommand() { + return pcpe.getMessage(); + } @Override - public void cancel() { - pcpe.setMessage("/commandhelper null"); + public void cancel() { + pcpe.setMessage("/commandhelper null"); pcpe.setCancelled(true); - isCancelled = true; - } - - public static BukkitMCPlayerCommandEvent _instantiate(MCPlayer entity, String command){ - return new BukkitMCPlayerCommandEvent(new PlayerCommandPreprocessEvent(((BukkitMCPlayer)entity)._Player(), command)); - } - - @Override - public void setCommand(String val) { - pcpe.setMessage(val); - } - - @Override - public boolean isCancelled() { - return isCancelled; - } - } - - @abstraction(type=Implementation.Type.BUKKIT) - public static class BukkitMCWorldChangedEvent extends BukkitMCPlayerEvent - implements MCWorldChangedEvent{ - PlayerChangedWorldEvent pcwe; - - public BukkitMCWorldChangedEvent(PlayerChangedWorldEvent e){ - super(e); - pcwe = e; - } - - @Override - public MCWorld getFrom() { - return new BukkitMCWorld(pcwe.getFrom()); - } - - @Override - public MCWorld getTo() { - return new BukkitMCWorld(pcwe.getPlayer().getWorld()); - } - - public static BukkitMCWorldChangedEvent _instantiate(MCPlayer entity, MCWorld from){ - return new BukkitMCWorldChangedEvent(new PlayerChangedWorldEvent(((BukkitMCPlayer)entity)._Player(), ((BukkitMCWorld)from).__World())); - } - } - - @abstraction(type=Implementation.Type.BUKKIT) - public static class BukkitMCPlayerMovedEvent implements MCPlayerMoveEvent { - BukkitMCLocation from; - BukkitMCLocation to; - BukkitMCPlayer player; - boolean cancelled = false; - - public BukkitMCPlayerMovedEvent(Player p, Location from, Location to){ - this.player = new BukkitMCPlayer(p); - this.from = new BukkitMCLocation(from); - this.to = new BukkitMCLocation(to); + isCancelled = true; } - @Override - public MCLocation getFrom() { - return from; + public static BukkitMCPlayerCommandEvent _instantiate(MCPlayer entity, String command) { + return new BukkitMCPlayerCommandEvent(new PlayerCommandPreprocessEvent(((BukkitMCPlayer) entity)._Player(), command)); } @Override - public MCLocation getTo() { - return to; + public void setCommand(String val) { + pcpe.setMessage(val); } @Override - public MCPlayer getPlayer() { - return player; + public boolean isCancelled() { + return isCancelled; } + } - @Override - public Object _GetObject() { - return null; + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCWorldChangedEvent extends BukkitMCPlayerEvent implements MCWorldChangedEvent { + + PlayerChangedWorldEvent pcwe; + + public BukkitMCWorldChangedEvent(PlayerChangedWorldEvent e) { + super(e); + pcwe = e; } @Override - public void setCancelled(boolean state) { - cancelled = state; + public MCWorld getFrom() { + return new BukkitMCWorld(pcwe.getFrom()); } @Override - public boolean isCancelled() { - return cancelled; + public MCWorld getTo() { + return new BukkitMCWorld(pcwe.getPlayer().getWorld()); } + public static BukkitMCWorldChangedEvent _instantiate(MCPlayer entity, MCWorld from) { + return new BukkitMCWorldChangedEvent(new PlayerChangedWorldEvent(((BukkitMCPlayer) entity)._Player(), ((BukkitMCWorld) from).__World())); + } } - public static class BukkitMCPlayerFishEvent extends BukkitMCPlayerEvent - implements MCPlayerFishEvent { + public static class BukkitMCPlayerFishEvent extends BukkitMCPlayerEvent implements MCPlayerFishEvent { + PlayerFishEvent e; public BukkitMCPlayerFishEvent(PlayerFishEvent event) { @@ -768,10 +836,10 @@ public BukkitMCPlayerFishEvent(PlayerFishEvent event) { @Override public MCEntity getCaught() { - if (e.getCaught() == null) { + if(e.getCaught() == null) { return null; } - return new BukkitMCEntity(e.getCaught()); + return BukkitConvertor.BukkitGetCorrectEntity(e.getCaught()); } @Override @@ -793,10 +861,20 @@ public MCFishingState getState() { public void setExpToDrop(int exp) { e.setExpToDrop(exp); } + + @Override + public MCEquipmentSlot getHand() { + if(e.getHand() == null) { + return null; + } else if(e.getHand() == EquipmentSlot.HAND) { + return MCEquipmentSlot.WEAPON; + } + return MCEquipmentSlot.OFF_HAND; + } } - public static class BukkitMCGamemodeChangeEvent extends BukkitMCPlayerEvent - implements MCGamemodeChangeEvent { + public static class BukkitMCGamemodeChangeEvent extends BukkitMCPlayerEvent implements MCGamemodeChangeEvent { + PlayerGameModeChangeEvent gmc; public BukkitMCGamemodeChangeEvent(PlayerGameModeChangeEvent event) { @@ -810,33 +888,8 @@ public MCGameMode getNewGameMode() { } } - public static class BukkitMCChatTabCompleteEvent extends BukkitMCPlayerEvent - implements MCChatTabCompleteEvent { - PlayerChatTabCompleteEvent tc; - - public BukkitMCChatTabCompleteEvent(PlayerChatTabCompleteEvent event) { - super(event); - this.tc = event; - } - - @Override - public String getChatMessage() { - return tc.getChatMessage(); - } - - @Override - public String getLastToken() { - return tc.getLastToken(); - } - - @Override - public Collection getTabCompletions() { - return tc.getTabCompletions(); - } - } + public static class BukkitMCExpChangeEvent extends BukkitMCPlayerEvent implements MCExpChangeEvent { - public static class BukkitMCExpChangeEvent extends BukkitMCPlayerEvent - implements MCExpChangeEvent { PlayerExpChangeEvent ec; public BukkitMCExpChangeEvent(PlayerExpChangeEvent event) { @@ -856,8 +909,8 @@ public void setAmount(int amount) { } @abstraction(type = Implementation.Type.BUKKIT) - public static class BukkitMCPlayerEditBookEvent extends BukkitMCPlayerEvent - implements MCPlayerEditBookEvent { + public static class BukkitMCPlayerEditBookEvent extends BukkitMCPlayerEvent implements MCPlayerEditBookEvent { + PlayerEditBookEvent pebe; public BukkitMCPlayerEditBookEvent(PlayerEditBookEvent event) { @@ -880,9 +933,14 @@ public void setNewBookMeta(MCBookMeta bookMeta) { pebe.setNewBookMeta(((BukkitMCBookMeta) bookMeta).getBookMeta()); } + @SuppressWarnings("deprecation") @Override public int getSlot() { - return pebe.getSlot(); + int slot = pebe.getSlot(); + if(slot == -1) { // bukkit offhand slot + return -106; // vanilla offhand slot + } + return slot; } @Override @@ -897,8 +955,8 @@ public void setSigning(boolean isSigning) { } @abstraction(type = Implementation.Type.BUKKIT) - public static class BukkitMCPlayerToggleFlightEvent extends BukkitMCPlayerEvent - implements MCPlayerToggleFlightEvent { + public static class BukkitMCPlayerToggleFlightEvent extends BukkitMCPlayerEvent implements MCPlayerToggleFlightEvent { + PlayerToggleFlightEvent ptfe; public BukkitMCPlayerToggleFlightEvent(PlayerToggleFlightEvent event) { @@ -923,8 +981,8 @@ public void setCancelled(boolean bln) { } @abstraction(type = Implementation.Type.BUKKIT) - public static class BukkitMCPlayerToggleSneakEvent extends BukkitMCPlayerEvent - implements MCPlayerToggleSneakEvent { + public static class BukkitMCPlayerToggleSneakEvent extends BukkitMCPlayerEvent implements MCPlayerToggleSneakEvent { + PlayerToggleSneakEvent ptse; public BukkitMCPlayerToggleSneakEvent(PlayerToggleSneakEvent event) { @@ -949,8 +1007,8 @@ public void setCancelled(boolean bln) { } @abstraction(type = Implementation.Type.BUKKIT) - public static class BukkitMCPlayerToggleSprintEvent extends BukkitMCPlayerEvent - implements MCPlayerToggleSprintEvent { + public static class BukkitMCPlayerToggleSprintEvent extends BukkitMCPlayerEvent implements MCPlayerToggleSprintEvent { + PlayerToggleSprintEvent ptse; public BukkitMCPlayerToggleSprintEvent(PlayerToggleSprintEvent event) { @@ -973,4 +1031,189 @@ public void setCancelled(boolean bln) { ptse.setCancelled(bln); } } + + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCPlayerResourcePackEvent extends BukkitMCPlayerEvent implements MCPlayerResourcePackEvent { + + PlayerResourcePackStatusEvent e; + + public BukkitMCPlayerResourcePackEvent(PlayerResourcePackStatusEvent event) { + super(event); + this.e = event; + } + + @Override + public MCResourcePackStatus getStatus() { + return BukkitMCResourcePackStatus.getConvertor().getAbstractedEnum(this.e.getStatus()); + } + + @Override + public UUID getId() { + return this.e.getID(); + } + } + + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCPlayerMoveEvent extends BukkitMCPlayerEvent implements MCPlayerMoveEvent { + + PlayerMoveEvent pme; + int threshold; + MCLocation from; + + public BukkitMCPlayerMoveEvent(PlayerMoveEvent event, int threshold, MCLocation from) { + super(event); + this.pme = event; + this.threshold = threshold; + this.from = from; + } + + @Override + public int getThreshold() { + return threshold; + } + + @Override + public MCLocation getFrom() { + return new BukkitMCLocation(from); + } + + @Override + public MCLocation getTo() { + return new BukkitMCLocation(pme.getTo()); + } + + @Override + public boolean isCancelled() { + return pme.isCancelled(); + } + + @Override + public void setCancelled(boolean bln) { + pme.setCancelled(bln); + } + } + + + @abstraction(type = Implementation.Type.BUKKIT) + public abstract static class BukkitMCPlayerBucketEvent extends BukkitMCPlayerEvent implements MCPlayerBucketEvent { + + PlayerBucketEvent pbe; + + public BukkitMCPlayerBucketEvent(PlayerBucketEvent event) { + super(event); + this.pbe = event; + } + + @Override + public MCBlock getBlock() { + return new BukkitMCBlock(pbe.getBlock()); + } + + @Override + public MCBlock getBlockClicked() { + return new BukkitMCBlock(pbe.getBlockClicked()); + } + + @Override + public MCBlockFace getBlockFace() { + return MCBlockFace.valueOf(pbe.getBlockFace().name()); + } + + @Override + public MCMaterial getBucket() { + return new BukkitMCMaterial(pbe.getBucket()); + } + + @Override + public MCEquipmentSlot getHand() { + if(pbe.getHand() == EquipmentSlot.HAND) { + return MCEquipmentSlot.WEAPON; + } + return MCEquipmentSlot.OFF_HAND; + } + + @Override + public MCItemStack getItemStack() { + return new BukkitMCItemStack(pbe.getItemStack()); + } + } + + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCPlayerBucketFillEvent extends BukkitMCPlayerBucketEvent implements MCPlayerBucketFillEvent { + + PlayerBucketFillEvent pbfe; + + public BukkitMCPlayerBucketFillEvent(PlayerBucketFillEvent event) { + super(event); + this.pbfe = event; + } + } + + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCPlayerBucketEmptyEvent extends BukkitMCPlayerBucketEvent implements MCPlayerBucketEmptyEvent { + + PlayerBucketEmptyEvent pbee; + + public BukkitMCPlayerBucketEmptyEvent(PlayerBucketEmptyEvent event) { + super(event); + this.pbee = event; + } + } + + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCPlayerAdvancementDoneEvent extends BukkitMCPlayerEvent implements MCPlayerAdvancementDoneEvent { + + PlayerAdvancementDoneEvent e; + + public BukkitMCPlayerAdvancementDoneEvent(PlayerAdvancementDoneEvent event) { + super(event); + this.e = event; + } + + @Override + public MCNamespacedKey getAdvancementKey() { + return new BukkitMCNamespacedKey(e.getAdvancement().getKey()); + } + + @Override + public String getTitle() { + if(((BukkitMCServer) Static.getServer()).isPaper()) { + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_17_X)) { + AdvancementDisplay display = e.getAdvancement().getDisplay(); + if(display != null) { + return PlainTextComponentSerializer.plainText().serialize(display.title()); + } + } + } else if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_18_X)) { + org.bukkit.advancement.AdvancementDisplay display = ReflectionUtils.invokeMethod(e.getAdvancement(), + "getDisplay"); + if(display != null) { + return display.getTitle(); + } + } + return null; + } + } + + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCPlayerStopUsingItemEvent extends BukkitMCPlayerEvent implements MCPlayerStopUsingItemEvent { + + PlayerStopUsingItemEvent e; + + public BukkitMCPlayerStopUsingItemEvent(PlayerStopUsingItemEvent event) { + super(event); + this.e = event; + } + + @Override + public MCItemStack getItem() { + return new BukkitMCItemStack(this.e.getItem()); + } + + @Override + public int getTicksHeldFor() { + return this.e.getTicksHeldFor(); + } + + } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitServerEvents.java b/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitServerEvents.java new file mode 100644 index 0000000000..d322ce35a3 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitServerEvents.java @@ -0,0 +1,221 @@ +package com.laytonsmith.abstraction.bukkit.events; + +import com.laytonsmith.abstraction.Implementation; +import com.laytonsmith.abstraction.MCCommandSender; +import com.laytonsmith.abstraction.MCPlayer; +import com.laytonsmith.abstraction.bukkit.BukkitMCCommandSender; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCPlayer; +import com.laytonsmith.abstraction.events.MCBroadcastMessageEvent; +import com.laytonsmith.abstraction.events.MCServerCommandEvent; +import com.laytonsmith.abstraction.events.MCServerPingEvent; +import com.laytonsmith.annotations.abstraction; + +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.server.BroadcastMessageEvent; +import org.bukkit.event.server.ServerCommandEvent; +import org.bukkit.event.server.ServerListPingEvent; + +import java.net.InetAddress; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +public class BukkitServerEvents { + + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCServerCommandEvent implements MCServerCommandEvent { + + ServerCommandEvent sce; + MCCommandSender sender; + + public BukkitMCServerCommandEvent(ServerCommandEvent sce, MCCommandSender sender) { + this.sce = sce; + this.sender = sender; + } + + @Override + public Object _GetObject() { + return sce; + } + + @Override + public String getCommand() { + return sce.getCommand(); + } + + @Override + public void setCommand(String command) { + sce.setCommand(command); + } + + @Override + public MCCommandSender getCommandSender() { + return sender; + } + } + + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCServerPingEvent implements MCServerPingEvent { + + private final ServerListPingEvent slp; + + public BukkitMCServerPingEvent(ServerListPingEvent event) { + slp = event; + } + + @Override + public Object _GetObject() { + return slp; + } + + @Override + public InetAddress getAddress() { + return slp.getAddress(); + } + + @Override + public int getMaxPlayers() { + return slp.getMaxPlayers(); + } + + @Override + public String getMOTD() { + return slp.getMotd(); + } + + @Override + public int getNumPlayers() { + return slp.getNumPlayers(); + } + + @Override + public void setMaxPlayers(int max) { + slp.setMaxPlayers(max); + } + + @Override + public void setMOTD(String motd) { + slp.setMotd(motd); + } + + @Override + public Set getPlayers() { + Set players = new HashSet<>(); + try { + Iterator iterator = slp.iterator(); + while(iterator.hasNext()) { + players.add(new BukkitMCPlayer(iterator.next())); + } + } catch (UnsupportedOperationException ex) { + // not implemented, ignore + } + return players; + } + + @Override + public void setPlayers(Collection players) { + Set ps = new HashSet<>(); + for(MCPlayer player : players) { + ps.add((Player) player.getHandle()); + } + try { + Iterator iterator = slp.iterator(); + while(iterator.hasNext()) { + if(!ps.contains(iterator.next())) { + iterator.remove(); + } + } + } catch (UnsupportedOperationException ex) { + // not implemented, ignore + } + } + } + + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCBroadcastMessageEvent implements MCBroadcastMessageEvent { + + private final BroadcastMessageEvent bme; + + public BukkitMCBroadcastMessageEvent(Event event) { + this.bme = (BroadcastMessageEvent) event; + } + + // This constructor is required by EventBuilder.instantiate(...). + public BukkitMCBroadcastMessageEvent(BroadcastMessageEvent event) { + this.bme = event; + } + + public static BroadcastMessageEvent _instantiate(String message, Set recipients) { + Set bukkitRecipients = new HashSet<>(recipients.size()); + for(MCCommandSender commandSender : recipients) { + if(commandSender.getHandle() instanceof CommandSender) { + bukkitRecipients.add((CommandSender) commandSender.getHandle()); + } + } + return new BroadcastMessageEvent(message, bukkitRecipients); + } + + @Override + public Object _GetObject() { + return this.bme; + } + + @Override + public void cancel(boolean state) { + this.bme.setCancelled(state); + } + + @Override + public String getMessage() { + return this.bme.getMessage(); + } + + @Override + public void setMessage(String message) { + this.bme.setMessage(message); + } + + /** + * Gets the recipients of this message. + * Modifications made to the returned set do not have influence on the event itself. + * This set can contain command senders like players, command blocks, command block functions and console. + * To only receive the player recipients, use the {@link #getPlayerRecipients()} method. + * @return The recipients of this message. + */ + @Override + public Set getRecipients() { + Set ret = new HashSet(); + for(CommandSender sender : this.bme.getRecipients()) { + ret.add(new BukkitMCCommandSender(sender)); + } + return ret; + } + + /** + * Gets the player recipients of this message. + * Modifications made to the returned set do not have influence on the event itself. + * This set can contain command senders like players, command blocks, command block functions and console. + * To receive player and non-player recipients, use the {@link #getRecipients()} method. + * @return The player recipients of this message. + */ + @Override + public Set getPlayerRecipients() { + Set ret = new HashSet(); + for(CommandSender sender : this.bme.getRecipients()) { + if(sender instanceof Player) { + ret.add(new BukkitMCPlayer((Player) sender)); + } + } + return ret; + } + + @Override + public boolean isCancelled() { + return this.bme.isCancelled(); + } + + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitVehicleEvents.java b/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitVehicleEvents.java index 3522bbafcb..17a7aab11e 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitVehicleEvents.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitVehicleEvents.java @@ -3,50 +3,47 @@ import com.laytonsmith.abstraction.Implementation; import com.laytonsmith.abstraction.MCEntity; import com.laytonsmith.abstraction.MCLocation; -import com.laytonsmith.abstraction.MCVehicle; import com.laytonsmith.abstraction.blocks.MCBlock; import com.laytonsmith.abstraction.bukkit.BukkitConvertor; import com.laytonsmith.abstraction.bukkit.BukkitMCLocation; -import com.laytonsmith.abstraction.bukkit.BukkitMCVehicle; import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCBlock; import com.laytonsmith.abstraction.enums.MCCollisionType; import com.laytonsmith.abstraction.events.MCVehicleBlockCollideEvent; -import com.laytonsmith.abstraction.events.MCVehicleEnitityCollideEvent; +import com.laytonsmith.abstraction.events.MCVehicleEntityCollideEvent; import com.laytonsmith.abstraction.events.MCVehicleEnterExitEvent; import com.laytonsmith.abstraction.events.MCVehicleEvent; import com.laytonsmith.abstraction.events.MCVehicleMoveEvent; +import com.laytonsmith.abstraction.events.MCVehicleDestroyEvent; import com.laytonsmith.annotations.abstraction; -import org.bukkit.Location; -import org.bukkit.entity.Vehicle; import org.bukkit.event.vehicle.VehicleBlockCollisionEvent; import org.bukkit.event.vehicle.VehicleCollisionEvent; import org.bukkit.event.vehicle.VehicleEnterEvent; import org.bukkit.event.vehicle.VehicleEntityCollisionEvent; import org.bukkit.event.vehicle.VehicleEvent; import org.bukkit.event.vehicle.VehicleExitEvent; +import org.bukkit.event.vehicle.VehicleMoveEvent; +import org.bukkit.event.vehicle.VehicleDestroyEvent; -/** - * - * @author jb_aero - */ public class BukkitVehicleEvents { - + @abstraction(type = Implementation.Type.BUKKIT) public static class BukkitMCVehicleEntityCollideEvent extends BukkitMCVehicleEvent - implements MCVehicleEnitityCollideEvent { + implements MCVehicleEntityCollideEvent { + + private final VehicleEntityCollisionEvent vec; + private MCEntity entity; - VehicleEntityCollisionEvent vec; - public BukkitMCVehicleEntityCollideEvent(VehicleCollisionEvent event) { + public BukkitMCVehicleEntityCollideEvent(VehicleEntityCollisionEvent event) { super(event); - vec = (VehicleEntityCollisionEvent) event; + vec = event; } - + @Override public MCEntity getEntity() { - if (vec.getEntity() == null) { - return null; + if(entity == null) { + entity = BukkitConvertor.BukkitGetCorrectEntity(vec.getEntity()); } - return BukkitConvertor.BukkitGetCorrectEntity(vec.getEntity()); + return entity; } @Override @@ -74,17 +71,18 @@ public MCCollisionType getCollisionType() { return MCCollisionType.ENTITY; } } - + @abstraction(type = Implementation.Type.BUKKIT) public static class BukkitMCVehicleBlockCollideEvent extends BukkitMCVehicleEvent implements MCVehicleBlockCollideEvent { VehicleBlockCollisionEvent vbc; + public BukkitMCVehicleBlockCollideEvent(VehicleCollisionEvent event) { super(event); vbc = (VehicleBlockCollisionEvent) event; } - + @Override public MCBlock getBlock() { return new BukkitMCBlock(vbc.getBlock()); @@ -95,108 +93,121 @@ public MCCollisionType getCollisionType() { return MCCollisionType.BLOCK; } } - + @abstraction(type = Implementation.Type.BUKKIT) - public static class BukkitMCVehicleEnterEvent extends BukkitMCVehicleEvent - implements MCVehicleEnterExitEvent { + public static class BukkitMCVehicleEnterEvent extends BukkitMCVehicleEvent implements MCVehicleEnterExitEvent { VehicleEnterEvent vee; + public BukkitMCVehicleEnterEvent(VehicleEnterEvent event) { super(event); vee = event; } - + @Override public MCEntity getEntity() { - if (vee.getEntered() == null) { - return null; - } return BukkitConvertor.BukkitGetCorrectEntity(vee.getEntered()); } } - + @abstraction(type = Implementation.Type.BUKKIT) - public static class BukkitMCVehicleExitEvent extends BukkitMCVehicleEvent - implements MCVehicleEnterExitEvent { + public static class BukkitMCVehicleExitEvent extends BukkitMCVehicleEvent implements MCVehicleEnterExitEvent { VehicleExitEvent vee; + public BukkitMCVehicleExitEvent(VehicleExitEvent event) { super(event); vee = event; } - + @Override public MCEntity getEntity() { - if (vee.getExited() == null) { - return null; - } return BukkitConvertor.BukkitGetCorrectEntity(vee.getExited()); } } - public static class BukkitMCVehicleEvent implements MCVehicleEvent { + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCVehicleMoveEvent extends BukkitMCVehicleEvent implements MCVehicleMoveEvent { - VehicleEvent ve; - public BukkitMCVehicleEvent(VehicleEvent event) { - ve = event; + VehicleMoveEvent vme; + int threshold; + boolean cancelled; + MCLocation from; + + public BukkitMCVehicleMoveEvent(VehicleMoveEvent event, int threshold, MCLocation from) { + super(event); + vme = event; + this.threshold = threshold; + this.cancelled = false; + this.from = from; } @Override - public MCVehicle getVehicle() { - if (ve.getVehicle() instanceof org.bukkit.entity.Vehicle) { - return (MCVehicle) BukkitConvertor.BukkitGetCorrectEntity(ve.getVehicle()); - } - return null; + public int getThreshold() { + return threshold; } @Override - public Object _GetObject() { - return ve; + public MCLocation getFrom() { + return new BukkitMCLocation(from); } - } - - @abstraction(type = Implementation.Type.BUKKIT) - public static class BukkitMCVehicleMoveEvent implements MCVehicleMoveEvent { - BukkitMCLocation from; - BukkitMCLocation to; - BukkitMCVehicle vehicle; - boolean cancelled = false; - - public BukkitMCVehicleMoveEvent(Vehicle vehicle, Location from, Location to) { - this.vehicle = new BukkitMCVehicle(vehicle); - this.from = new BukkitMCLocation(from); - this.to = new BukkitMCLocation(to); + @Override + public MCLocation getTo() { + return new BukkitMCLocation(vme.getTo()); } @Override - public MCLocation getFrom() { - return from; + public void setCancelled(boolean state) { + cancelled = state; } @Override - public MCLocation getTo() { - return to; + public boolean isCancelled() { + return cancelled; } + } - @Override - public MCVehicle getVehicle() { - return vehicle; + @abstraction(type = Implementation.Type.BUKKIT) + public static class BukkitMCVehicleDestroyEvent extends BukkitMCVehicleEvent implements MCVehicleDestroyEvent { + + VehicleDestroyEvent vee; + + public BukkitMCVehicleDestroyEvent(VehicleDestroyEvent event) { + super(event); + vee = event; } @Override - public Object _GetObject() { - return null; + public MCEntity getAttacker() { + if(vee.getAttacker() == null) { + return null; + } + return BukkitConvertor.BukkitGetCorrectEntity(vee.getAttacker()); + } + } + + public static class BukkitMCVehicleEvent implements MCVehicleEvent { + + private final VehicleEvent ve; + private MCEntity vehicle; + + public BukkitMCVehicleEvent(VehicleEvent event) { + ve = event; } @Override - public void setCancelled(boolean state) { - cancelled = state; + public MCEntity getVehicle() { + if(vehicle == null) { + vehicle = BukkitConvertor.BukkitGetCorrectEntity(ve.getVehicle()); + } + return vehicle; } @Override - public boolean isCancelled() { - return cancelled; + public Object _GetObject() { + return ve; } } + } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitWeatherEvents.java b/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitWeatherEvents.java new file mode 100644 index 0000000000..9b4cef46bc --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitWeatherEvents.java @@ -0,0 +1,86 @@ +package com.laytonsmith.abstraction.bukkit.events; + +import com.laytonsmith.abstraction.entities.MCLightningStrike; +import com.laytonsmith.abstraction.MCWorld; +import com.laytonsmith.abstraction.bukkit.BukkitMCWorld; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCLightningStrike; +import com.laytonsmith.abstraction.events.MCLightningStrikeEvent; +import com.laytonsmith.abstraction.events.MCThunderChangeEvent; +import com.laytonsmith.abstraction.events.MCWeatherChangeEvent; +import com.laytonsmith.abstraction.events.MCWeatherEvent; +import org.bukkit.event.weather.LightningStrikeEvent; +import org.bukkit.event.weather.ThunderChangeEvent; +import org.bukkit.event.weather.WeatherChangeEvent; +import org.bukkit.event.weather.WeatherEvent; + +public final class BukkitWeatherEvents { + + public abstract static class BukkitMCWeatherEvent implements MCWeatherEvent { + + private final WeatherEvent event; + + public BukkitMCWeatherEvent(WeatherEvent event) { + this.event = event; + } + + @Override + public Object _GetObject() { + return this.event; + } + + @Override + public MCWorld getWorld() { + return new BukkitMCWorld(this.event.getWorld()); + } + } + + public static class BukkitMCLightningStrikeEvent extends BukkitMCWeatherEvent implements MCLightningStrikeEvent { + + private final LightningStrikeEvent event; + + public BukkitMCLightningStrikeEvent(LightningStrikeEvent event) { + super(event); + this.event = event; + } + + @Override + public MCLightningStrike getLightning() { + return new BukkitMCLightningStrike(this.event.getLightning()); + } + + @Override + public Cause getCause() { + return Cause.valueOf(event.getCause().name()); + } + } + + public static class BukkitMCThunderChangeEvent extends BukkitMCWeatherEvent implements MCThunderChangeEvent { + + private final ThunderChangeEvent event; + + public BukkitMCThunderChangeEvent(ThunderChangeEvent event) { + super(event); + this.event = event; + } + + @Override + public boolean toThunderState() { + return this.event.toThunderState(); + } + } + + public static class BukkitMCWeatherChangeEvent extends BukkitMCWeatherEvent implements MCWeatherChangeEvent { + + private final WeatherChangeEvent event; + + public BukkitMCWeatherChangeEvent(WeatherChangeEvent event) { + super(event); + this.event = event; + } + + @Override + public boolean toWeatherState() { + return this.event.toWeatherState(); + } + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitWorldEvents.java b/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitWorldEvents.java index 05be511a92..dfd11ba23d 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitWorldEvents.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/events/BukkitWorldEvents.java @@ -1,126 +1,114 @@ -package com.laytonsmith.abstraction.bukkit.events; - -import com.laytonsmith.abstraction.MCLocation; -import com.laytonsmith.abstraction.MCPlayer; -import com.laytonsmith.abstraction.MCWorld; -import com.laytonsmith.abstraction.blocks.MCBlockState; -import com.laytonsmith.abstraction.bukkit.BukkitMCLocation; -import com.laytonsmith.abstraction.bukkit.BukkitMCPlayer; -import com.laytonsmith.abstraction.bukkit.BukkitMCWorld; -import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCBlockState; -import com.laytonsmith.abstraction.enums.MCTreeType; -import com.laytonsmith.abstraction.enums.bukkit.BukkitMCTreeType; -import com.laytonsmith.abstraction.events.MCStructureGrowEvent; -import com.laytonsmith.abstraction.events.MCWorldEvent; -import com.laytonsmith.abstraction.events.MCWorldLoadEvent; -import com.laytonsmith.abstraction.events.MCWorldSaveEvent; -import com.laytonsmith.abstraction.events.MCWorldUnloadEvent; -import java.util.ArrayList; -import java.util.List; -import org.bukkit.block.BlockState; -import org.bukkit.entity.Player; -import org.bukkit.event.world.StructureGrowEvent; -import org.bukkit.event.world.WorldEvent; -import org.bukkit.event.world.WorldLoadEvent; -import org.bukkit.event.world.WorldSaveEvent; -import org.bukkit.event.world.WorldUnloadEvent; - -/** - * - * @author KingFisher - */ -public final class BukkitWorldEvents { - - private BukkitWorldEvents() { - } - - public static abstract class BukkitMCWorldEvent implements MCWorldEvent { - - private final WorldEvent _event; - - public BukkitMCWorldEvent(WorldEvent event) { - _event = event; - } - - @Override - public Object _GetObject() { - return _event; - } - - @Override - public MCWorld getWorld() { - return new BukkitMCWorld(_event.getWorld()); - } - } - - public static class BukkitMCStructureGrowEvent extends BukkitMCWorldEvent implements MCStructureGrowEvent { - - private final StructureGrowEvent _event; - - public BukkitMCStructureGrowEvent(StructureGrowEvent event) { - super(event); - _event = event; - } - - @Override - public List getBlocks() { - List blocks = _event.getBlocks(); - ArrayList r = new ArrayList<>(blocks.size()); - for (BlockState block : blocks) { - r.add(new BukkitMCBlockState(block)); - } - return r; - } - - @Override - public MCLocation getLocation() { - return new BukkitMCLocation(_event.getLocation()); - } - - @Override - public MCPlayer getPlayer() { - Player player = _event.getPlayer(); - return player == null ? null : new BukkitMCPlayer(_event.getPlayer()); - } - - @Override - public MCTreeType getSpecies() { - return BukkitMCTreeType.getConvertor().getAbstractedEnum(_event.getSpecies()); - } - - @Override - public boolean isFromBonemeal() { - return _event.isFromBonemeal(); - } - } - - public static class BukkitMCWorldSaveEvent extends BukkitMCWorldEvent implements MCWorldSaveEvent { - - private final WorldSaveEvent _event; - - public BukkitMCWorldSaveEvent(WorldSaveEvent event) { - super(event); - _event = event; - } - } - - public static class BukkitMCWorldUnloadEvent extends BukkitMCWorldEvent implements MCWorldUnloadEvent { - - private final WorldUnloadEvent _event; - - public BukkitMCWorldUnloadEvent(WorldUnloadEvent event) { - super(event); - _event = event; - } - } - - public static class BukkitMCWorldLoadEvent extends BukkitMCWorldEvent implements MCWorldLoadEvent { - - private final WorldLoadEvent _event; - - public BukkitMCWorldLoadEvent(WorldLoadEvent event) { - super(event); - _event = event; - } - } -} \ No newline at end of file +package com.laytonsmith.abstraction.bukkit.events; + +import com.laytonsmith.abstraction.MCLocation; +import com.laytonsmith.abstraction.MCPlayer; +import com.laytonsmith.abstraction.MCWorld; +import com.laytonsmith.abstraction.blocks.MCBlockState; +import com.laytonsmith.abstraction.bukkit.BukkitMCLocation; +import com.laytonsmith.abstraction.bukkit.BukkitMCWorld; +import com.laytonsmith.abstraction.bukkit.blocks.BukkitMCBlockState; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCPlayer; +import com.laytonsmith.abstraction.enums.MCTreeType; +import com.laytonsmith.abstraction.enums.bukkit.BukkitMCTreeType; +import com.laytonsmith.abstraction.events.MCStructureGrowEvent; +import com.laytonsmith.abstraction.events.MCWorldEvent; +import com.laytonsmith.abstraction.events.MCWorldLoadEvent; +import com.laytonsmith.abstraction.events.MCWorldSaveEvent; +import com.laytonsmith.abstraction.events.MCWorldUnloadEvent; +import org.bukkit.block.BlockState; +import org.bukkit.entity.Player; +import org.bukkit.event.world.StructureGrowEvent; +import org.bukkit.event.world.WorldEvent; +import org.bukkit.event.world.WorldLoadEvent; +import org.bukkit.event.world.WorldSaveEvent; +import org.bukkit.event.world.WorldUnloadEvent; + +import java.util.ArrayList; +import java.util.List; + +public final class BukkitWorldEvents { + + private BukkitWorldEvents() { + } + + public abstract static class BukkitMCWorldEvent implements MCWorldEvent { + + private final WorldEvent event; + + public BukkitMCWorldEvent(WorldEvent event) { + this.event = event; + } + + @Override + public Object _GetObject() { + return this.event; + } + + @Override + public MCWorld getWorld() { + return new BukkitMCWorld(this.event.getWorld()); + } + } + + public static class BukkitMCStructureGrowEvent extends BukkitMCWorldEvent implements MCStructureGrowEvent { + + private final StructureGrowEvent event; + + public BukkitMCStructureGrowEvent(StructureGrowEvent event) { + super(event); + this.event = event; + } + + @Override + public List getBlocks() { + List blocks = this.event.getBlocks(); + ArrayList r = new ArrayList<>(blocks.size()); + for(BlockState block : blocks) { + r.add(new BukkitMCBlockState(block)); + } + return r; + } + + @Override + public MCLocation getLocation() { + return new BukkitMCLocation(this.event.getLocation()); + } + + @Override + public MCPlayer getPlayer() { + Player player = this.event.getPlayer(); + return player == null ? null : new BukkitMCPlayer(this.event.getPlayer()); + } + + @Override + public MCTreeType getSpecies() { + return BukkitMCTreeType.getConvertor().getAbstractedEnum(this.event.getSpecies()); + } + + @Override + public boolean isFromBonemeal() { + return this.event.isFromBonemeal(); + } + } + + public static class BukkitMCWorldSaveEvent extends BukkitMCWorldEvent implements MCWorldSaveEvent { + + public BukkitMCWorldSaveEvent(WorldSaveEvent event) { + super(event); + } + } + + public static class BukkitMCWorldUnloadEvent extends BukkitMCWorldEvent implements MCWorldUnloadEvent { + + public BukkitMCWorldUnloadEvent(WorldUnloadEvent event) { + super(event); + } + } + + public static class BukkitMCWorldLoadEvent extends BukkitMCWorldEvent implements MCWorldLoadEvent { + + public BukkitMCWorldLoadEvent(WorldLoadEvent event) { + super(event); + } + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/events/drivers/BukkitBlockListener.java b/src/main/java/com/laytonsmith/abstraction/bukkit/events/drivers/BukkitBlockListener.java index 077449eb5a..1c5e5aeaa2 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/events/drivers/BukkitBlockListener.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/events/drivers/BukkitBlockListener.java @@ -1,73 +1,157 @@ - - package com.laytonsmith.abstraction.bukkit.events.drivers; +import com.laytonsmith.abstraction.MCItemStack; import com.laytonsmith.abstraction.bukkit.events.BukkitBlockEvents; import com.laytonsmith.core.events.Driver; import com.laytonsmith.core.events.EventUtils; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.ExperienceOrb; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.block.BlockBurnEvent; import org.bukkit.event.block.BlockDispenseEvent; +import org.bukkit.event.block.BlockExplodeEvent; +import org.bukkit.event.block.BlockFadeEvent; +import org.bukkit.event.block.BlockFromToEvent; import org.bukkit.event.block.BlockGrowEvent; import org.bukkit.event.block.BlockIgniteEvent; +import org.bukkit.event.block.BlockPistonExtendEvent; +import org.bukkit.event.block.BlockPistonRetractEvent; import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.block.NotePlayEvent; import org.bukkit.event.block.SignChangeEvent; +import org.bukkit.event.block.BlockFormEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.PluginManager; +public class BukkitBlockListener implements Listener { + + @EventHandler(priority = EventPriority.LOWEST) + public void onPistonExtend(final BlockPistonExtendEvent e) { + BukkitBlockEvents.BukkitMCBlockPistonExtendEvent mce = new BukkitBlockEvents.BukkitMCBlockPistonExtendEvent(e); + EventUtils.TriggerListener(Driver.PISTON_EXTEND, "piston_extend", mce); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onPistonRetract(final BlockPistonRetractEvent e) { + BukkitBlockEvents.BukkitMCBlockPistonRetractEvent mce = new BukkitBlockEvents.BukkitMCBlockPistonRetractEvent(e); + EventUtils.TriggerListener(Driver.PISTON_RETRACT, "piston_retract", mce); + } -/** - * - * - */ -public class BukkitBlockListener implements Listener{ - @EventHandler(priority=EventPriority.LOWEST) - public void onSignChange(SignChangeEvent e){ + @EventHandler(priority = EventPriority.LOWEST) + public void onSignChange(SignChangeEvent e) { BukkitBlockEvents.BukkitMCSignChangeEvent mce = new BukkitBlockEvents.BukkitMCSignChangeEvent(e); - EventUtils.TriggerExternal(mce); - EventUtils.TriggerListener(Driver.SIGN_CHANGED, "sign_changed", mce); - } - - @EventHandler(priority=EventPriority.LOWEST) - public void onBlockPlace(BlockPlaceEvent e){ + EventUtils.TriggerListener(Driver.SIGN_CHANGED, "sign_changed", mce); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onBlockPlace(BlockPlaceEvent e) { BukkitBlockEvents.BukkitMCBlockPlaceEvent bpe = new BukkitBlockEvents.BukkitMCBlockPlaceEvent(e); - EventUtils.TriggerExternal(bpe); - EventUtils.TriggerListener(Driver.BLOCK_PLACE, "block_place", bpe); - } - - @EventHandler(priority=EventPriority.LOWEST) - public void onBlockBreak(BlockBreakEvent e){ + EventUtils.TriggerListener(Driver.BLOCK_PLACE, "block_place", bpe); + } + + private static boolean ignorebreak = false; + + @EventHandler(priority = EventPriority.LOWEST) + public void onBlockBreak(BlockBreakEvent e) { + if(ignorebreak) { + return; + } BukkitBlockEvents.BukkitMCBlockBreakEvent bbe = new BukkitBlockEvents.BukkitMCBlockBreakEvent(e); - EventUtils.TriggerExternal(bbe); - EventUtils.TriggerListener(Driver.BLOCK_BREAK, "block_break", bbe); - } + EventUtils.TriggerListener(Driver.BLOCK_BREAK, "block_break", bbe); + if(bbe.isModified() && !e.isCancelled()) { + if(bbe.getDrops().isEmpty()) { + e.setDropItems(false); + return; + } + e.setCancelled(true); + // If we've modified the drops, create a new event for other plugins (eg. block loggers, region protection) + BlockBreakEvent chevent = new BlockBreakEvent(e.getBlock(), e.getPlayer()); + chevent.setExpToDrop(bbe.getExpToDrop()); + PluginManager manager = Bukkit.getServer().getPluginManager(); + ignorebreak = true; + try { + manager.callEvent(chevent); + } finally { + ignorebreak = false; + } + if(!chevent.isCancelled()) { + Block block = chevent.getBlock(); + block.setType(Material.AIR); + Location loc = block.getLocation(); + loc.subtract(0.0, 0.125, 0.0); + if(chevent.isDropItems()) { + for(MCItemStack item : bbe.getDrops()) { + block.getWorld().dropItemNaturally(loc, (ItemStack) item.getHandle()); + } + } + int amt = chevent.getExpToDrop(); + if(amt > 0) { + ExperienceOrb exp = (ExperienceOrb) block.getWorld().spawnEntity(loc, EntityType.EXPERIENCE_ORB); + exp.setExperience(amt); + } + } + } + } @EventHandler(priority = EventPriority.LOWEST) public void onBlockDispense(BlockDispenseEvent e) { BukkitBlockEvents.BukkitMCBlockDispenseEvent bde = new BukkitBlockEvents.BukkitMCBlockDispenseEvent(e); - EventUtils.TriggerExternal(bde); EventUtils.TriggerListener(Driver.BLOCK_DISPENSE, "block_dispense", bde); } - - @EventHandler(priority=EventPriority.LOWEST) - public void onBlockBurn(BlockBurnEvent e){ + + @EventHandler(priority = EventPriority.LOWEST) + public void onBlockBurn(BlockBurnEvent e) { BukkitBlockEvents.BukkitMCBlockBurnEvent bbe = new BukkitBlockEvents.BukkitMCBlockBurnEvent(e); - EventUtils.TriggerExternal(bbe); EventUtils.TriggerListener(Driver.BLOCK_BURN, "block_burn", bbe); } + @EventHandler(priority = EventPriority.LOWEST) + public void onBlockFromTo(BlockFromToEvent e) { + BukkitBlockEvents.BukkitMCBlockFromToEvent bbe = new BukkitBlockEvents.BukkitMCBlockFromToEvent(e); + EventUtils.TriggerListener(Driver.BLOCK_FROM_TO, "block_from_to", bbe); + } + @EventHandler(priority = EventPriority.LOWEST) public void onBlockIgnite(BlockIgniteEvent e) { BukkitBlockEvents.BukkitMCBlockIgniteEvent bie = new BukkitBlockEvents.BukkitMCBlockIgniteEvent(e); - EventUtils.TriggerExternal(bie); EventUtils.TriggerListener(Driver.BLOCK_IGNITE, "block_ignite", bie); } @EventHandler(priority = EventPriority.LOWEST) public void onBlockGrow(BlockGrowEvent e) { BukkitBlockEvents.BukkitMCBlockGrowEvent bge = new BukkitBlockEvents.BukkitMCBlockGrowEvent(e); - EventUtils.TriggerExternal(bge); EventUtils.TriggerListener(Driver.BLOCK_GROW, "block_grow", bge); } + + @EventHandler(priority = EventPriority.LOWEST) + public void onNotePlay(NotePlayEvent e) { + BukkitBlockEvents.BukkitMCNotePlayEvent npe = new BukkitBlockEvents.BukkitMCNotePlayEvent(e); + EventUtils.TriggerListener(Driver.NOTE_PLAY, "note_play", npe); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onBlockFade(BlockFadeEvent e) { + BukkitBlockEvents.BukkitMCBlockFadeEvent bfe = new BukkitBlockEvents.BukkitMCBlockFadeEvent(e); + EventUtils.TriggerListener(Driver.BLOCK_FADE, "block_fade", bfe); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onBlockExplode(BlockExplodeEvent e) { + BukkitBlockEvents.BukkitMCBlockExplodeEvent bee = new BukkitBlockEvents.BukkitMCBlockExplodeEvent(e); + EventUtils.TriggerListener(Driver.BLOCK_EXPLODE, "block_explode", bee); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onBlockForm(BlockFormEvent event) { + BukkitBlockEvents.BukkitBlockFormEventEvent form = new BukkitBlockEvents.BukkitBlockFormEventEvent(event); + EventUtils.TriggerListener(Driver.BLOCK_FORM, "block_form", form); + } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/events/drivers/BukkitEntityListener.java b/src/main/java/com/laytonsmith/abstraction/bukkit/events/drivers/BukkitEntityListener.java index d83c907f0d..b0f649b2da 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/events/drivers/BukkitEntityListener.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/events/drivers/BukkitEntityListener.java @@ -1,10 +1,37 @@ package com.laytonsmith.abstraction.bukkit.events.drivers; import com.laytonsmith.abstraction.MCPlayer; -import com.laytonsmith.abstraction.bukkit.events.BukkitEntityEvents; +import com.laytonsmith.abstraction.bukkit.events.BukkitEntityEvents.BukkitMCCreatureSpawnEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitEntityEvents.BukkitMCEntityChangeBlockEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitEntityEvents.BukkitMCEntityDamageEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitEntityEvents.BukkitMCEntityDamageByEntityEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitEntityEvents.BukkitMCEntityDeathEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitEntityEvents.BukkitMCEntityEnterPortalEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitEntityEvents.BukkitMCEntityExplodeEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitEntityEvents.BukkitMCEntityInteractEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitEntityEvents.BukkitMCEntityPortalEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitEntityEvents.BukkitMCEntityRegainHealthEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitEntityEvents.BukkitMCEntityToggleGlideEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitEntityEvents.BukkitMCEntityToggleSwimEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitEntityEvents.BukkitMCEntityUnleashEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitEntityEvents.BukkitMCFireworkExplodeEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitEntityEvents.BukkitMCHangingBreakEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitEntityEvents.BukkitMCHangingPlaceEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitEntityEvents.BukkitMCItemDespawnEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitEntityEvents.BukkitMCItemSpawnEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitEntityEvents.BukkitMCPlayerDropItemEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitEntityEvents.BukkitMCPlayerInteractAtEntityEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitEntityEvents.BukkitMCPlayerInteractEntityEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitEntityEvents.BukkitMCPlayerPickupItemEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitEntityEvents.BukkitMCPotionSplashEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitEntityEvents.BukkitMCProjectileHitEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitEntityEvents.BukkitMCProjectileLaunchEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitEntityEvents.BukkitMCTargetEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitEntityEvents.BukkitEntityPotionEffectEvent; import com.laytonsmith.abstraction.bukkit.events.BukkitPlayerEvents; import com.laytonsmith.core.events.Driver; import com.laytonsmith.core.events.EventUtils; +import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; @@ -13,138 +40,207 @@ import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.EntityEvent; import org.bukkit.event.entity.EntityExplodeEvent; +import org.bukkit.event.entity.EntityInteractEvent; +import org.bukkit.event.entity.EntityPickupItemEvent; import org.bukkit.event.entity.EntityPortalEnterEvent; +import org.bukkit.event.entity.EntityPortalEvent; +import org.bukkit.event.entity.EntityRegainHealthEvent; import org.bukkit.event.entity.EntityTargetEvent; +import org.bukkit.event.entity.EntityToggleGlideEvent; +import org.bukkit.event.entity.EntityToggleSwimEvent; +import org.bukkit.event.entity.EntityUnleashEvent; +import org.bukkit.event.entity.EntityPotionEffectEvent; +import org.bukkit.event.entity.FireworkExplodeEvent; +import org.bukkit.event.entity.ItemDespawnEvent; import org.bukkit.event.entity.ItemSpawnEvent; import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.entity.PotionSplashEvent; import org.bukkit.event.entity.ProjectileHitEvent; import org.bukkit.event.entity.ProjectileLaunchEvent; import org.bukkit.event.hanging.HangingBreakEvent; +import org.bukkit.event.hanging.HangingPlaceEvent; import org.bukkit.event.player.PlayerDropItemEvent; +import org.bukkit.event.player.PlayerInteractAtEntityEvent; import org.bukkit.event.player.PlayerInteractEntityEvent; -import org.bukkit.event.player.PlayerPickupItemEvent; -/** - * - * - */ -public class BukkitEntityListener implements Listener{ +public class BukkitEntityListener implements Listener { - @EventHandler(priority=EventPriority.LOWEST) + @EventHandler(priority = EventPriority.LOWEST) public void onSpawn(CreatureSpawnEvent event) { - BukkitEntityEvents.BukkitMCCreatureSpawnEvent cse = new BukkitEntityEvents.BukkitMCCreatureSpawnEvent(event); - EventUtils.TriggerExternal(cse); + BukkitMCCreatureSpawnEvent cse = new BukkitMCCreatureSpawnEvent(event); EventUtils.TriggerListener(Driver.CREATURE_SPAWN, "creature_spawn", cse); } - - @EventHandler(priority=EventPriority.LOWEST) + + @EventHandler(priority = EventPriority.LOWEST) public void onClickEnt(PlayerInteractEntityEvent event) { - BukkitEntityEvents.BukkitMCPlayerInteractEntityEvent piee = new BukkitEntityEvents.BukkitMCPlayerInteractEntityEvent(event); - EventUtils.TriggerExternal(piee); + BukkitMCPlayerInteractEntityEvent piee = new BukkitMCPlayerInteractEntityEvent(event); EventUtils.TriggerListener(Driver.PLAYER_INTERACT_ENTITY, "player_interact_entity", piee); } - - @EventHandler(priority=EventPriority.LOWEST) - public void onItemDrop(PlayerDropItemEvent event) { - BukkitEntityEvents.BukkitMCPlayerDropItemEvent pdie = new BukkitEntityEvents.BukkitMCPlayerDropItemEvent(event); - EventUtils.TriggerExternal(pdie); + + @EventHandler(priority = EventPriority.LOWEST) + public void onClickAtEnt(PlayerInteractAtEntityEvent event) { + BukkitMCPlayerInteractAtEntityEvent piaee = new BukkitMCPlayerInteractAtEntityEvent(event); + EventUtils.TriggerListener(Driver.PLAYER_INTERACT_AT_ENTITY, "player_interact_at_entity", piaee); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onItemDrop(PlayerDropItemEvent event) { + BukkitMCPlayerDropItemEvent pdie = new BukkitMCPlayerDropItemEvent(event); EventUtils.TriggerListener(Driver.ITEM_DROP, "item_drop", pdie); - } - - @EventHandler(priority=EventPriority.LOWEST) - public void onItemPickup(PlayerPickupItemEvent event) { - BukkitEntityEvents.BukkitMCPlayerPickupItemEvent ppie = new BukkitEntityEvents.BukkitMCPlayerPickupItemEvent(event); - EventUtils.TriggerExternal(ppie); - EventUtils.TriggerListener(Driver.ITEM_PICKUP, "item_pickup", ppie); - } - - @EventHandler(priority=EventPriority.LOWEST) + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onItemPickup(EntityPickupItemEvent event) { + if(((EntityEvent) event).getEntity() instanceof Player) { + BukkitMCPlayerPickupItemEvent ppie = new BukkitMCPlayerPickupItemEvent(event); + EventUtils.TriggerListener(Driver.ITEM_PICKUP, "item_pickup", ppie); + } + } + + @EventHandler(priority = EventPriority.LOWEST) public void onEntityDeath(EntityDeathEvent event) { - BukkitEntityEvents.BukkitMCEntityDeathEvent ede; - if (event instanceof PlayerDeathEvent) { - ede = new BukkitPlayerEvents.BukkitMCPlayerDeathEvent((PlayerDeathEvent) event); + BukkitMCEntityDeathEvent ede; + if(event instanceof PlayerDeathEvent) { + ede = new BukkitPlayerEvents.BukkitMCPlayerDeathEvent(event); } else { - ede = new BukkitEntityEvents.BukkitMCEntityDeathEvent(event); + ede = new BukkitMCEntityDeathEvent(event); } - EventUtils.TriggerExternal(ede); EventUtils.TriggerListener(Driver.ENTITY_DEATH, "entity_death", ede); - if (event instanceof PlayerDeathEvent) { + if(event instanceof PlayerDeathEvent) { EventUtils.TriggerListener(Driver.PLAYER_DEATH, "player_death", ede); } } - - @EventHandler(priority=EventPriority.LOWEST) - public void onTargetLiving(EntityTargetEvent event) { - BukkitEntityEvents.BukkitMCTargetEvent ete = new BukkitEntityEvents.BukkitMCTargetEvent(event); - EventUtils.TriggerExternal(ete); - EventUtils.TriggerListener(Driver.TARGET_ENTITY, "target_player", ete); - } - - @EventHandler(priority=EventPriority.LOWEST) + + @EventHandler(priority = EventPriority.LOWEST) + public void onTargetLiving(EntityTargetEvent event) { + if(event.getTarget() instanceof Player) { + BukkitMCTargetEvent ete = new BukkitMCTargetEvent(event); + EventUtils.TriggerListener(Driver.TARGET_ENTITY, "target_player", ete); + } + } + + @EventHandler(priority = EventPriority.LOWEST) public void onEntityDamage(EntityDamageEvent event) { - BukkitEntityEvents.BukkitMCEntityDamageEvent ede; - if (event instanceof EntityDamageByEntityEvent) { - ede = new BukkitEntityEvents.BukkitMCEntityDamageByEntityEvent((EntityDamageByEntityEvent) event); - EventUtils.TriggerExternal(ede); + BukkitMCEntityDamageEvent ede; + if(event instanceof EntityDamageByEntityEvent) { + ede = new BukkitMCEntityDamageByEntityEvent(event); EventUtils.TriggerListener(Driver.ENTITY_DAMAGE, "entity_damage", ede); - if (ede.getEntity() instanceof MCPlayer) { + if(ede.getEntity() instanceof MCPlayer) { EventUtils.TriggerListener(Driver.ENTITY_DAMAGE_PLAYER, "entity_damage_player", ede); } } else { - ede = new BukkitEntityEvents.BukkitMCEntityDamageEvent(event); - EventUtils.TriggerExternal(ede); + ede = new BukkitMCEntityDamageEvent(event); EventUtils.TriggerListener(Driver.ENTITY_DAMAGE, "entity_damage", ede); } } - @EventHandler(priority=EventPriority.LOWEST) + @EventHandler(priority = EventPriority.LOWEST) public void onPHit(ProjectileHitEvent event) { - BukkitEntityEvents.BukkitMCProjectileHitEvent phe = - new BukkitEntityEvents.BukkitMCProjectileHitEvent(event); - EventUtils.TriggerExternal(phe); + BukkitMCProjectileHitEvent phe = new BukkitMCProjectileHitEvent(event); EventUtils.TriggerListener(Driver.PROJECTILE_HIT, "projectile_hit", phe); } - @EventHandler(priority=EventPriority.LOWEST) + @EventHandler(priority = EventPriority.LOWEST) public void onProjectileLaunch(ProjectileLaunchEvent event) { - BukkitEntityEvents.BukkitMCProjectileLaunchEvent ple = new BukkitEntityEvents.BukkitMCProjectileLaunchEvent(event); - EventUtils.TriggerExternal(ple); + BukkitMCProjectileLaunchEvent ple = new BukkitMCProjectileLaunchEvent(event); EventUtils.TriggerListener(Driver.PROJECTILE_LAUNCH, "projectile_launch", ple); } - @EventHandler(priority= EventPriority.LOWEST) + @EventHandler(priority = EventPriority.LOWEST) public void onPortalEnter(EntityPortalEnterEvent event) { - BukkitEntityEvents.BukkitMCEntityEnterPortalEvent pe = new BukkitEntityEvents.BukkitMCEntityEnterPortalEvent(event); - EventUtils.TriggerExternal(pe); + BukkitMCEntityEnterPortalEvent pe = new BukkitMCEntityEnterPortalEvent(event); EventUtils.TriggerListener(Driver.ENTITY_ENTER_PORTAL, "entity_enter_portal", pe); } - - @EventHandler(priority= EventPriority.LOWEST) + + @EventHandler(priority = EventPriority.LOWEST) public void onExplode(EntityExplodeEvent event) { - BukkitEntityEvents.BukkitMCEntityExplodeEvent ee = new BukkitEntityEvents.BukkitMCEntityExplodeEvent(event); - EventUtils.TriggerExternal(ee); + BukkitMCEntityExplodeEvent ee = new BukkitMCEntityExplodeEvent(event); EventUtils.TriggerListener(Driver.ENTITY_EXPLODE, "entity_explode", ee); } - + + @EventHandler(priority = EventPriority.LOWEST) + public void onItemDespawn(ItemDespawnEvent event) { + BukkitMCItemDespawnEvent id = new BukkitMCItemDespawnEvent(event); + EventUtils.TriggerListener(Driver.ITEM_DESPAWN, "item_despawn", id); + } + @EventHandler(priority = EventPriority.LOWEST) public void onItemSpawn(ItemSpawnEvent event) { - BukkitEntityEvents.BukkitMCItemSpawnEvent is = new BukkitEntityEvents.BukkitMCItemSpawnEvent(event); - EventUtils.TriggerExternal(is); + BukkitMCItemSpawnEvent is = new BukkitMCItemSpawnEvent(event); EventUtils.TriggerListener(Driver.ITEM_SPAWN, "item_spawn", is); } @EventHandler(priority = EventPriority.LOWEST) public void onChangeBlock(EntityChangeBlockEvent event) { - BukkitEntityEvents.BukkitMCEntityChangeBlockEvent ecbe = new BukkitEntityEvents.BukkitMCEntityChangeBlockEvent(event); - EventUtils.TriggerExternal(ecbe); + BukkitMCEntityChangeBlockEvent ecbe = new BukkitMCEntityChangeBlockEvent(event); EventUtils.TriggerListener(Driver.ENTITY_CHANGE_BLOCK, "entity_change_block", ecbe); } + @EventHandler(priority = EventPriority.LOWEST) + public void onInteract(EntityInteractEvent event) { + BukkitMCEntityInteractEvent eie = new BukkitMCEntityInteractEvent(event); + EventUtils.TriggerListener(Driver.ENTITY_INTERACT, "entity_interact", eie); + } + @EventHandler(priority = EventPriority.LOWEST) public void onHangingBreak(HangingBreakEvent event) { - BukkitEntityEvents.BukkitMCHangingBreakEvent hbe = new BukkitEntityEvents.BukkitMCHangingBreakEvent(event); - EventUtils.TriggerExternal(hbe); + BukkitMCHangingBreakEvent hbe = new BukkitMCHangingBreakEvent(event); EventUtils.TriggerListener(Driver.HANGING_BREAK, "hanging_break", hbe); } + + @EventHandler(priority = EventPriority.LOWEST) + public void onHangingPlace(HangingPlaceEvent event) { + BukkitMCHangingPlaceEvent hpe = new BukkitMCHangingPlaceEvent(event); + EventUtils.TriggerListener(Driver.HANGING_PLACE, "hanging_place", hpe); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onEntityToggleGlide(EntityToggleGlideEvent event) { + BukkitMCEntityToggleGlideEvent etge = new BukkitMCEntityToggleGlideEvent(event); + EventUtils.TriggerListener(Driver.ENTITY_TOGGLE_GLIDE, "entity_toggle_glide", etge); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onEntityToggleSwim(EntityToggleSwimEvent event) { + BukkitMCEntityToggleSwimEvent etse = new BukkitMCEntityToggleSwimEvent(event); + EventUtils.TriggerListener(Driver.ENTITY_TOGGLE_SWIM, "entity_toggle_swim", etse); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onFireworkExplode(FireworkExplodeEvent event) { + BukkitMCFireworkExplodeEvent fee = new BukkitMCFireworkExplodeEvent(event); + EventUtils.TriggerListener(Driver.FIREWORK_EXPLODE, "firework_explode", fee); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onRegainHealth(EntityRegainHealthEvent event) { + BukkitMCEntityRegainHealthEvent erhe = new BukkitMCEntityRegainHealthEvent(event); + EventUtils.TriggerListener(Driver.ENTITY_REGAIN_HEALTH, "entity_regain_health", erhe); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onPortalTravel(EntityPortalEvent event) { + BukkitMCEntityPortalEvent epe = new BukkitMCEntityPortalEvent(event); + EventUtils.TriggerListener(Driver.ENTITY_PORTAL_TRAVEL, "entity_portal_travel", epe); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onEntityUnleash(EntityUnleashEvent event) { + BukkitMCEntityUnleashEvent epe = new BukkitMCEntityUnleashEvent(event); + EventUtils.TriggerListener(Driver.ENTITY_UNLEASH, "entity_unleash", epe); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onPotionSplash(PotionSplashEvent event) { + BukkitMCPotionSplashEvent pse = new BukkitMCPotionSplashEvent(event); + EventUtils.TriggerListener(Driver.POTION_SPLASH, "potion_splash", pse); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onEntityPotionEffectEvent(EntityPotionEffectEvent event) { + BukkitEntityPotionEffectEvent potion = new BukkitEntityPotionEffectEvent(event); + EventUtils.TriggerListener(Driver.ENTITY_POTION_EFFECT, "entity_potion", potion); + } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/events/drivers/BukkitInventoryListener.java b/src/main/java/com/laytonsmith/abstraction/bukkit/events/drivers/BukkitInventoryListener.java index 468c20fd81..fdd54e9527 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/events/drivers/BukkitInventoryListener.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/events/drivers/BukkitInventoryListener.java @@ -7,10 +7,16 @@ import com.laytonsmith.abstraction.bukkit.events.BukkitInventoryEvents.BukkitMCInventoryDragEvent; import com.laytonsmith.abstraction.bukkit.events.BukkitInventoryEvents.BukkitMCInventoryOpenEvent; import com.laytonsmith.abstraction.bukkit.events.BukkitInventoryEvents.BukkitMCItemHeldEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitInventoryEvents.BukkitMCItemSwapEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitInventoryEvents.BukkitMCPrepareAnvilEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitInventoryEvents.BukkitMCPrepareGrindstoneEvent; import com.laytonsmith.abstraction.bukkit.events.BukkitInventoryEvents.BukkitMCPrepareItemCraftEvent; import com.laytonsmith.abstraction.bukkit.events.BukkitInventoryEvents.BukkitMCPrepareItemEnchantEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitInventoryEvents.BukkitMCPrepareSmithingEvent; +import com.laytonsmith.annotations.EventIdentifier; import com.laytonsmith.core.events.Driver; import com.laytonsmith.core.events.EventUtils; +import org.bukkit.event.Event; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; @@ -20,68 +26,84 @@ import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.inventory.InventoryDragEvent; import org.bukkit.event.inventory.InventoryOpenEvent; +import org.bukkit.event.inventory.PrepareAnvilEvent; +import org.bukkit.event.inventory.PrepareGrindstoneEvent; import org.bukkit.event.inventory.PrepareItemCraftEvent; +import org.bukkit.event.inventory.PrepareSmithingEvent; import org.bukkit.event.player.PlayerItemHeldEvent; +import org.bukkit.event.player.PlayerSwapHandItemsEvent; -/** - * - * - */ -public class BukkitInventoryListener implements Listener{ - - @EventHandler(priority=EventPriority.LOWEST) +public class BukkitInventoryListener implements Listener { + + @EventHandler(priority = EventPriority.LOWEST) public void onInvClick(InventoryClickEvent event) { BukkitMCInventoryClickEvent ice = new BukkitInventoryEvents.BukkitMCInventoryClickEvent(event); - EventUtils.TriggerExternal(ice); EventUtils.TriggerListener(Driver.INVENTORY_CLICK, "inventory_click", ice); } - @EventHandler(priority=EventPriority.LOWEST) + @EventHandler(priority = EventPriority.LOWEST) public void onInvDrag(InventoryDragEvent event) { BukkitMCInventoryDragEvent ide = new BukkitInventoryEvents.BukkitMCInventoryDragEvent(event); - EventUtils.TriggerExternal(ide); EventUtils.TriggerListener(Driver.INVENTORY_DRAG, "inventory_drag", ide); } - - @EventHandler(priority=EventPriority.LOWEST) + + @EventHandler(priority = EventPriority.LOWEST) public void onInvOpen(InventoryOpenEvent event) { BukkitMCInventoryOpenEvent ioe = new BukkitInventoryEvents.BukkitMCInventoryOpenEvent(event); - EventUtils.TriggerExternal(ioe); EventUtils.TriggerListener(Driver.INVENTORY_OPEN, "inventory_open", ioe); } - - @EventHandler(priority=EventPriority.LOWEST) + + @EventHandler(priority = EventPriority.LOWEST) public void onInvClose(InventoryCloseEvent event) { BukkitMCInventoryCloseEvent ice = new BukkitInventoryEvents.BukkitMCInventoryCloseEvent(event); - EventUtils.TriggerExternal(ice); EventUtils.TriggerListener(Driver.INVENTORY_CLOSE, "inventory_close", ice); } - - @EventHandler(priority= EventPriority.LOWEST) + + @EventHandler(priority = EventPriority.LOWEST) public void onItemEnchant(EnchantItemEvent event) { BukkitMCEnchantItemEvent eie = new BukkitInventoryEvents.BukkitMCEnchantItemEvent(event); - EventUtils.TriggerExternal(eie); EventUtils.TriggerListener(Driver.ITEM_ENCHANT, "item_enchant", eie); } - - @EventHandler(priority= EventPriority.LOWEST) + + @EventHandler(priority = EventPriority.LOWEST) public void onPreEnchant(PrepareItemEnchantEvent event) { BukkitMCPrepareItemEnchantEvent pie = new BukkitInventoryEvents.BukkitMCPrepareItemEnchantEvent(event); - EventUtils.TriggerExternal(pie); EventUtils.TriggerListener(Driver.ITEM_PRE_ENCHANT, "item_pre_enchant", pie); } - - @EventHandler(priority=EventPriority.LOWEST) + + @EventHandler(priority = EventPriority.LOWEST) public void onItemHeld(PlayerItemHeldEvent event) { BukkitMCItemHeldEvent ih = new BukkitInventoryEvents.BukkitMCItemHeldEvent(event); - EventUtils.TriggerExternal(ih); EventUtils.TriggerListener(Driver.ITEM_HELD, "item_held", ih); } - - @EventHandler(priority=EventPriority.LOWEST) + + @EventHandler(priority = EventPriority.LOWEST) + public void onItemSwap(PlayerSwapHandItemsEvent event) { + BukkitMCItemSwapEvent is = new BukkitInventoryEvents.BukkitMCItemSwapEvent(event); + EventUtils.TriggerListener(Driver.ITEM_SWAP, "item_swap", is); + } + + @EventHandler(priority = EventPriority.LOWEST) public void onPreCraft(PrepareItemCraftEvent event) { BukkitMCPrepareItemCraftEvent pc = new BukkitInventoryEvents.BukkitMCPrepareItemCraftEvent(event); - EventUtils.TriggerExternal(pc); EventUtils.TriggerListener(Driver.ITEM_PRE_CRAFT, "item_pre_craft", pc); } + + @EventHandler(priority = EventPriority.LOWEST) + public void onPreAnvil(PrepareAnvilEvent event) { + BukkitMCPrepareAnvilEvent pa = new BukkitInventoryEvents.BukkitMCPrepareAnvilEvent(event); + EventUtils.TriggerListener(Driver.ITEM_PRE_ANVIL, "item_pre_anvil", pa); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onPreSmithing(PrepareSmithingEvent event) { + BukkitMCPrepareSmithingEvent ps = new BukkitInventoryEvents.BukkitMCPrepareSmithingEvent(event); + EventUtils.TriggerListener(Driver.ITEM_PRE_SMITHING, "item_pre_smithing", ps); + } + + @EventIdentifier(event = Driver.ITEM_PRE_GRINDSTONE, className = "org.bukkit.event.inventory.PrepareGrindstoneEvent") + public void onPreGrindstone(Event event) { + BukkitMCPrepareGrindstoneEvent pg = new BukkitInventoryEvents.BukkitMCPrepareGrindstoneEvent((PrepareGrindstoneEvent) event); + EventUtils.TriggerListener(Driver.ITEM_PRE_GRINDSTONE, "item_pre_grindstone", pg); + } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/events/drivers/BukkitPlayerListener.java b/src/main/java/com/laytonsmith/abstraction/bukkit/events/drivers/BukkitPlayerListener.java index 481ec14612..2fe2cce254 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/events/drivers/BukkitPlayerListener.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/events/drivers/BukkitPlayerListener.java @@ -1,26 +1,56 @@ package com.laytonsmith.abstraction.bukkit.events.drivers; -import com.laytonsmith.abstraction.bukkit.BukkitMCPlayer; -import com.laytonsmith.abstraction.bukkit.events.BukkitPlayerEvents; +import com.laytonsmith.abstraction.MCLocation; +import com.laytonsmith.abstraction.bukkit.BukkitMCLocation; +import com.laytonsmith.abstraction.bukkit.BukkitMCServer; +import com.laytonsmith.abstraction.bukkit.events.BukkitPlayerEvents.BukkitMCExpChangeEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitPlayerEvents.BukkitMCFoodLevelChangeEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitPlayerEvents.BukkitMCGamemodeChangeEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitPlayerEvents.BukkitMCPlayerAdvancementDoneEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitPlayerEvents.BukkitMCPlayerBucketEmptyEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitPlayerEvents.BukkitMCPlayerBucketFillEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitPlayerEvents.BukkitMCPlayerEnterBedEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitPlayerEvents.BukkitMCPlayerLeaveBedEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitPlayerEvents.BukkitMCPlayerChatEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitPlayerEvents.BukkitMCPlayerEditBookEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitPlayerEvents.BukkitMCPlayerFishEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitPlayerEvents.BukkitMCPlayerInteractEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitPlayerEvents.BukkitMCPlayerItemConsumeEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitPlayerEvents.BukkitMCPlayerJoinEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitPlayerEvents.BukkitMCPlayerKickEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitPlayerEvents.BukkitMCPlayerLoginEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitPlayerEvents.BukkitMCPlayerMoveEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitPlayerEvents.BukkitMCPlayerPortalEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitPlayerEvents.BukkitMCPlayerQuitEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitPlayerEvents.BukkitMCPlayerRespawnEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitPlayerEvents.BukkitMCPlayerResourcePackEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitPlayerEvents.BukkitMCPlayerStopUsingItemEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitPlayerEvents.BukkitMCPlayerTeleportEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitPlayerEvents.BukkitMCPlayerToggleFlightEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitPlayerEvents.BukkitMCPlayerToggleSneakEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitPlayerEvents.BukkitMCPlayerToggleSprintEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitPlayerEvents.BukkitMCWorldChangedEvent; +import com.laytonsmith.annotations.EventIdentifier; import com.laytonsmith.commandhelper.CommandHelperPlugin; -import com.laytonsmith.core.Static; -import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.events.Driver; import com.laytonsmith.core.events.EventUtils; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.logging.Level; -import java.util.logging.Logger; +import com.laytonsmith.core.events.drivers.PlayerEvents; +import io.papermc.paper.event.player.PlayerStopUsingItemEvent; import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.event.Event; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.entity.FoodLevelChangeEvent; import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.event.player.PlayerAdvancementDoneEvent; import org.bukkit.event.player.PlayerBedEnterEvent; import org.bukkit.event.player.PlayerBedLeaveEvent; +import org.bukkit.event.player.PlayerBucketEmptyEvent; +import org.bukkit.event.player.PlayerBucketFillEvent; import org.bukkit.event.player.PlayerChangedWorldEvent; -import org.bukkit.event.player.PlayerChatTabCompleteEvent; import org.bukkit.event.player.PlayerEditBookEvent; import org.bukkit.event.player.PlayerExpChangeEvent; import org.bukkit.event.player.PlayerFishEvent; @@ -30,99 +60,100 @@ import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerKickEvent; import org.bukkit.event.player.PlayerLoginEvent; +import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerPortalEvent; -import org.bukkit.event.player.PlayerPreLoginEvent; import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerResourcePackStatusEvent; import org.bukkit.event.player.PlayerRespawnEvent; import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.event.player.PlayerToggleFlightEvent; import org.bukkit.event.player.PlayerToggleSneakEvent; import org.bukkit.event.player.PlayerToggleSprintEvent; -/** - * - * - */ +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.logging.Level; +import java.util.logging.Logger; + public class BukkitPlayerListener implements Listener { - @EventHandler(priority = EventPriority.LOWEST) - public void onPlayerKick(PlayerKickEvent e) { - BukkitPlayerEvents.BukkitMCPlayerKickEvent pke = new BukkitPlayerEvents.BukkitMCPlayerKickEvent(e); - //EventUtils.TriggerExternal(pke); - EventUtils.TriggerListener(Driver.PLAYER_KICK, "player_kick", pke); - } - @EventHandler(priority = EventPriority.LOWEST) - public void onPlayerBedEnter(PlayerBedEnterEvent e) { - BukkitPlayerEvents.BukkitMCPlayerBedEvent be = new BukkitPlayerEvents.BukkitMCPlayerBedEvent(e); - //EventUtils.TriggerExternal(be); - EventUtils.TriggerListener(Driver.PLAYER_BED_EVENT, "player_enter_bed", be); - } - + public void onFoodLevelChange(FoodLevelChangeEvent e) { + BukkitMCFoodLevelChangeEvent pke = new BukkitMCFoodLevelChangeEvent(e); + EventUtils.TriggerListener(Driver.FOOD_LEVEL_CHANGED, "food_level_changed", pke); + } + @EventHandler(priority = EventPriority.LOWEST) - public void onPlayerBedLeave(PlayerBedLeaveEvent e) { - BukkitPlayerEvents.BukkitMCPlayerBedEvent be = new BukkitPlayerEvents.BukkitMCPlayerBedEvent(e); - //EventUtils.TriggerExternal(be); - EventUtils.TriggerListener(Driver.PLAYER_BED_EVENT, "player_leave_bed", be); - } - + public void onPlayerKick(PlayerKickEvent e) { + BukkitMCPlayerKickEvent pke = new BukkitMCPlayerKickEvent(e); + EventUtils.TriggerListener(Driver.PLAYER_KICK, "player_kick", pke); + } + @EventHandler(priority = EventPriority.LOWEST) - public void onPlayerLogin(PlayerLoginEvent e) { - BukkitPlayerEvents.BukkitMCPlayerLoginEvent ple = new BukkitPlayerEvents.BukkitMCPlayerLoginEvent(e); - //EventUtils.TriggerExternal(ple); - EventUtils.TriggerListener(Driver.PLAYER_LOGIN, "player_login", ple); + public void onPlayerBedEnter(PlayerBedEnterEvent e) { + BukkitMCPlayerEnterBedEvent be = new BukkitMCPlayerEnterBedEvent(e); + EventUtils.TriggerListener(Driver.PLAYER_ENTER_BED, "player_enter_bed", be); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onPlayerBedLeave(PlayerBedLeaveEvent e) { + BukkitMCPlayerLeaveBedEvent be = new BukkitMCPlayerLeaveBedEvent(e); + EventUtils.TriggerListener(Driver.PLAYER_LEAVE_BED, "player_leave_bed", be); } @EventHandler(priority = EventPriority.LOWEST) - public void onPlayerPreLogin(PlayerPreLoginEvent e) { - BukkitPlayerEvents.BukkitMCPlayerPreLoginEvent pple = new BukkitPlayerEvents.BukkitMCPlayerPreLoginEvent(e); - //EventUtils.TriggerExternal(pple); - EventUtils.TriggerListener(Driver.PLAYER_PRELOGIN, "player_prelogin", pple); + public void onPlayerLogin(PlayerLoginEvent e) { + BukkitMCPlayerLoginEvent ple = new BukkitMCPlayerLoginEvent(e); + EventUtils.TriggerListener(Driver.PLAYER_LOGIN, "player_login", ple); + if(e.getResult() == PlayerLoginEvent.Result.ALLOWED) { + // store UUID for fallback player lookups by name + BukkitMCServer server = (BukkitMCServer) CommandHelperPlugin.myServer; + server.playerUUIDsByName.put(e.getPlayer().getName(), e.getPlayer().getUniqueId()); + } } @EventHandler(priority = EventPriority.LOWEST) public void onPlayerJoin(PlayerJoinEvent e) { - BukkitPlayerEvents.BukkitMCPlayerJoinEvent pje = new BukkitPlayerEvents.BukkitMCPlayerJoinEvent(e); - //EventUtils.TriggerExternal(pje); + BukkitMCPlayerJoinEvent pje = new BukkitMCPlayerJoinEvent(e); EventUtils.TriggerListener(Driver.PLAYER_JOIN, "player_join", pje); } @EventHandler(priority = EventPriority.LOWEST) public void onPlayerInteract(PlayerInteractEvent e) { - BukkitPlayerEvents.BukkitMCPlayerInteractEvent pie = new BukkitPlayerEvents.BukkitMCPlayerInteractEvent(e); - //EventUtils.TriggerExternal(pie); - EventUtils.TriggerListener(Driver.PLAYER_INTERACT, "player_interact", pie); - EventUtils.TriggerListener(Driver.PLAYER_INTERACT, "pressure_plate_activated", pie); + BukkitMCPlayerInteractEvent pie = new BukkitMCPlayerInteractEvent(e); + if(e.getAction().equals(Action.PHYSICAL)) { + EventUtils.TriggerListener(Driver.PLAYER_INTERACT, "pressure_plate_activated", pie); + } else { + EventUtils.TriggerListener(Driver.PLAYER_INTERACT, "player_interact", pie); + } } @EventHandler(priority = EventPriority.LOWEST) public void onPlayerRespawn(PlayerRespawnEvent event) { - BukkitPlayerEvents.BukkitMCPlayerRespawnEvent pre = new BukkitPlayerEvents.BukkitMCPlayerRespawnEvent(event); - //EventUtils.TriggerExternal(pre); + BukkitMCPlayerRespawnEvent pre = new BukkitMCPlayerRespawnEvent(event); EventUtils.TriggerListener(Driver.PLAYER_SPAWN, "player_spawn", pre); } - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled=true) + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onPlayerChat(final AsyncPlayerChatEvent event) { - if(CommandHelperPlugin.self.interpreterListener - .isInInterpreterMode(event.getPlayer().getName())){ - //They are in interpreter mode, so we want it to handle this, not everything else. - return; - } - - if(event.isAsynchronous()){ + if(CommandHelperPlugin.self.interpreterListener.isInInterpreterMode(event.getPlayer().getName())) { + //They are in interpreter mode, so we want it to handle this, not everything else. + return; + } + + if(event.isAsynchronous()) { //The async event gets priority, and if cancelled, doesn't trigger a normal player_chat event. - BukkitPlayerEvents.BukkitMCPlayerChatEvent pce = new BukkitPlayerEvents.BukkitMCPlayerChatEvent(event); + BukkitMCPlayerChatEvent pce = new BukkitMCPlayerChatEvent(event); EventUtils.TriggerListener(Driver.PLAYER_CHAT, "async_player_chat", pce); - - if(event.isCancelled()){ + if(event.isCancelled()) { return; } } - - if (EventUtils.GetEvents(Driver.PLAYER_CHAT) != null - && !EventUtils.GetEvents(Driver.PLAYER_CHAT).isEmpty()) { - if (event.isAsynchronous()) { + + if(EventUtils.GetEvents(Driver.PLAYER_CHAT) != null && !EventUtils.GetEvents(Driver.PLAYER_CHAT).isEmpty()) { + if(event.isAsynchronous()) { //We have to do the full processing on the main server thread, and //block on it as well, so if we cancel it or something, the change //will actually take effect. The easiest way to do this is to cancel the @@ -130,10 +161,10 @@ public void onPlayerChat(final AsyncPlayerChatEvent event) { //registering on lowest, this will hopefully not cause any problems, //but if it does, tough. Barring play-dirty mode, there's not a whole //lot that can be done reasonably. - + // SortedSet events = EventUtils.GetEvents(Driver.PLAYER_CHAT); -// Event driver = EventList.getEvent(Driver.PLAYER_CHAT, "player_chat"); -// //Unfortunately, due to priority issues, if any event is syncronous, all of them +// Event driver = EventList.getEvent(Driver.PLAYER_CHAT, "player_chat"); +// //Unfortunately, due to priority issues, if any event is synchronous, all of them // //have to be synchronous. // boolean canBeAsync = true; // boolean actuallyNeedToFire = false; @@ -149,7 +180,7 @@ public void onPlayerChat(final AsyncPlayerChatEvent event) { // if(f.runAsync() != null && f.runAsync() == false){ // //Nope, can't be run async :( // canBeAsync = false; -// } +// } // } // try { // if(driver.matches(b.getPrefilter(), new BukkitPlayerEvents.BukkitMCPlayerChatEvent(event))){ @@ -160,16 +191,15 @@ public void onPlayerChat(final AsyncPlayerChatEvent event) { // //No need to fire this one // } // } -// +// // if(!actuallyNeedToFire){ // //Yay! Prefilters finally actually optimized something! // return; // } - //Until there is a more reliable way to detect isConst() on a parse tree, (that supports procs) //this must always be synchronous. boolean canBeAsync = false; - if(canBeAsync){ + if(canBeAsync) { //Fire away! fireChat(event); } else { @@ -182,11 +212,11 @@ public Object call() throws Exception { onPlayerChat(copy); return null; } - }); - while(true){ + }); + while(true) { try { - f.get(); - break; + f.get(); + break; } catch (InterruptedException ex) { //I don't know why this happens, but screw it, we're gonna try again, and it's gonna like it. } catch (ExecutionException ex) { @@ -206,109 +236,171 @@ public Object call() throws Exception { } private void fireChat(AsyncPlayerChatEvent event) { - BukkitPlayerEvents.BukkitMCPlayerChatEvent pce = new BukkitPlayerEvents.BukkitMCPlayerChatEvent(event); - //EventUtils.TriggerExternal(pce); + BukkitMCPlayerChatEvent pce = new BukkitMCPlayerChatEvent(event); EventUtils.TriggerListener(Driver.PLAYER_CHAT, "player_chat", pce); } @EventHandler(priority = EventPriority.LOWEST) public void onPlayerQuit(PlayerQuitEvent event) { - BukkitPlayerEvents.BukkitMCPlayerQuitEvent pqe = new BukkitPlayerEvents.BukkitMCPlayerQuitEvent(event); - //EventUtils.TriggerExternal(pqe); + BukkitMCPlayerQuitEvent pqe = new BukkitMCPlayerQuitEvent(event); EventUtils.TriggerListener(Driver.PLAYER_QUIT, "player_quit", pqe); + // clear UUID for fallback player lookups by name + ((BukkitMCServer) CommandHelperPlugin.myServer).playerUUIDsByName.remove(event.getPlayer().getName()); } @EventHandler(priority = EventPriority.LOWEST) public void onPlayerChangedWorld(PlayerChangedWorldEvent event) { - BukkitMCPlayer currentPlayer = (BukkitMCPlayer) Static.GetPlayer(event.getPlayer().getName(), Target.UNKNOWN); - //Apparently this happens sometimes, so prevent it - if (!event.getFrom().equals(currentPlayer._Player().getWorld())) { - BukkitPlayerEvents.BukkitMCWorldChangedEvent wce = new BukkitPlayerEvents.BukkitMCWorldChangedEvent(event); - //EventUtils.TriggerExternal(wce); - EventUtils.TriggerListener(Driver.WORLD_CHANGED, "world_changed", wce); + if(event.getFrom().equals(event.getPlayer().getWorld())) { + return; } + + BukkitMCWorldChangedEvent wce = new BukkitMCWorldChangedEvent(event); + EventUtils.TriggerListener(Driver.WORLD_CHANGED, "world_changed", wce); } @EventHandler(priority = EventPriority.LOWEST) public void onPlayerTeleport(PlayerTeleportEvent event) { - if (event.getFrom().equals(event.getTo())) { + if(event.getFrom().equals(event.getTo()) || event.getTo() == null) { return; } - - BukkitPlayerEvents.BukkitMCPlayerTeleportEvent pte = new BukkitPlayerEvents.BukkitMCPlayerTeleportEvent(event); - //EventUtils.TriggerExternal(pte); + + BukkitMCPlayerTeleportEvent pte = new BukkitMCPlayerTeleportEvent(event); EventUtils.TriggerListener(Driver.PLAYER_TELEPORT, "player_teleport", pte); } - @EventHandler(priority = EventPriority.LOWEST) public void onPortalEnter(PlayerPortalEvent event) { - BukkitPlayerEvents.BukkitMCPlayerPortalEvent pe = new BukkitPlayerEvents.BukkitMCPlayerPortalEvent(event); - //EventUtils.TriggerExternal(pe); + BukkitMCPlayerPortalEvent pe = new BukkitMCPlayerPortalEvent(event); EventUtils.TriggerListener(Driver.PLAYER_PORTAL_TRAVEL, "player_portal_travel", pe); } @EventHandler(priority = EventPriority.LOWEST) public void onConsume(PlayerItemConsumeEvent event) { - BukkitPlayerEvents.BukkitMCPlayerItemConsumeEvent pic = - new BukkitPlayerEvents.BukkitMCPlayerItemConsumeEvent(event); - //EventUtils.TriggerExternal(pic); + BukkitMCPlayerItemConsumeEvent pic = new BukkitMCPlayerItemConsumeEvent(event); EventUtils.TriggerListener(Driver.PLAYER_CONSUME, "player_consume", pic); } - - @EventHandler(priority= EventPriority.LOWEST) + + @EventHandler(priority = EventPriority.LOWEST) public void onFish(PlayerFishEvent event) { - BukkitPlayerEvents.BukkitMCPlayerFishEvent fish = new BukkitPlayerEvents.BukkitMCPlayerFishEvent(event); - //EventUtils.TriggerExternal(fish); + BukkitMCPlayerFishEvent fish = new BukkitMCPlayerFishEvent(event); EventUtils.TriggerListener(Driver.PLAYER_FISH, "player_fish", fish); } - - @EventHandler(priority= EventPriority.LOWEST) + + @EventHandler(priority = EventPriority.LOWEST) public void onGamemodeChange(PlayerGameModeChangeEvent event) { - BukkitPlayerEvents.BukkitMCGamemodeChangeEvent e = new BukkitPlayerEvents.BukkitMCGamemodeChangeEvent(event); - //EventUtils.TriggerExternal(e); + BukkitMCGamemodeChangeEvent e = new BukkitMCGamemodeChangeEvent(event); EventUtils.TriggerListener(Driver.GAMEMODE_CHANGE, "gamemode_change", e); } - - @EventHandler(priority= EventPriority.LOWEST) - public void onChatTab(PlayerChatTabCompleteEvent event) { - BukkitPlayerEvents.BukkitMCChatTabCompleteEvent e = new BukkitPlayerEvents.BukkitMCChatTabCompleteEvent(event); - //EventUtils.TriggerExternal(e); - EventUtils.TriggerListener(Driver.TAB_COMPLETE, "tab_complete_chat", e); - } - - @EventHandler(priority= EventPriority.LOWEST) + + @EventHandler(priority = EventPriority.LOWEST) public void onExpChange(PlayerExpChangeEvent event) { - BukkitPlayerEvents.BukkitMCExpChangeEvent e = new BukkitPlayerEvents.BukkitMCExpChangeEvent(event); - //EventUtils.TriggerExternal(e); + BukkitMCExpChangeEvent e = new BukkitMCExpChangeEvent(event); EventUtils.TriggerListener(Driver.EXP_CHANGE, "exp_change", e); } @EventHandler(priority = EventPriority.LOWEST) public void onPlayerEditBook(PlayerEditBookEvent event) { - BukkitPlayerEvents.BukkitMCPlayerEditBookEvent pebe = new BukkitPlayerEvents.BukkitMCPlayerEditBookEvent(event); - //EventUtils.TriggerExternal(pebe); + BukkitMCPlayerEditBookEvent pebe = new BukkitMCPlayerEditBookEvent(event); EventUtils.TriggerListener(Driver.BOOK_EDITED, "book_edited", pebe); } - + @EventHandler(priority = EventPriority.LOWEST) public void onPlayerToggleFlight(PlayerToggleFlightEvent event) { - BukkitPlayerEvents.BukkitMCPlayerToggleFlightEvent ptfe = new BukkitPlayerEvents.BukkitMCPlayerToggleFlightEvent(event); - //EventUtils.TriggerExternal(ptfe); + BukkitMCPlayerToggleFlightEvent ptfe = new BukkitMCPlayerToggleFlightEvent(event); EventUtils.TriggerListener(Driver.PLAYER_TOGGLE_FLIGHT, "player_toggle_flight", ptfe); } @EventHandler(priority = EventPriority.LOWEST) public void onPlayerToggleSneak(PlayerToggleSneakEvent event) { - BukkitPlayerEvents.BukkitMCPlayerToggleSneakEvent ptse = new BukkitPlayerEvents.BukkitMCPlayerToggleSneakEvent(event); - //EventUtils.TriggerExternal(ptse); + BukkitMCPlayerToggleSneakEvent ptse = new BukkitMCPlayerToggleSneakEvent(event); EventUtils.TriggerListener(Driver.PLAYER_TOGGLE_SNEAK, "player_toggle_sneak", ptse); } @EventHandler(priority = EventPriority.LOWEST) public void onPlayerToggleSprint(PlayerToggleSprintEvent event) { - BukkitPlayerEvents.BukkitMCPlayerToggleSprintEvent ptse = new BukkitPlayerEvents.BukkitMCPlayerToggleSprintEvent(event); - //EventUtils.TriggerExternal(ptse); + BukkitMCPlayerToggleSprintEvent ptse = new BukkitMCPlayerToggleSprintEvent(event); EventUtils.TriggerListener(Driver.PLAYER_TOGGLE_SPRINT, "player_toggle_sprint", ptse); } + + @EventHandler(priority = EventPriority.LOWEST) + public void onPlayerResourcePackStatus(PlayerResourcePackStatusEvent event) { + BukkitMCPlayerResourcePackEvent prpse = new BukkitMCPlayerResourcePackEvent(event); + EventUtils.TriggerListener(Driver.RESOURCE_PACK_STATUS, "resource_pack_status", prpse); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onPlayerMove(PlayerMoveEvent event) { + Location from = event.getFrom(); + Location to = event.getTo(); + if(to == null || from.getX() == to.getX() && from.getY() == to.getY() && from.getZ() == to.getZ()) { + return; + } + String p = event.getPlayer().getName(); + for(Integer threshold : PlayerEvents.GetThresholdList()) { + Map lastLocations = PlayerEvents.GetLastLocations(threshold); + MCLocation last; + if(!lastLocations.containsKey(p)) { + last = new BukkitMCLocation(from); + lastLocations.put(p, last); + } else { + last = lastLocations.get(p); + } + MCLocation movedTo = new BukkitMCLocation(to); + if(!movedTo.getWorld().getName().equals(last.getWorld().getName())) { + lastLocations.put(p, movedTo); + continue; + } + if(last.distance(movedTo) > threshold) { + BukkitMCPlayerMoveEvent pme = new BukkitMCPlayerMoveEvent(event, threshold, last); + EventUtils.TriggerListener(Driver.PLAYER_MOVE, "player_move", pme); + if(!pme.isCancelled()) { + lastLocations.put(p, movedTo); + } + } + } + } + + // Reset player_move lastLocations when player respawns or teleports + @EventHandler(priority = EventPriority.MONITOR) + public void onNewRespawnLocation(PlayerRespawnEvent event) { + for(Integer i : PlayerEvents.GetThresholdList()) { + PlayerEvents.GetLastLocations(i).put(event.getPlayer().getName(), new BukkitMCLocation(event.getRespawnLocation())); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onNewTeleportLocation(PlayerTeleportEvent event) { + if(event.getFrom().equals(event.getTo())) { + return; + } + if(!event.isCancelled()) { + for(Integer i : PlayerEvents.GetThresholdList()) { + PlayerEvents.GetLastLocations(i).put(event.getPlayer().getName(), new BukkitMCLocation(event.getTo())); + } + } + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onBucketFill(PlayerBucketFillEvent event) { + BukkitMCPlayerBucketFillEvent pbfe = new BukkitMCPlayerBucketFillEvent(event); + EventUtils.TriggerListener(Driver.PLAYER_BUCKET_FILL, "player_bucket_fill", pbfe); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onBucketEmpty(PlayerBucketEmptyEvent event) { + BukkitMCPlayerBucketEmptyEvent pbee = new BukkitMCPlayerBucketEmptyEvent(event); + EventUtils.TriggerListener(Driver.PLAYER_BUCKET_EMPTY, "player_bucket_empty", pbee); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onPlayerAdvancementDoneEvent(PlayerAdvancementDoneEvent event) { + BukkitMCPlayerAdvancementDoneEvent pade = new BukkitMCPlayerAdvancementDoneEvent(event); + EventUtils.TriggerListener(Driver.PLAYER_ADVANCEMENT_DONE, "player_advancement_done", pade); + } + + @EventIdentifier(event = Driver.PLAYER_STOP_USING_ITEM, className = "io.papermc.paper.event.player.PlayerStopUsingItemEvent") + public void onPlayerStopUsingItemEvent(Event event) { + BukkitMCPlayerStopUsingItemEvent psuie = new BukkitMCPlayerStopUsingItemEvent((PlayerStopUsingItemEvent) event); + EventUtils.TriggerListener(Driver.PLAYER_STOP_USING_ITEM, "player_stop_using_item", psuie); + } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/events/drivers/BukkitServerListener.java b/src/main/java/com/laytonsmith/abstraction/bukkit/events/drivers/BukkitServerListener.java index 57cc28d9f7..19f183fde3 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/events/drivers/BukkitServerListener.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/events/drivers/BukkitServerListener.java @@ -1,55 +1,42 @@ - - package com.laytonsmith.abstraction.bukkit.events.drivers; import com.laytonsmith.abstraction.MCLocation; import com.laytonsmith.abstraction.bukkit.BukkitMCLocation; -import com.laytonsmith.abstraction.bukkit.events.BukkitMiscEvents; +import com.laytonsmith.abstraction.bukkit.events.BukkitServerEvents; import com.laytonsmith.abstraction.events.MCRedstoneChangedEvent; -import com.laytonsmith.commandhelper.CommandHelperPlugin; import com.laytonsmith.core.events.Driver; import com.laytonsmith.core.events.EventUtils; import com.laytonsmith.core.events.drivers.ServerEvents; import java.util.Map; -import java.util.Set; + import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.block.BlockPhysicsEvent; -import org.bukkit.event.server.ServerCommandEvent; +import org.bukkit.event.server.BroadcastMessageEvent; import org.bukkit.event.server.ServerListPingEvent; -/** - * - * - */ -public class BukkitServerListener implements Listener{ - - //@EventHandler(priority= EventPriority.LOWEST) - public void onServerCommandEvent(ServerCommandEvent e){ +public class BukkitServerListener implements Listener { - } - - @EventHandler(priority= EventPriority.LOWEST) + @EventHandler(priority = EventPriority.LOWEST) public void onPing(ServerListPingEvent event) { - BukkitMiscEvents.BukkitMCServerPingEvent pe = new BukkitMiscEvents.BukkitMCServerPingEvent(event); - EventUtils.TriggerExternal(pe); + BukkitServerEvents.BukkitMCServerPingEvent pe = new BukkitServerEvents.BukkitMCServerPingEvent(event); EventUtils.TriggerListener(Driver.SERVER_PING, "server_ping", pe); } - @EventHandler(priority=EventPriority.LOWEST) - public void onBlockPhysics(BlockPhysicsEvent event){ + @EventHandler(priority = EventPriority.LOWEST) + public void onBlockPhysics(BlockPhysicsEvent event) { Map locations = ServerEvents.getRedstoneMonitors(); - if(locations.isEmpty()){ + if(locations.isEmpty()) { // Bail as quickly as we can if this isn't being used. return; } final MCLocation blockLocation = new BukkitMCLocation(event.getBlock().getLocation()); - if(locations.containsKey(blockLocation)){ + if(locations.containsKey(blockLocation)) { // This is a monitored location, so we will be triggering the event. boolean wasPowered = locations.get(blockLocation); final boolean isPowered = blockLocation.getBlock().isBlockPowered(); - if(wasPowered != isPowered){ + if(wasPowered != isPowered) { // It was changed, so set the state appropriately now. locations.put(blockLocation, isPowered); EventUtils.TriggerListener(Driver.REDSTONE_CHANGED, "redstone_changed", new MCRedstoneChangedEvent() { @@ -72,4 +59,10 @@ public Object _GetObject() { } } } + + @EventHandler(priority = EventPriority.LOWEST) + public void onBroadcast(BroadcastMessageEvent event) { + EventUtils.TriggerListener(Driver.BROADCAST_MESSAGE, "broadcast_message", + new BukkitServerEvents.BukkitMCBroadcastMessageEvent(event)); + } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/events/drivers/BukkitVehicleListener.java b/src/main/java/com/laytonsmith/abstraction/bukkit/events/drivers/BukkitVehicleListener.java index 4c192eec15..9675245484 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/events/drivers/BukkitVehicleListener.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/events/drivers/BukkitVehicleListener.java @@ -1,13 +1,17 @@ - - package com.laytonsmith.abstraction.bukkit.events.drivers; +import com.laytonsmith.abstraction.MCLocation; +import com.laytonsmith.abstraction.bukkit.BukkitMCLocation; import com.laytonsmith.abstraction.bukkit.events.BukkitVehicleEvents.BukkitMCVehicleBlockCollideEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitVehicleEvents.BukkitMCVehicleDestroyEvent; import com.laytonsmith.abstraction.bukkit.events.BukkitVehicleEvents.BukkitMCVehicleEnterEvent; import com.laytonsmith.abstraction.bukkit.events.BukkitVehicleEvents.BukkitMCVehicleEntityCollideEvent; import com.laytonsmith.abstraction.bukkit.events.BukkitVehicleEvents.BukkitMCVehicleExitEvent; +import com.laytonsmith.abstraction.bukkit.events.BukkitVehicleEvents.BukkitMCVehicleMoveEvent; import com.laytonsmith.core.events.Driver; import com.laytonsmith.core.events.EventUtils; +import com.laytonsmith.core.events.drivers.VehicleEvents; +import org.bukkit.Location; import org.bukkit.entity.Animals; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -16,43 +20,75 @@ import org.bukkit.event.vehicle.VehicleEnterEvent; import org.bukkit.event.vehicle.VehicleEntityCollisionEvent; import org.bukkit.event.vehicle.VehicleExitEvent; +import org.bukkit.event.vehicle.VehicleMoveEvent; +import org.bukkit.event.vehicle.VehicleDestroyEvent; +import org.bukkit.util.Vector; + +import java.util.Map; +import java.util.UUID; -/** - * - * - */ -public class BukkitVehicleListener implements Listener{ - - @EventHandler(priority= EventPriority.LOWEST) +public class BukkitVehicleListener implements Listener { + + @EventHandler(priority = EventPriority.LOWEST) public void onEnter(VehicleEnterEvent event) { BukkitMCVehicleEnterEvent vee = new BukkitMCVehicleEnterEvent(event); - EventUtils.TriggerExternal(vee); EventUtils.TriggerListener(Driver.VEHICLE_ENTER, "vehicle_enter", vee); } - - @EventHandler(priority= EventPriority.LOWEST) + + @EventHandler(priority = EventPriority.LOWEST) public void onExit(VehicleExitEvent event) { BukkitMCVehicleExitEvent vee = new BukkitMCVehicleExitEvent(event); - EventUtils.TriggerExternal(vee); EventUtils.TriggerListener(Driver.VEHICLE_LEAVE, "vehicle_leave", vee); } - - @EventHandler(priority= EventPriority.LOWEST) + + @EventHandler(priority = EventPriority.LOWEST) public void onBlockCollide(VehicleBlockCollisionEvent event) { - if (event.getVehicle() instanceof Animals && event.getVehicle().getPassenger() == null) { + if(event.getVehicle() instanceof Animals && event.getVehicle().isEmpty()) { return; } BukkitMCVehicleBlockCollideEvent vbc = new BukkitMCVehicleBlockCollideEvent(event); - EventUtils.TriggerExternal(vbc); EventUtils.TriggerListener(Driver.VEHICLE_COLLIDE, "vehicle_collide", vbc); } - @EventHandler(priority= EventPriority.LOWEST) + @EventHandler(priority = EventPriority.LOWEST) public void onEntityCollide(VehicleEntityCollisionEvent event) { - if (event.getVehicle().getPassenger() != event.getEntity()) { - BukkitMCVehicleEntityCollideEvent vec = new BukkitMCVehicleEntityCollideEvent(event); - EventUtils.TriggerExternal(vec); - EventUtils.TriggerListener(Driver.VEHICLE_COLLIDE, "vehicle_collide", vec); + BukkitMCVehicleEntityCollideEvent vec = new BukkitMCVehicleEntityCollideEvent(event); + EventUtils.TriggerListener(Driver.VEHICLE_COLLIDE, "vehicle_collide", vec); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onVehicleMove(VehicleMoveEvent event) { + Location from = event.getFrom(); + Location to = event.getTo(); + UUID id = event.getVehicle().getUniqueId(); + for(Integer threshold : VehicleEvents.GetThresholdList()) { + Map lastLocations = VehicleEvents.GetLastLocations(threshold); + if(!lastLocations.containsKey(id)) { + lastLocations.put(id, new BukkitMCLocation(from)); + continue; + } + MCLocation last = lastLocations.get(id); + if(!to.getWorld().getName().equals(last.getWorld().getName())) { + lastLocations.put(id, new BukkitMCLocation(to)); + continue; + } + BukkitMCLocation movedTo = new BukkitMCLocation(to); + if(last.distance(movedTo) > threshold) { + BukkitMCVehicleMoveEvent vme = new BukkitMCVehicleMoveEvent(event, threshold, last); + EventUtils.TriggerListener(Driver.VEHICLE_MOVE, "vehicle_move", vme); + if(!vme.isCancelled()) { + lastLocations.put(id, movedTo); + } else { + event.getVehicle().setVelocity(new Vector(0, 0, 0)); + event.getVehicle().teleport(from); + } + } } } + + @EventHandler(priority = EventPriority.LOWEST) + public void onVehicleDestroy(VehicleDestroyEvent event) { + BukkitMCVehicleDestroyEvent vee = new BukkitMCVehicleDestroyEvent(event); + EventUtils.TriggerListener(Driver.VEHICLE_DESTROY, "vehicle_destroy", vee); + } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/events/drivers/BukkitWeatherListener.java b/src/main/java/com/laytonsmith/abstraction/bukkit/events/drivers/BukkitWeatherListener.java index b1a7ec1092..3fd2601bee 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/events/drivers/BukkitWeatherListener.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/events/drivers/BukkitWeatherListener.java @@ -1,13 +1,29 @@ - - package com.laytonsmith.abstraction.bukkit.events.drivers; +import com.laytonsmith.abstraction.bukkit.events.BukkitWeatherEvents; +import com.laytonsmith.core.events.Driver; +import com.laytonsmith.core.events.EventUtils; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; +import org.bukkit.event.weather.LightningStrikeEvent; +import org.bukkit.event.weather.ThunderChangeEvent; +import org.bukkit.event.weather.WeatherChangeEvent; + +public class BukkitWeatherListener implements Listener { + + @EventHandler(priority = EventPriority.LOWEST) + public void onLightningStrike(LightningStrikeEvent event) { + EventUtils.TriggerListener(Driver.LIGHTNING_STRIKE, "lightning_strike", new BukkitWeatherEvents.BukkitMCLightningStrikeEvent(event)); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onThunderChange(ThunderChangeEvent event) { + EventUtils.TriggerListener(Driver.THUNDER_CHANGE, "thunder_change", new BukkitWeatherEvents.BukkitMCThunderChangeEvent(event)); + } -/** - * - * - */ -public class BukkitWeatherListener implements Listener{ - + @EventHandler(priority = EventPriority.LOWEST) + public void onWeatherChange(WeatherChangeEvent event) { + EventUtils.TriggerListener(Driver.WEATHER_CHANGE, "weather_change", new BukkitWeatherEvents.BukkitMCWeatherChangeEvent(event)); + } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/events/drivers/BukkitWorldListener.java b/src/main/java/com/laytonsmith/abstraction/bukkit/events/drivers/BukkitWorldListener.java index 1e3520a58c..ec75cb2881 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/events/drivers/BukkitWorldListener.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/events/drivers/BukkitWorldListener.java @@ -11,10 +11,6 @@ import org.bukkit.event.world.WorldSaveEvent; import org.bukkit.event.world.WorldUnloadEvent; -/** - * - * - */ public class BukkitWorldListener implements Listener { @EventHandler(priority = EventPriority.LOWEST) @@ -36,4 +32,4 @@ public void onWorldUnload(WorldUnloadEvent event) { public void onWorldLoad(WorldLoadEvent event) { EventUtils.TriggerListener(Driver.WORLD_LOAD, "world_load", new BukkitWorldEvents.BukkitMCWorldLoadEvent(event)); } -} \ No newline at end of file +} diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/pluginmessages/BukkitMCMessenger.java b/src/main/java/com/laytonsmith/abstraction/bukkit/pluginmessages/BukkitMCMessenger.java index b940b6e10b..3ed951a4a2 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/pluginmessages/BukkitMCMessenger.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/pluginmessages/BukkitMCMessenger.java @@ -10,48 +10,49 @@ /** * - * @author Jason Unger + * @author Jason Unger (entityreborn@gmail.com) */ public class BukkitMCMessenger implements MCMessenger { + Messenger messenger; public BukkitMCMessenger(Messenger messager) { this.messenger = messager; } - + @Override - public MCPluginMessageListenerRegistration registerIncomingPluginChannel( - String channel) { + public MCPluginMessageListenerRegistration registerIncomingPluginChannel(String channel) { PluginMessageListenerRegistration reg; - + reg = messenger.registerIncomingPluginChannel( - CommandHelperPlugin.self, channel, - CommandHelperMessageListener.getInstance()); + CommandHelperPlugin.self, channel, + CommandHelperMessageListener.getInstance()); return new BukkitMCPluginMessageListenerRegistration(reg); } - + @Override public boolean isIncomingChannelRegistered(String channel) { return messenger.isIncomingChannelRegistered(CommandHelperPlugin.self, channel); } - + @Override public void unregisterIncomingPluginChannel(String channel) { messenger.unregisterIncomingPluginChannel( - CommandHelperPlugin.self, channel, - CommandHelperMessageListener.getInstance()); + CommandHelperPlugin.self, channel, + CommandHelperMessageListener.getInstance() + ); } - + @Override public Set getIncomingChannels() { return messenger.getIncomingChannels(CommandHelperPlugin.self); } - + @Override public void closeAllChannels() { Set chans = getIncomingChannels(); - for (String chan : chans) { + for(String chan : chans) { unregisterIncomingPluginChannel(chan); } } diff --git a/src/main/java/com/laytonsmith/abstraction/bukkit/pluginmessages/BukkitMCPluginMessageListenerRegistration.java b/src/main/java/com/laytonsmith/abstraction/bukkit/pluginmessages/BukkitMCPluginMessageListenerRegistration.java index e589848b7b..1113062d78 100644 --- a/src/main/java/com/laytonsmith/abstraction/bukkit/pluginmessages/BukkitMCPluginMessageListenerRegistration.java +++ b/src/main/java/com/laytonsmith/abstraction/bukkit/pluginmessages/BukkitMCPluginMessageListenerRegistration.java @@ -5,13 +5,13 @@ /** * - * @author Jason Unger + * @author Jason Unger (entityreborn@gmail.com) */ public class BukkitMCPluginMessageListenerRegistration implements MCPluginMessageListenerRegistration { + PluginMessageListenerRegistration registration; public BukkitMCPluginMessageListenerRegistration(PluginMessageListenerRegistration registration) { this.registration = registration; } - } diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCAbstractHorse.java b/src/main/java/com/laytonsmith/abstraction/entities/MCAbstractHorse.java new file mode 100644 index 0000000000..ec2a621619 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCAbstractHorse.java @@ -0,0 +1,24 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCInventoryHolder; +import com.laytonsmith.abstraction.MCItemStack; + +public interface MCAbstractHorse extends MCTameable, MCVehicle, MCInventoryHolder { + + double getJumpStrength(); + + void setJumpStrength(double strength); + + int getDomestication(); + + int getMaxDomestication(); + + void setDomestication(int level); + + void setMaxDomestication(int level); + + void setSaddle(MCItemStack stack); + + MCItemStack getSaddle(); + +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCAgeable.java b/src/main/java/com/laytonsmith/abstraction/entities/MCAgeable.java new file mode 100644 index 0000000000..29e08d9b04 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCAgeable.java @@ -0,0 +1,18 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCLivingEntity; + +public interface MCAgeable extends MCLivingEntity { + + int getAge(); + + void setAge(int age); + + boolean isAdult(); + + void setAdult(); + + boolean isBaby(); + + void setBaby(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCAllay.java b/src/main/java/com/laytonsmith/abstraction/entities/MCAllay.java new file mode 100644 index 0000000000..58404d9126 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCAllay.java @@ -0,0 +1,6 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCInventoryHolder; + +public interface MCAllay extends MCInventoryHolder { +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCAnimal.java b/src/main/java/com/laytonsmith/abstraction/entities/MCAnimal.java new file mode 100644 index 0000000000..6b8dd54141 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCAnimal.java @@ -0,0 +1,10 @@ +package com.laytonsmith.abstraction.entities; + +import java.util.UUID; + +public interface MCAnimal extends MCBreedable { + int getLoveTicks(); + void setLoveTicks(int ticks); + UUID getBreedCause(); + void setBreedCause(UUID cause); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCAreaEffectCloud.java b/src/main/java/com/laytonsmith/abstraction/entities/MCAreaEffectCloud.java new file mode 100644 index 0000000000..0531b51f9c --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCAreaEffectCloud.java @@ -0,0 +1,69 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCColor; +import com.laytonsmith.abstraction.MCEntity; +import com.laytonsmith.abstraction.MCLivingEntity; +import com.laytonsmith.abstraction.MCPotionData; +import com.laytonsmith.abstraction.MCProjectileSource; +import com.laytonsmith.abstraction.enums.MCParticle; +import com.laytonsmith.abstraction.enums.MCPotionType; + +import java.util.List; + +public interface MCAreaEffectCloud extends MCEntity { + + MCPotionData getBasePotionData(); + + MCPotionType getBasePotionType(); + + MCColor getColor(); + + List getCustomEffects(); + + int getDuration(); + + int getDurationOnUse(); + + MCParticle getParticle(); + + float getRadius(); + + float getRadiusOnUse(); + + float getRadiusPerTick(); + + int getReapplicationDelay(); + + MCProjectileSource getSource(); + + int getWaitTime(); + + void addCustomEffect(MCLivingEntity.MCEffect effect); + + void clearCustomEffects(); + + void setBasePotionData(MCPotionData data); + + void setBasePotionType(MCPotionType type); + + void setColor(MCColor color); + + void setDuration(int ticks); + + void setDurationOnUse(int ticks); + + void setParticle(MCParticle particle, Object data); + + void setRadius(float radius); + + void setRadiusOnUse(float radius); + + void setRadiusPerTick(float radius); + + void setReapplicationDelay(int ticks); + + void setSource(MCProjectileSource source); + + void setWaitTime(int ticks); + +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCArmorStand.java b/src/main/java/com/laytonsmith/abstraction/entities/MCArmorStand.java new file mode 100644 index 0000000000..b927a87331 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCArmorStand.java @@ -0,0 +1,202 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.PureUtilities.Vector3D; +import com.laytonsmith.abstraction.enums.MCBodyPart; + +import java.util.Map; + +public interface MCArmorStand { + + /** + * + * @return A map of body part keys and angle vector values + */ + Map getAllPoses(); + + /** + * Set all poses at once. + * + * @param posemap A map of body part keys and angle vector values + */ + void setAllPoses(Map posemap); + + /** + * Returns the armor stand's body's current pose as a 3D vector of doubles. Each component is the angle for that + * axis in radians. + * + * @return the current pose + */ + Vector3D getBodyPose(); + + /** + * Sets the armor stand's body's current pose as a 3D vector of doubles. Each component is the angle for that axis + * in radians. + * + * @param pose the current pose + */ + void setBodyPose(Vector3D pose); + + /** + * Returns the armor stand's left arm's current pose as a 3D vector of doubles. Each component is the angle for that + * axis in radians. + * + * @return the current pose + */ + Vector3D getLeftArmPose(); + + /** + * Sets the armor stand's left arm's current pose as a 3D vector of doubles. Each component is the angle for that + * axis in radians. + * + * @param pose the current pose + */ + void setLeftArmPose(Vector3D pose); + + /** + * Returns the armor stand's right arm's current pose as a 3D vector of doubles. Each component is the angle for + * that axis in radians. + * + * @return the current pose + */ + Vector3D getRightArmPose(); + + /** + * Sets the armor stand's right arm's current pose as a 3D vector of doubles. Each component is the angle for that + * axis in radians. + * + * @param pose the current pose + */ + void setRightArmPose(Vector3D pose); + + /** + * Returns the armor stand's left leg's current pose as a 3D vector of doubles. Each component is the angle for that + * axis in radians. + * + * @return the current pose + */ + Vector3D getLeftLegPose(); + + /** + * Sets the armor stand's left leg's current pose as a 3D vector of doubles. Each component is the angle for that + * axis in radians. + * + * @param pose the current pose + */ + void setLeftLegPose(Vector3D pose); + + /** + * Returns the armor stand's right leg's current pose as a 3D vector of doubles. Each component is the angle for + * that axis in radians. + * + * @return the current pose + */ + Vector3D getRightLegPose(); + + /** + * Sets the armor stand's right leg's current pose as a 3D vector of doubles. Each component is the angle for that + * axis in radians. + * + * @param pose the current pose + */ + void setRightLegPose(Vector3D pose); + + /** + * Returns the armor stand's head's current pose as a 3D vector of doubles. Each component is the angle for that + * axis in radians. + * + * @return the current pose + */ + Vector3D getHeadPose(); + + /** + * Sets the armor stand's head's current pose as a 3D vector of doubles. Each component is the angle for that axis + * in radians. + * + * @param pose the current pose + */ + void setHeadPose(Vector3D pose); + + /** + * Returns whether the armor stand has a base plate + * + * @return whether it has a base plate + */ + boolean hasBasePlate(); + + /** + * Sets whether the armor stand has a base plate + * + * @param basePlate whether is has a base plate + */ + void setHasBasePlate(boolean basePlate); + + /** + * Returns whether gravity applies to this armor stand + * + * @return whether gravity applies + */ + boolean hasGravity(); + + /** + * Sets whether gravity applies to this armor stand + * + * @param gravity whether gravity should apply + */ + void setHasGravity(boolean gravity); + + /** + * Returns whether the armor stand should be visible or not + * + * @return whether the stand is visible or not + */ + boolean isVisible(); + + /** + * Sets whether the armor stand should be visible or not + * + * @param visible whether the stand is visible or not + */ + void setVisible(boolean visible); + + /** + * Returns whether this armor stand has arms + * + * @return whether this has arms or not + */ + boolean hasArms(); + + /** + * Sets whether this armor stand has arms + * + * @param arms whether this has arms or not + */ + void setHasArms(boolean arms); + + /** + * Returns whether this armor stand is scaled down + * + * @return whether this is scaled down + */ + boolean isSmall(); + + /** + * Sets whether this armor stand is scaled down + * + * @param small whether this is scaled down + */ + void setSmall(boolean small); + + /** + * Returns whether this armor stand is a marker, meaning it has a tiny collision box and disables interaction + * + * @return whether this is a marker + */ + Boolean isMarker(); + + /** + * Sets whether this armor stand is a marker, meaning it has a tiny collision box and disables interaction + * + * @param marker whether this is a marker + */ + void setMarker(boolean marker); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCArrow.java b/src/main/java/com/laytonsmith/abstraction/entities/MCArrow.java index a7792b1121..a5afdc3e5e 100644 --- a/src/main/java/com/laytonsmith/abstraction/entities/MCArrow.java +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCArrow.java @@ -1,12 +1,55 @@ package com.laytonsmith.abstraction.entities; -import com.laytonsmith.abstraction.MCProjectile; +import com.laytonsmith.abstraction.MCColor; +import com.laytonsmith.abstraction.MCLivingEntity; +import com.laytonsmith.abstraction.MCPotionData; +import com.laytonsmith.abstraction.enums.MCPotionType; + +import java.util.List; public interface MCArrow extends MCProjectile { - public int getKnockbackStrength(); - public void setKnockbackStrength(int strength); + int getKnockbackStrength(); + + void setKnockbackStrength(int strength); + + boolean isCritical(); + + void setCritical(boolean critical); + + double getDamage(); + + void setDamage(double damage); + + MCPotionData getBasePotionData(); + + MCPotionType getBasePotionType(); + + List getCustomEffects(); + + void addCustomEffect(MCLivingEntity.MCEffect effect); + + void clearCustomEffects(); + + void setBasePotionData(MCPotionData pd); + + void setBasePotionType(MCPotionType type); + + int getPierceLevel(); + + void setPierceLevel(int level); + + PickupStatus getPickupStatus(); + + void setPickupStatus(PickupStatus status); + + MCColor getColor(); + + void setColor(MCColor color); - public boolean isCritical(); - public void setCritical(boolean critical); -} \ No newline at end of file + enum PickupStatus { + ALLOWED, + DISALLOWED, + CREATIVE_ONLY + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCAxolotl.java b/src/main/java/com/laytonsmith/abstraction/entities/MCAxolotl.java new file mode 100644 index 0000000000..e771b92aa6 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCAxolotl.java @@ -0,0 +1,10 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.enums.MCAxolotlType; + +public interface MCAxolotl extends MCAnimal { + boolean isPlayingDead(); + void setPlayingDead(boolean playingDead); + MCAxolotlType getAxolotlType(); + void setAxolotlType(MCAxolotlType type); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCBee.java b/src/main/java/com/laytonsmith/abstraction/entities/MCBee.java new file mode 100644 index 0000000000..a1677a0982 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCBee.java @@ -0,0 +1,16 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCLocation; + +public interface MCBee extends MCAnimal { + MCLocation getHiveLocation(); + void setHiveLocation(MCLocation loc); + MCLocation getFlowerLocation(); + void setFlowerLocation(MCLocation loc); + boolean hasNectar(); + void setHasNectar(boolean nectar); + boolean hasStung(); + void setHasStung(boolean stung); + int getAnger(); + void setAnger(int ticks); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCBlockDisplay.java b/src/main/java/com/laytonsmith/abstraction/entities/MCBlockDisplay.java new file mode 100644 index 0000000000..d7b397dc8f --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCBlockDisplay.java @@ -0,0 +1,11 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.blocks.MCBlockData; + +public interface MCBlockDisplay extends MCDisplay { + + MCBlockData getBlockData(); + + void setBlockData(MCBlockData data); + +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCBoat.java b/src/main/java/com/laytonsmith/abstraction/entities/MCBoat.java index 3dd17f38b9..f6180fa168 100644 --- a/src/main/java/com/laytonsmith/abstraction/entities/MCBoat.java +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCBoat.java @@ -1,14 +1,9 @@ package com.laytonsmith.abstraction.entities; -import com.laytonsmith.abstraction.MCVehicle; +import com.laytonsmith.abstraction.MCLeashable; +import com.laytonsmith.abstraction.enums.MCTreeSpecies; -public interface MCBoat extends MCVehicle { - public double getMaxSpeed(); - public void setMaxSpeed(double speed); - public double getOccupiedDeclaration(); - public void setOccupiedDeclaration(double rate); - public double getUnoccupiedDeclaration(); - public void setUnoccupiedDeclaration(double rate); - public boolean getWorkOnLand(); - public void setWorkOnLand(boolean workOnLand); +public interface MCBoat extends MCVehicle, MCLeashable { + MCTreeSpecies getWoodType(); + void setWoodType(MCTreeSpecies type); } diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCBogged.java b/src/main/java/com/laytonsmith/abstraction/entities/MCBogged.java new file mode 100644 index 0000000000..bb3383b90a --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCBogged.java @@ -0,0 +1,4 @@ +package com.laytonsmith.abstraction.entities; + +public interface MCBogged extends MCSkeleton { +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCBreedable.java b/src/main/java/com/laytonsmith/abstraction/entities/MCBreedable.java new file mode 100644 index 0000000000..1db9dea1da --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCBreedable.java @@ -0,0 +1,12 @@ +package com.laytonsmith.abstraction.entities; + +public interface MCBreedable extends MCAgeable { + + boolean getCanBreed(); + + void setCanBreed(boolean breed); + + boolean getAgeLock(); + + void setAgeLock(boolean lock); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCCamel.java b/src/main/java/com/laytonsmith/abstraction/entities/MCCamel.java new file mode 100644 index 0000000000..074acda605 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCCamel.java @@ -0,0 +1,6 @@ +package com.laytonsmith.abstraction.entities; + +public interface MCCamel extends MCAbstractHorse { + boolean isDashing(); + void setDashing(boolean dashing); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCCat.java b/src/main/java/com/laytonsmith/abstraction/entities/MCCat.java new file mode 100644 index 0000000000..fb5505197c --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCCat.java @@ -0,0 +1,13 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.enums.MCCatType; +import com.laytonsmith.abstraction.enums.MCDyeColor; + +public interface MCCat extends MCTameable { + MCDyeColor getCollarColor(); + void setCollarColor(MCDyeColor color); + boolean isSitting(); + void setSitting(boolean sitting); + MCCatType getCatType(); + void setCatType(MCCatType type); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCChestBoat.java b/src/main/java/com/laytonsmith/abstraction/entities/MCChestBoat.java new file mode 100644 index 0000000000..d4c4a55d8f --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCChestBoat.java @@ -0,0 +1,6 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCInventoryHolder; + +public interface MCChestBoat extends MCBoat, MCInventoryHolder { +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCChestedHorse.java b/src/main/java/com/laytonsmith/abstraction/entities/MCChestedHorse.java new file mode 100644 index 0000000000..c5791e7189 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCChestedHorse.java @@ -0,0 +1,9 @@ +package com.laytonsmith.abstraction.entities; + +public interface MCChestedHorse extends MCAbstractHorse { + + boolean hasChest(); + + void setHasChest(boolean hasChest); + +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCCommandMinecart.java b/src/main/java/com/laytonsmith/abstraction/entities/MCCommandMinecart.java index 85435b5e14..5fe130240b 100644 --- a/src/main/java/com/laytonsmith/abstraction/entities/MCCommandMinecart.java +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCCommandMinecart.java @@ -1,8 +1,15 @@ package com.laytonsmith.abstraction.entities; -public interface MCCommandMinecart extends MCMinecart { - public String getName(); - public void setName(String cmd); - public String getCommand(); - public void setCommand(String cmd); +import com.laytonsmith.abstraction.MCCommandSender; + +public interface MCCommandMinecart extends MCMinecart, MCCommandSender { + + @Override + String getName(); + + void setName(String cmd); + + String getCommand(); + + void setCommand(String cmd); } diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCComplexEntityPart.java b/src/main/java/com/laytonsmith/abstraction/entities/MCComplexEntityPart.java index a039fb5505..2371c12d30 100644 --- a/src/main/java/com/laytonsmith/abstraction/entities/MCComplexEntityPart.java +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCComplexEntityPart.java @@ -2,11 +2,7 @@ import com.laytonsmith.abstraction.MCEntity; -/** - * - * @author Hekta - */ public interface MCComplexEntityPart extends MCEntity { - public MCComplexLivingEntity getParent(); -} \ No newline at end of file + MCComplexLivingEntity getParent(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCComplexLivingEntity.java b/src/main/java/com/laytonsmith/abstraction/entities/MCComplexLivingEntity.java index 2b08e7ea9b..b02fd04c3c 100644 --- a/src/main/java/com/laytonsmith/abstraction/entities/MCComplexLivingEntity.java +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCComplexLivingEntity.java @@ -3,11 +3,7 @@ import com.laytonsmith.abstraction.MCLivingEntity; import java.util.Set; -/** - * - * @author Hekta - */ public interface MCComplexLivingEntity extends MCLivingEntity { - public Set getParts(); -} \ No newline at end of file + Set getParts(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCCreeper.java b/src/main/java/com/laytonsmith/abstraction/entities/MCCreeper.java index 080f080669..8a8a1c7d78 100644 --- a/src/main/java/com/laytonsmith/abstraction/entities/MCCreeper.java +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCCreeper.java @@ -1,11 +1,20 @@ - package com.laytonsmith.abstraction.entities; -/** - * - * @author Jason Unger - */ public interface MCCreeper { + boolean isPowered(); + void setPowered(boolean powered); + + int getMaxFuseTicks(); + + void setMaxFuseTicks(int ticks); + + int getFuseTicks(); + + void setFuseTicks(int ticks); + + int getExplosionRadius(); + + void setExplosionRadius(int radius); } diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCDisplay.java b/src/main/java/com/laytonsmith/abstraction/entities/MCDisplay.java new file mode 100644 index 0000000000..a38892019d --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCDisplay.java @@ -0,0 +1,74 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCColor; +import com.laytonsmith.abstraction.MCEntity; + +public interface MCDisplay extends MCEntity { + + Billboard getBillboard(); + + void setBillboard(Billboard billboard); + + Brightness getBrightness(); + + void setBrightness(Brightness brightness); + + MCColor getGlowColorOverride(); + + void setGlowColorOverride(MCColor color); + + float getDisplayHeight(); + + void setDisplayHeight(float height); + + float getDisplayWidth(); + + void setDisplayWidth(float width); + + int getInterpolationDurationTicks(); + + void setInterpolationDurationTicks(int ticks); + + int getInterpolationDelayTicks(); + + void setInterpolationDelayTicks(int ticks); + + /** + * Added in MC 1.20.2 + * @return ticks + */ + int getTeleportDuration(); + + /** + * Added in MC 1.20.2 + * @param ticks + */ + void setTeleportDuration(int ticks); + + float getShadowRadius(); + + void setShadowRadius(float radius); + + float getShadowStrength(); + + void setShadowStrength(float strength); + + float getViewRange(); + + void setViewRange(float range); + + MCTransformation getTransformation(); + + void setTransformation(MCTransformation transformation); + + void setTransformationMatrix(float[] mtrxf); + + enum Billboard { + CENTER, + FIXED, + HORIZONTAL, + VERTICAL + } + + record Brightness(int block, int sky) {} +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCDonkey.java b/src/main/java/com/laytonsmith/abstraction/entities/MCDonkey.java new file mode 100644 index 0000000000..a9815dff44 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCDonkey.java @@ -0,0 +1,4 @@ +package com.laytonsmith.abstraction.entities; + +public interface MCDonkey extends MCChestedHorse { +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCDrowned.java b/src/main/java/com/laytonsmith/abstraction/entities/MCDrowned.java new file mode 100644 index 0000000000..68f22e0a66 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCDrowned.java @@ -0,0 +1,4 @@ +package com.laytonsmith.abstraction.entities; + +public interface MCDrowned extends MCZombie { +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCElderGuardian.java b/src/main/java/com/laytonsmith/abstraction/entities/MCElderGuardian.java new file mode 100644 index 0000000000..7854bc0760 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCElderGuardian.java @@ -0,0 +1,4 @@ +package com.laytonsmith.abstraction.entities; + +public interface MCElderGuardian extends MCGuardian { +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCEnderCrystal.java b/src/main/java/com/laytonsmith/abstraction/entities/MCEnderCrystal.java new file mode 100644 index 0000000000..1d1ba32042 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCEnderCrystal.java @@ -0,0 +1,15 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCEntity; +import com.laytonsmith.abstraction.MCLocation; + +public interface MCEnderCrystal extends MCEntity { + + boolean isShowingBottom(); + + void setShowingBottom(boolean showing); + + MCLocation getBeamTarget(); + + void setBeamTarget(MCLocation target); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCEnderDragon.java b/src/main/java/com/laytonsmith/abstraction/entities/MCEnderDragon.java index 21c9321e49..812e4db5d4 100644 --- a/src/main/java/com/laytonsmith/abstraction/entities/MCEnderDragon.java +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCEnderDragon.java @@ -1,9 +1,11 @@ package com.laytonsmith.abstraction.entities; -/** - * - * @author Hekta - */ +import com.laytonsmith.abstraction.enums.MCEnderDragonPhase; + public interface MCEnderDragon extends MCComplexLivingEntity { -} \ No newline at end of file + MCEnderDragonPhase getPhase(); + + void setPhase(MCEnderDragonPhase phase); + +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCEnderDragonPart.java b/src/main/java/com/laytonsmith/abstraction/entities/MCEnderDragonPart.java index 38863536de..78a8255ed0 100644 --- a/src/main/java/com/laytonsmith/abstraction/entities/MCEnderDragonPart.java +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCEnderDragonPart.java @@ -1,9 +1,5 @@ package com.laytonsmith.abstraction.entities; -/** - * - * @author Hekta - */ public interface MCEnderDragonPart extends MCComplexEntityPart { -} \ No newline at end of file +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCEnderSignal.java b/src/main/java/com/laytonsmith/abstraction/entities/MCEnderSignal.java index d16bc152ec..58217df451 100644 --- a/src/main/java/com/laytonsmith/abstraction/entities/MCEnderSignal.java +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCEnderSignal.java @@ -1,7 +1,16 @@ package com.laytonsmith.abstraction.entities; import com.laytonsmith.abstraction.MCEntity; +import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.MCLocation; public interface MCEnderSignal extends MCEntity { - + int getDespawnTicks(); + void setDespawnTicks(int ticks); + boolean getDropItem(); + void setDropItem(boolean drop); + MCLocation getTargetLocation(); + void setTargetLocation(MCLocation loc); + MCItemStack getItem(); + void setItem(MCItemStack stack); } diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCEnderman.java b/src/main/java/com/laytonsmith/abstraction/entities/MCEnderman.java index b9878ff9a4..07f5bdb0e8 100644 --- a/src/main/java/com/laytonsmith/abstraction/entities/MCEnderman.java +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCEnderman.java @@ -1,12 +1,15 @@ package com.laytonsmith.abstraction.entities; import com.laytonsmith.abstraction.MCLivingEntity; -import com.laytonsmith.abstraction.MCMaterialData; +import com.laytonsmith.abstraction.blocks.MCBlockData; public interface MCEnderman extends MCLivingEntity { - public MCMaterialData getCarriedMaterial(); - public int getCarriedType(); - public byte getCarriedData(); - public void setCarriedMaterial(MCMaterialData held); - public void setCarriedMaterial(int type, byte data); + + /** + * Gets the data of the block that the Enderman is carrying. + * @return {@link MCBlockData} containing the carried block, or {@code null} if none. + */ + MCBlockData getCarriedMaterial(); + + void setCarriedMaterial(MCBlockData held); } diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCEvoker.java b/src/main/java/com/laytonsmith/abstraction/entities/MCEvoker.java new file mode 100644 index 0000000000..d5364369c1 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCEvoker.java @@ -0,0 +1,6 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCLivingEntity; + +public interface MCEvoker extends MCLivingEntity { +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCEvokerFangs.java b/src/main/java/com/laytonsmith/abstraction/entities/MCEvokerFangs.java new file mode 100644 index 0000000000..021f7f02b8 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCEvokerFangs.java @@ -0,0 +1,9 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCEntity; +import com.laytonsmith.abstraction.MCLivingEntity; + +public interface MCEvokerFangs extends MCEntity { + MCLivingEntity getOwner(); + void setOwner(MCLivingEntity owner); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCExperienceOrb.java b/src/main/java/com/laytonsmith/abstraction/entities/MCExperienceOrb.java new file mode 100644 index 0000000000..f0ef5c296e --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCExperienceOrb.java @@ -0,0 +1,10 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCEntity; + +public interface MCExperienceOrb extends MCEntity { + + int getExperience(); + + void setExperience(int amount); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCFallingBlock.java b/src/main/java/com/laytonsmith/abstraction/entities/MCFallingBlock.java new file mode 100644 index 0000000000..78f37025ac --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCFallingBlock.java @@ -0,0 +1,12 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCEntity; +import com.laytonsmith.abstraction.blocks.MCMaterial; + +public interface MCFallingBlock extends MCEntity { + boolean getDropItem(); + MCMaterial getMaterial(); + void setDropItem(boolean drop); + boolean canHurtEntities(); + void setHurtEntities(boolean hurt); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCFireball.java b/src/main/java/com/laytonsmith/abstraction/entities/MCFireball.java new file mode 100644 index 0000000000..305ce09672 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCFireball.java @@ -0,0 +1,10 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.PureUtilities.Vector3D; + +public interface MCFireball extends MCProjectile { + + Vector3D getDirection(); + + void setDirection(Vector3D vector); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCFirework.java b/src/main/java/com/laytonsmith/abstraction/entities/MCFirework.java index 54b9dc869e..d933027e2a 100644 --- a/src/main/java/com/laytonsmith/abstraction/entities/MCFirework.java +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCFirework.java @@ -1,10 +1,16 @@ package com.laytonsmith.abstraction.entities; -import com.laytonsmith.abstraction.MCEntity; import com.laytonsmith.abstraction.MCFireworkMeta; -public interface MCFirework extends MCEntity { +public interface MCFirework extends MCProjectile { - public MCFireworkMeta getFireWorkMeta(); - public void setFireWorkMeta(MCFireworkMeta fm); + MCFireworkMeta getFireWorkMeta(); + + void setFireWorkMeta(MCFireworkMeta fm); + + boolean isShotAtAngle(); + + void setShotAtAngle(boolean shotAtAngle); + + void detonate(); } diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCFishHook.java b/src/main/java/com/laytonsmith/abstraction/entities/MCFishHook.java index 7a5de46541..bc39640dbd 100644 --- a/src/main/java/com/laytonsmith/abstraction/entities/MCFishHook.java +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCFishHook.java @@ -1,18 +1,5 @@ package com.laytonsmith.abstraction.entities; -import com.laytonsmith.abstraction.MCProjectile; - public interface MCFishHook extends MCProjectile { - /** - * This only refers to with the hook is being reeled in. - * @return chance from 0.0 to 1.0 - */ - public double getBiteChance(); - /** - * Setting this only has an effect when the hook is reeled in. - * 0.0 represents no chance, 1.0 guarantees that a fish will be caught - * when the hook is pulled in. - * @param chance, must be from 0.0 to 1.0 (inclusive) - */ - public void setBiteChance(double chance); + } diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCFox.java b/src/main/java/com/laytonsmith/abstraction/entities/MCFox.java new file mode 100644 index 0000000000..8c7bafbcb4 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCFox.java @@ -0,0 +1,12 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.enums.MCFoxType; + +public interface MCFox extends MCAnimal { + MCFoxType getVariant(); + void setVariant(MCFoxType type); + boolean isCrouching(); + void setCrouching(boolean crouching); + boolean isSitting(); + void setSitting(boolean sitting); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCFrog.java b/src/main/java/com/laytonsmith/abstraction/entities/MCFrog.java new file mode 100644 index 0000000000..a87820ed6e --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCFrog.java @@ -0,0 +1,18 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCEntity; +import com.laytonsmith.annotations.MEnum; + +public interface MCFrog extends MCAnimal { + + @MEnum("com.commandhelper.FrogType") + enum MCFrogType { + TEMPERATE, WARM, COLD + } + + MCFrogType getFrogType(); + void setFrogType(MCFrogType type); + MCEntity getTongueTarget(); + void setTongueTarget(MCEntity target); + +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCGlowItemFrame.java b/src/main/java/com/laytonsmith/abstraction/entities/MCGlowItemFrame.java new file mode 100644 index 0000000000..cee9331ffd --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCGlowItemFrame.java @@ -0,0 +1,5 @@ +package com.laytonsmith.abstraction.entities; + +public interface MCGlowItemFrame extends MCItemFrame { + +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCGoat.java b/src/main/java/com/laytonsmith/abstraction/entities/MCGoat.java new file mode 100644 index 0000000000..c6bf22b84d --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCGoat.java @@ -0,0 +1,6 @@ +package com.laytonsmith.abstraction.entities; + +public interface MCGoat extends MCAnimal { + boolean isScreaming(); + void setScreaming(boolean screaming); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCGuardian.java b/src/main/java/com/laytonsmith/abstraction/entities/MCGuardian.java new file mode 100644 index 0000000000..2d7e693667 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCGuardian.java @@ -0,0 +1,6 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCLivingEntity; + +public interface MCGuardian extends MCLivingEntity { +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCHanging.java b/src/main/java/com/laytonsmith/abstraction/entities/MCHanging.java new file mode 100644 index 0000000000..825b7b2c55 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCHanging.java @@ -0,0 +1,13 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCEntity; +import com.laytonsmith.abstraction.blocks.MCBlockFace; + +public interface MCHanging extends MCEntity { + + MCBlockFace getFacing(); + + void setFacingDirection(MCBlockFace direction); + + boolean setFacingDirection(MCBlockFace direction, boolean force); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCHoglin.java b/src/main/java/com/laytonsmith/abstraction/entities/MCHoglin.java new file mode 100644 index 0000000000..09dade8fc9 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCHoglin.java @@ -0,0 +1,5 @@ +package com.laytonsmith.abstraction.entities; + +public interface MCHoglin extends MCAnimal { + +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCHopperMinecart.java b/src/main/java/com/laytonsmith/abstraction/entities/MCHopperMinecart.java new file mode 100644 index 0000000000..85e2c4e1c7 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCHopperMinecart.java @@ -0,0 +1,7 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCInventoryHolder; + +public interface MCHopperMinecart extends MCMinecart, MCInventoryHolder { + +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCHorse.java b/src/main/java/com/laytonsmith/abstraction/entities/MCHorse.java index 9c6c1fdd03..247a64046a 100644 --- a/src/main/java/com/laytonsmith/abstraction/entities/MCHorse.java +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCHorse.java @@ -2,46 +2,46 @@ import com.laytonsmith.abstraction.MCInventoryHolder; import com.laytonsmith.abstraction.MCItemStack; -import com.laytonsmith.abstraction.MCTameable; -import com.laytonsmith.abstraction.MCVehicle; import com.laytonsmith.annotations.MEnum; public interface MCHorse extends MCTameable, MCVehicle, MCInventoryHolder { - @MEnum("HorseVariant") - public enum MCHorseVariant { - HORSE, DONKEY, MULE, SKELETON, ZOMBIE - } - @MEnum("HorseColor") - public enum MCHorseColor { + + @MEnum("com.commandhelper.HorseColor") + enum MCHorseColor { BLACK, BROWN, CHESTNUT, CREAMY, DARK_BROWN, GRAY, WHITE } - @MEnum("HorsePattern") - public enum MCHorsePattern { + + @MEnum("com.commandhelper.HorsePattern") + enum MCHorsePattern { NONE, SOCKS, WHITEFIELD, WHITE_DOTS, BLACK_DOTS } - - public MCHorseVariant getVariant(); - public MCHorseColor getColor(); - public MCHorsePattern getPattern(); - - public void setVariant(MCHorseVariant variant); - public void setColor(MCHorseColor color); - public void setPattern(MCHorsePattern pattern); - - public double getJumpStrength(); - public void setJumpStrength(double strength); - - public boolean hasChest(); - public void setHasChest(boolean hasChest); - - public int getDomestication(); - public int getMaxDomestication(); - public void setDomestication(int level); - public void setMaxDomestication(int level); - + + MCHorseColor getColor(); + + MCHorsePattern getPattern(); + + void setColor(MCHorseColor color); + + void setPattern(MCHorsePattern pattern); + + double getJumpStrength(); + + void setJumpStrength(double strength); + + int getDomestication(); + + int getMaxDomestication(); + + void setDomestication(int level); + + void setMaxDomestication(int level); + // Inventory - public void setSaddle(MCItemStack stack); - public MCItemStack getSaddle(); - public void setArmor(MCItemStack stack); - public MCItemStack getArmor(); + void setSaddle(MCItemStack stack); + + MCItemStack getSaddle(); + + void setArmor(MCItemStack stack); + + MCItemStack getArmor(); } diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCHusk.java b/src/main/java/com/laytonsmith/abstraction/entities/MCHusk.java new file mode 100644 index 0000000000..10257d1aed --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCHusk.java @@ -0,0 +1,4 @@ +package com.laytonsmith.abstraction.entities; + +public interface MCHusk extends MCZombie { +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCInteraction.java b/src/main/java/com/laytonsmith/abstraction/entities/MCInteraction.java new file mode 100644 index 0000000000..08eea1ed98 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCInteraction.java @@ -0,0 +1,35 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCEntity; + +import java.util.UUID; + +public interface MCInteraction extends MCEntity { + double getWidth(); + void setWidth(double width); + double getHeight(); + void setHeight(double height); + boolean isResponsive(); + void setResponsive(boolean response); + MCPreviousInteraction getLastAttack(); + MCPreviousInteraction getLastInteraction(); + + class MCPreviousInteraction { + private final UUID uuid; + private final long timestamp; + + public MCPreviousInteraction(UUID uuid, long timestamp) { + this.uuid = uuid; + this.timestamp = timestamp; + } + + public UUID getUuid() { + return uuid; + } + + public long getTimestamp() { + return timestamp; + } + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCIronGolem.java b/src/main/java/com/laytonsmith/abstraction/entities/MCIronGolem.java index 99d627769e..4715e303d2 100644 --- a/src/main/java/com/laytonsmith/abstraction/entities/MCIronGolem.java +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCIronGolem.java @@ -2,11 +2,9 @@ import com.laytonsmith.abstraction.MCLivingEntity; -/** - * - * @author Hekta - */ public interface MCIronGolem extends MCLivingEntity { - public boolean isPlayerCreated(); - public void setPlayerCreated(boolean playerCreated); -} \ No newline at end of file + + boolean isPlayerCreated(); + + void setPlayerCreated(boolean playerCreated); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCItem.java b/src/main/java/com/laytonsmith/abstraction/entities/MCItem.java new file mode 100644 index 0000000000..815ff6ef7d --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCItem.java @@ -0,0 +1,29 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCEntity; +import com.laytonsmith.abstraction.MCItemStack; + +import java.util.UUID; + +public interface MCItem extends MCEntity { + + MCItemStack getItemStack(); + + int getPickupDelay(); + + void setItemStack(MCItemStack stack); + + void setPickupDelay(int delay); + + UUID getOwner(); + + void setOwner(UUID owner); + + UUID getThrower(); + + void setThrower(UUID thrower); + + boolean willDespawn(); + + void setWillDespawn(boolean despawn); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCItemDisplay.java b/src/main/java/com/laytonsmith/abstraction/entities/MCItemDisplay.java new file mode 100644 index 0000000000..3ddfedf610 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCItemDisplay.java @@ -0,0 +1,26 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCItemStack; + +public interface MCItemDisplay extends MCDisplay { + + MCItemStack getItem(); + + void setItem(MCItemStack item); + + ModelTransform getItemModelTransform(); + + void setItemModelTransform(ModelTransform transform); + + enum ModelTransform { + FIRSTPERSON_LEFTHAND, + FIRSTPERSON_RIGHTHAND, + FIXED, + GROUND, + GUI, + HEAD, + NONE, + THIRDPERSON_LEFTHAND, + THIRDPERSON_RIGHTHAND + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCItemFrame.java b/src/main/java/com/laytonsmith/abstraction/entities/MCItemFrame.java index 94f1cae515..d69cffe994 100644 --- a/src/main/java/com/laytonsmith/abstraction/entities/MCItemFrame.java +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCItemFrame.java @@ -1,18 +1,23 @@ package com.laytonsmith.abstraction.entities; -import com.laytonsmith.abstraction.MCHanging; import com.laytonsmith.abstraction.MCItemStack; import com.laytonsmith.abstraction.enums.MCRotation; -/** - * - * @author Hekta - */ public interface MCItemFrame extends MCHanging { - public MCItemStack getItem(); - public void setItem(MCItemStack item); + MCItemStack getItem(); - public MCRotation getRotation(); - public void setRotation(MCRotation rotation); -} \ No newline at end of file + void setItem(MCItemStack item); + + MCRotation getRotation(); + + void setRotation(MCRotation rotation); + + boolean isVisible(); + + void setVisible(boolean visible); + + boolean isFixed(); + + void setFixed(boolean fixed); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCItemProjectile.java b/src/main/java/com/laytonsmith/abstraction/entities/MCItemProjectile.java new file mode 100644 index 0000000000..257b7f5658 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCItemProjectile.java @@ -0,0 +1,9 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.MCMetadatable; + +public interface MCItemProjectile extends MCProjectile, MCMetadatable { + MCItemStack getItem(); + void setItem(MCItemStack item); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCLightningStrike.java b/src/main/java/com/laytonsmith/abstraction/entities/MCLightningStrike.java new file mode 100644 index 0000000000..66aa1a1417 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCLightningStrike.java @@ -0,0 +1,8 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCEntity; + +public interface MCLightningStrike extends MCEntity { + + boolean isEffect(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCLlama.java b/src/main/java/com/laytonsmith/abstraction/entities/MCLlama.java new file mode 100644 index 0000000000..5f92925cc5 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCLlama.java @@ -0,0 +1,15 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.annotations.MEnum; + +public interface MCLlama extends MCChestedHorse { + + @MEnum("com.commandhelper.LlamaColor") + enum MCLlamaColor { + CREAMY, WHITE, BROWN, GRAY + } + + MCLlamaColor getLlamaColor(); + + void setLlamaColor(MCLlamaColor color); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCLlamaSpit.java b/src/main/java/com/laytonsmith/abstraction/entities/MCLlamaSpit.java new file mode 100644 index 0000000000..7710a8cd1c --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCLlamaSpit.java @@ -0,0 +1,4 @@ +package com.laytonsmith.abstraction.entities; + +public interface MCLlamaSpit extends MCProjectile { +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCMagmaCube.java b/src/main/java/com/laytonsmith/abstraction/entities/MCMagmaCube.java index cfa763a86d..7c65a916e3 100644 --- a/src/main/java/com/laytonsmith/abstraction/entities/MCMagmaCube.java +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCMagmaCube.java @@ -1,9 +1,5 @@ package com.laytonsmith.abstraction.entities; -/** - * - * @author Hekta - */ public interface MCMagmaCube extends MCSlime { -} \ No newline at end of file +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCMannequin.java b/src/main/java/com/laytonsmith/abstraction/entities/MCMannequin.java new file mode 100644 index 0000000000..fd82105529 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCMannequin.java @@ -0,0 +1,8 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCLivingEntity; + +public interface MCMannequin extends MCLivingEntity { + boolean isImmovable(); + void setImmovable(boolean immovable); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCMinecart.java b/src/main/java/com/laytonsmith/abstraction/entities/MCMinecart.java index 9e65d11d20..c5110375f4 100644 --- a/src/main/java/com/laytonsmith/abstraction/entities/MCMinecart.java +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCMinecart.java @@ -1,12 +1,26 @@ package com.laytonsmith.abstraction.entities; -import com.laytonsmith.abstraction.MCVehicle; +import com.laytonsmith.abstraction.blocks.MCBlockData; public interface MCMinecart extends MCVehicle { - public void setDamage(double damage); - public double getDamage(); - public double getMaxSpeed(); - public void setMaxSpeed(double speed); - public boolean isSlowWhenEmpty(); - public void setSlowWhenEmpty(boolean slow); + + void setDamage(double damage); + + double getDamage(); + + double getMaxSpeed(); + + void setMaxSpeed(double speed); + + boolean isSlowWhenEmpty(); + + void setSlowWhenEmpty(boolean slow); + + void setDisplayBlock(MCBlockData data); + + MCBlockData getDisplayBlock(); + + void setDisplayBlockOffset(int offset); + + int getDisplayBlockOffset(); } diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCMule.java b/src/main/java/com/laytonsmith/abstraction/entities/MCMule.java new file mode 100644 index 0000000000..a36432c92e --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCMule.java @@ -0,0 +1,4 @@ +package com.laytonsmith.abstraction.entities; + +public interface MCMule extends MCChestedHorse { +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCMushroomCow.java b/src/main/java/com/laytonsmith/abstraction/entities/MCMushroomCow.java new file mode 100644 index 0000000000..ecf3772578 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCMushroomCow.java @@ -0,0 +1,8 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.enums.MCMushroomCowType; + +public interface MCMushroomCow extends MCAnimal { + MCMushroomCowType getVariant(); + void setVariant(MCMushroomCowType type); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCOcelot.java b/src/main/java/com/laytonsmith/abstraction/entities/MCOcelot.java index 73e29f0c2d..a1c2b258b7 100644 --- a/src/main/java/com/laytonsmith/abstraction/entities/MCOcelot.java +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCOcelot.java @@ -1,16 +1,5 @@ package com.laytonsmith.abstraction.entities; -import com.laytonsmith.abstraction.MCTameable; -import com.laytonsmith.abstraction.enums.MCOcelotType; +public interface MCOcelot extends MCAnimal { -/** - * - * @author jb_aero - */ -public interface MCOcelot extends MCTameable { - - MCOcelotType getCatType(); - boolean isSitting(); - void setCatType(MCOcelotType type); - void setSitting(boolean sitting); } diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCOminousItemSpawner.java b/src/main/java/com/laytonsmith/abstraction/entities/MCOminousItemSpawner.java new file mode 100644 index 0000000000..c04c8eac4b --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCOminousItemSpawner.java @@ -0,0 +1,15 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCEntity; +import com.laytonsmith.abstraction.MCItemStack; + +public interface MCOminousItemSpawner extends MCEntity { + + MCItemStack getItem(); + + void setItem(MCItemStack item); + + long getDelay(); + + void setDelay(long delay); +} diff --git a/src/main/java/com/laytonsmith/abstraction/MCPainting.java b/src/main/java/com/laytonsmith/abstraction/entities/MCPainting.java similarity index 79% rename from src/main/java/com/laytonsmith/abstraction/MCPainting.java rename to src/main/java/com/laytonsmith/abstraction/entities/MCPainting.java index 5f5e3f2eac..c8c0ab8a2c 100644 --- a/src/main/java/com/laytonsmith/abstraction/MCPainting.java +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCPainting.java @@ -1,12 +1,12 @@ -package com.laytonsmith.abstraction; +package com.laytonsmith.abstraction.entities; import com.laytonsmith.abstraction.enums.MCArt; -/** - * - */ public interface MCPainting extends MCHanging { + MCArt getArt(); + boolean setArt(MCArt art); + boolean setArt(MCArt art, boolean force); } diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCPanda.java b/src/main/java/com/laytonsmith/abstraction/entities/MCPanda.java new file mode 100644 index 0000000000..cf30d906f9 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCPanda.java @@ -0,0 +1,25 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.annotations.MEnum; + +public interface MCPanda extends MCAnimal { + + @MEnum("com.commandhelper.PandaGene") + enum Gene { + AGGRESSIVE, BROWN, LAZY, NORMAL, PLAYFUL, WEAK, WORRIED + } + + MCPanda.Gene getMainGene(); + void setMainGene(Gene gene); + MCPanda.Gene getHiddenGene(); + void setHiddenGene(Gene gene); + boolean isRolling(); + void setRolling(boolean rolling); + boolean isSneezing(); + void setSneezing(boolean sneezing); + boolean isEating(); + void setEating(boolean eating); + boolean isOnBack(); + void setOnBack(boolean onBack); + +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCParrot.java b/src/main/java/com/laytonsmith/abstraction/entities/MCParrot.java new file mode 100644 index 0000000000..8764f426ea --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCParrot.java @@ -0,0 +1,14 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.enums.MCParrotType; + +public interface MCParrot extends MCTameable { + + boolean isSitting(); + + void setSitting(boolean sitting); + + MCParrotType getVariant(); + + void setVariant(MCParrotType variant); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCPhantom.java b/src/main/java/com/laytonsmith/abstraction/entities/MCPhantom.java new file mode 100644 index 0000000000..81345d0e65 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCPhantom.java @@ -0,0 +1,8 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCLivingEntity; + +public interface MCPhantom extends MCLivingEntity { + int getSize(); + void setSize(int size); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCPig.java b/src/main/java/com/laytonsmith/abstraction/entities/MCPig.java index 6322f799bf..fdae7fa8bc 100644 --- a/src/main/java/com/laytonsmith/abstraction/entities/MCPig.java +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCPig.java @@ -1,9 +1,8 @@ package com.laytonsmith.abstraction.entities; -import com.laytonsmith.abstraction.MCAgeable; -import com.laytonsmith.abstraction.MCVehicle; +public interface MCPig extends MCAnimal, MCVehicle { -public interface MCPig extends MCAgeable, MCVehicle { - public boolean isSaddled(); - public void setSaddled(boolean saddled); + boolean isSaddled(); + + void setSaddled(boolean saddled); } diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCPigZombie.java b/src/main/java/com/laytonsmith/abstraction/entities/MCPigZombie.java index 0907697c55..e62cbdb195 100644 --- a/src/main/java/com/laytonsmith/abstraction/entities/MCPigZombie.java +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCPigZombie.java @@ -1,14 +1,12 @@ package com.laytonsmith.abstraction.entities; -/** - * - * @author Hekta - */ public interface MCPigZombie extends MCZombie { - public int getAnger(); - public void setAnger(int anger); + int getAnger(); - public boolean isAngry(); - public void setAngry(boolean isAngry); -} \ No newline at end of file + void setAnger(int anger); + + boolean isAngry(); + + void setAngry(boolean isAngry); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCPiglin.java b/src/main/java/com/laytonsmith/abstraction/entities/MCPiglin.java new file mode 100644 index 0000000000..db9080d695 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCPiglin.java @@ -0,0 +1,6 @@ +package com.laytonsmith.abstraction.entities; + +public interface MCPiglin extends MCAgeable { + boolean isImmuneToZombification(); + void setImmuneToZombification(boolean immune); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCWither.java b/src/main/java/com/laytonsmith/abstraction/entities/MCPiglinBrute.java similarity index 55% rename from src/main/java/com/laytonsmith/abstraction/entities/MCWither.java rename to src/main/java/com/laytonsmith/abstraction/entities/MCPiglinBrute.java index 276be8845c..a0354179c4 100644 --- a/src/main/java/com/laytonsmith/abstraction/entities/MCWither.java +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCPiglinBrute.java @@ -2,10 +2,6 @@ import com.laytonsmith.abstraction.MCLivingEntity; -/** - * - * @author Hekta - */ -public interface MCWither extends MCLivingEntity { +public interface MCPiglinBrute extends MCLivingEntity { -} \ No newline at end of file +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCPillager.java b/src/main/java/com/laytonsmith/abstraction/entities/MCPillager.java new file mode 100644 index 0000000000..c88b0c5809 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCPillager.java @@ -0,0 +1,6 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCInventoryHolder; + +public interface MCPillager extends MCInventoryHolder { +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCProjectile.java b/src/main/java/com/laytonsmith/abstraction/entities/MCProjectile.java new file mode 100644 index 0000000000..c257b043bc --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCProjectile.java @@ -0,0 +1,16 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCEntity; +import com.laytonsmith.abstraction.MCMetadatable; +import com.laytonsmith.abstraction.MCProjectileSource; + +public interface MCProjectile extends MCEntity, MCMetadatable { + + boolean doesBounce(); + + MCProjectileSource getShooter(); + + void setBounce(boolean doesBounce); + + void setShooter(MCProjectileSource shooter); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCPufferfish.java b/src/main/java/com/laytonsmith/abstraction/entities/MCPufferfish.java new file mode 100644 index 0000000000..ae97ea50c2 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCPufferfish.java @@ -0,0 +1,6 @@ +package com.laytonsmith.abstraction.entities; + +public interface MCPufferfish { + int getPuffState(); + void setPuffState(int state); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCRabbit.java b/src/main/java/com/laytonsmith/abstraction/entities/MCRabbit.java new file mode 100644 index 0000000000..ea98c1cc13 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCRabbit.java @@ -0,0 +1,10 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.enums.MCRabbitType; + +public interface MCRabbit extends MCAnimal { + + MCRabbitType getRabbitType(); + + void setRabbitType(MCRabbitType type); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCSalmon.java b/src/main/java/com/laytonsmith/abstraction/entities/MCSalmon.java new file mode 100644 index 0000000000..382b806350 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCSalmon.java @@ -0,0 +1,16 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCLivingEntity; + +public interface MCSalmon extends MCLivingEntity { + + Variant getVariant(); + void setVariant(Variant size); + + enum Variant { + SMALL, + MEDIUM, + LARGE + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCSheep.java b/src/main/java/com/laytonsmith/abstraction/entities/MCSheep.java index 12819d4095..21e317d49e 100644 --- a/src/main/java/com/laytonsmith/abstraction/entities/MCSheep.java +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCSheep.java @@ -1,15 +1,14 @@ package com.laytonsmith.abstraction.entities; -import com.laytonsmith.abstraction.MCAgeable; import com.laytonsmith.abstraction.enums.MCDyeColor; -public interface MCSheep extends MCAgeable { - - public MCDyeColor getColor(); - - public void setColor(MCDyeColor color); - - public boolean isSheared(); - - public void setSheared(boolean shear); +public interface MCSheep extends MCAnimal { + + MCDyeColor getColor(); + + void setColor(MCDyeColor color); + + boolean isSheared(); + + void setSheared(boolean shear); } diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCShulker.java b/src/main/java/com/laytonsmith/abstraction/entities/MCShulker.java new file mode 100644 index 0000000000..0e87ca7fde --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCShulker.java @@ -0,0 +1,11 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCLivingEntity; +import com.laytonsmith.abstraction.enums.MCDyeColor; + +public interface MCShulker extends MCLivingEntity { + + MCDyeColor getColor(); + void setColor(MCDyeColor color); + +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCShulkerBullet.java b/src/main/java/com/laytonsmith/abstraction/entities/MCShulkerBullet.java new file mode 100644 index 0000000000..8b248ef854 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCShulkerBullet.java @@ -0,0 +1,11 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCEntity; + +public interface MCShulkerBullet extends MCProjectile { + + void setTarget(MCEntity entity); + + MCEntity getTarget(); + +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCSkeleton.java b/src/main/java/com/laytonsmith/abstraction/entities/MCSkeleton.java index 242a6c6abd..3474b9c935 100644 --- a/src/main/java/com/laytonsmith/abstraction/entities/MCSkeleton.java +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCSkeleton.java @@ -1,13 +1,6 @@ package com.laytonsmith.abstraction.entities; import com.laytonsmith.abstraction.MCLivingEntity; -import com.laytonsmith.abstraction.enums.MCSkeletonType; -/** - * - * @author Hekta - */ public interface MCSkeleton extends MCLivingEntity { - public MCSkeletonType getSkeletonType(); - public void setSkeletonType(MCSkeletonType type); -} \ No newline at end of file +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCSkeletonHorse.java b/src/main/java/com/laytonsmith/abstraction/entities/MCSkeletonHorse.java new file mode 100644 index 0000000000..efcaedf219 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCSkeletonHorse.java @@ -0,0 +1,4 @@ +package com.laytonsmith.abstraction.entities; + +public interface MCSkeletonHorse extends MCAbstractHorse { +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCSlime.java b/src/main/java/com/laytonsmith/abstraction/entities/MCSlime.java index 5a242910e7..6c36b89daf 100644 --- a/src/main/java/com/laytonsmith/abstraction/entities/MCSlime.java +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCSlime.java @@ -2,11 +2,9 @@ import com.laytonsmith.abstraction.MCLivingEntity; -/** - * - * @author Hekta - */ public interface MCSlime extends MCLivingEntity { - public int getSize(); - public void setSize(int size); -} \ No newline at end of file + + int getSize(); + + void setSize(int size); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCSniffer.java b/src/main/java/com/laytonsmith/abstraction/entities/MCSniffer.java new file mode 100644 index 0000000000..ea5d92a2cb --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCSniffer.java @@ -0,0 +1,4 @@ +package com.laytonsmith.abstraction.entities; + +public interface MCSniffer extends MCAnimal { +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCSnowball.java b/src/main/java/com/laytonsmith/abstraction/entities/MCSnowball.java deleted file mode 100644 index 77f5f5e8aa..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/entities/MCSnowball.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.laytonsmith.abstraction.entities; - -import com.laytonsmith.abstraction.MCProjectile; - -/** - * - * @author Hekta - */ -public interface MCSnowball extends MCProjectile { - -} \ No newline at end of file diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCSnowman.java b/src/main/java/com/laytonsmith/abstraction/entities/MCSnowman.java new file mode 100644 index 0000000000..549218acdf --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCSnowman.java @@ -0,0 +1,10 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCLivingEntity; + +public interface MCSnowman extends MCLivingEntity { + + void setDerp(boolean derp); + + boolean isDerp(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCSpectralArrow.java b/src/main/java/com/laytonsmith/abstraction/entities/MCSpectralArrow.java new file mode 100644 index 0000000000..17fe3f3fb8 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCSpectralArrow.java @@ -0,0 +1,12 @@ +package com.laytonsmith.abstraction.entities; + +public interface MCSpectralArrow extends MCProjectile { + int getKnockbackStrength(); + void setKnockbackStrength(int strength); + boolean isCritical(); + void setCritical(boolean critical); + double getDamage(); + void setDamage(double damage); + int getGlowingTicks(); + void setGlowingTicks(int ticks); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCStorageMinecart.java b/src/main/java/com/laytonsmith/abstraction/entities/MCStorageMinecart.java new file mode 100644 index 0000000000..4b74bd84ae --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCStorageMinecart.java @@ -0,0 +1,7 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCInventoryHolder; + +public interface MCStorageMinecart extends MCMinecart, MCInventoryHolder { + +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCStray.java b/src/main/java/com/laytonsmith/abstraction/entities/MCStray.java new file mode 100644 index 0000000000..1ae0f18861 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCStray.java @@ -0,0 +1,4 @@ +package com.laytonsmith.abstraction.entities; + +public interface MCStray extends MCSkeleton { +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCStrider.java b/src/main/java/com/laytonsmith/abstraction/entities/MCStrider.java new file mode 100644 index 0000000000..8c28d1c93c --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCStrider.java @@ -0,0 +1,6 @@ +package com.laytonsmith.abstraction.entities; + +public interface MCStrider extends MCAnimal, MCVehicle { + boolean isSaddled(); + void setSaddled(boolean saddled); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCTNT.java b/src/main/java/com/laytonsmith/abstraction/entities/MCTNT.java new file mode 100644 index 0000000000..9cf360ab67 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCTNT.java @@ -0,0 +1,14 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCEntity; + +public interface MCTNT extends MCEntity { + + MCEntity getSource(); + + void setSource(MCEntity source); + + int getFuseTicks(); + + void setFuseTicks(int ticks); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCTadpole.java b/src/main/java/com/laytonsmith/abstraction/entities/MCTadpole.java new file mode 100644 index 0000000000..3ab86d66d5 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCTadpole.java @@ -0,0 +1,6 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCLivingEntity; + +public interface MCTadpole extends MCLivingEntity { +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCTameable.java b/src/main/java/com/laytonsmith/abstraction/entities/MCTameable.java new file mode 100644 index 0000000000..5ed459ab4c --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCTameable.java @@ -0,0 +1,14 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCAnimalTamer; + +public interface MCTameable extends MCAnimal { + + boolean isTamed(); + + void setTamed(boolean bln); + + MCAnimalTamer getOwner(); + + void setOwner(MCAnimalTamer at); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCTextDisplay.java b/src/main/java/com/laytonsmith/abstraction/entities/MCTextDisplay.java new file mode 100644 index 0000000000..71a99d927c --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCTextDisplay.java @@ -0,0 +1,40 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCColor; + +public interface MCTextDisplay extends MCDisplay { + + Alignment getAlignment(); + + void setAlignment(Alignment alignment); + + MCColor getBackgroundColor(); + + void setBackgroundColor(MCColor color); + + int getLineWidth(); + + void setLineWidth(int width); + + boolean isVisibleThroughBlocks(); + + void setVisibleThroughBlocks(boolean visible); + + boolean hasShadow(); + + void setHasShadow(boolean hasShadow); + + String getText(); + + void setText(String text); + + byte getOpacity(); + + void setOpacity(byte opacity); + + enum Alignment { + CENTER, + LEFT, + RIGHT + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCThrownPotion.java b/src/main/java/com/laytonsmith/abstraction/entities/MCThrownPotion.java index 18712ff57e..89d7582363 100644 --- a/src/main/java/com/laytonsmith/abstraction/entities/MCThrownPotion.java +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCThrownPotion.java @@ -1,14 +1,10 @@ package com.laytonsmith.abstraction.entities; import com.laytonsmith.abstraction.MCItemStack; -import com.laytonsmith.abstraction.MCProjectile; -/** - * - * @author Hekta - */ public interface MCThrownPotion extends MCProjectile { - public MCItemStack getItem(); - public void setItem(MCItemStack item); -} \ No newline at end of file + MCItemStack getItem(); + + void setItem(MCItemStack item); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCTrader.java b/src/main/java/com/laytonsmith/abstraction/entities/MCTrader.java new file mode 100644 index 0000000000..6bd568975b --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCTrader.java @@ -0,0 +1,9 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCInventoryHolder; +import com.laytonsmith.abstraction.MCMerchant; + +public interface MCTrader extends MCBreedable, MCInventoryHolder { + + MCMerchant asMerchant(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCTransformation.java b/src/main/java/com/laytonsmith/abstraction/entities/MCTransformation.java new file mode 100644 index 0000000000..f102748808 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCTransformation.java @@ -0,0 +1,20 @@ +package com.laytonsmith.abstraction.entities; + +import org.joml.Quaternionf; +import org.joml.Vector3f; + +/** + * + * @author Cailin + */ +public interface MCTransformation { + + public Vector3f getTranslation(); + + public Quaternionf getLeftRotation(); + + public Vector3f getScale(); + + public Quaternionf getRightRotation(); + +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCTrident.java b/src/main/java/com/laytonsmith/abstraction/entities/MCTrident.java new file mode 100644 index 0000000000..77218d3087 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCTrident.java @@ -0,0 +1,10 @@ +package com.laytonsmith.abstraction.entities; + +public interface MCTrident extends MCProjectile { + int getKnockbackStrength(); + void setKnockbackStrength(int strength); + boolean isCritical(); + void setCritical(boolean critical); + double getDamage(); + void setDamage(double damage); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCTropicalFish.java b/src/main/java/com/laytonsmith/abstraction/entities/MCTropicalFish.java new file mode 100644 index 0000000000..b73ef927a1 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCTropicalFish.java @@ -0,0 +1,32 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCLivingEntity; +import com.laytonsmith.abstraction.enums.MCDyeColor; +import com.laytonsmith.annotations.MEnum; + +public interface MCTropicalFish extends MCLivingEntity { + + MCDyeColor getPatternColor(); + void setPatternColor(MCDyeColor color); + MCDyeColor getBodyColor(); + void setBodyColor(MCDyeColor color); + MCTropicalFish.MCPattern getPattern(); + void setPattern(MCTropicalFish.MCPattern pattern); + + @MEnum("com.commandhelper.TropicalFishPattern") + enum MCPattern { + KOB, + SUNSTREAK, + SNOOPER, + DASHER, + BRINELY, + SPOTTY, + FLOPPER, + STRIPEY, + GLITTER, + BLOCKFISH, + BETTY, + CLAYFISH + } + +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCVehicle.java b/src/main/java/com/laytonsmith/abstraction/entities/MCVehicle.java new file mode 100644 index 0000000000..0c7293d948 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCVehicle.java @@ -0,0 +1,7 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCEntity; + +public interface MCVehicle extends MCEntity { + // Look at ALL the functions! +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCVex.java b/src/main/java/com/laytonsmith/abstraction/entities/MCVex.java new file mode 100644 index 0000000000..e7fe816665 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCVex.java @@ -0,0 +1,8 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCLivingEntity; + +public interface MCVex extends MCLivingEntity { + boolean isCharging(); + void setCharging(boolean charging); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCVillager.java b/src/main/java/com/laytonsmith/abstraction/entities/MCVillager.java index 8f9e616d33..b40f491677 100644 --- a/src/main/java/com/laytonsmith/abstraction/entities/MCVillager.java +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCVillager.java @@ -1,13 +1,13 @@ package com.laytonsmith.abstraction.entities; -import com.laytonsmith.abstraction.MCAgeable; import com.laytonsmith.abstraction.enums.MCProfession; -/** - * - * @author Hekta - */ -public interface MCVillager extends MCAgeable { - public MCProfession getProfession(); - public void setProfession(MCProfession profession); -} \ No newline at end of file +public interface MCVillager extends MCTrader { + + MCProfession getProfession(); + void setProfession(MCProfession profession); + int getLevel(); + void setLevel(int level); + int getExperience(); + void setExperience(int exp); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCVindicator.java b/src/main/java/com/laytonsmith/abstraction/entities/MCVindicator.java new file mode 100644 index 0000000000..aa206f1072 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCVindicator.java @@ -0,0 +1,6 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCLivingEntity; + +public interface MCVindicator extends MCLivingEntity { +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCWanderingTrader.java b/src/main/java/com/laytonsmith/abstraction/entities/MCWanderingTrader.java new file mode 100644 index 0000000000..a3ca2025ce --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCWanderingTrader.java @@ -0,0 +1,4 @@ +package com.laytonsmith.abstraction.entities; + +public interface MCWanderingTrader extends MCTrader { +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCWarden.java b/src/main/java/com/laytonsmith/abstraction/entities/MCWarden.java new file mode 100644 index 0000000000..91338e6a20 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCWarden.java @@ -0,0 +1,6 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.MCLivingEntity; + +public interface MCWarden extends MCLivingEntity { +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCWitch.java b/src/main/java/com/laytonsmith/abstraction/entities/MCWitch.java index ba5f05cdb6..97273c98e1 100644 --- a/src/main/java/com/laytonsmith/abstraction/entities/MCWitch.java +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCWitch.java @@ -2,10 +2,6 @@ import com.laytonsmith.abstraction.MCLivingEntity; -/** - * - * @author Hekta - */ public interface MCWitch extends MCLivingEntity { -} \ No newline at end of file +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCWitherSkeleton.java b/src/main/java/com/laytonsmith/abstraction/entities/MCWitherSkeleton.java new file mode 100644 index 0000000000..d37741750e --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCWitherSkeleton.java @@ -0,0 +1,4 @@ +package com.laytonsmith.abstraction.entities; + +public interface MCWitherSkeleton extends MCSkeleton { +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCWitherSkull.java b/src/main/java/com/laytonsmith/abstraction/entities/MCWitherSkull.java index de9e2aa139..6859bdce1c 100644 --- a/src/main/java/com/laytonsmith/abstraction/entities/MCWitherSkull.java +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCWitherSkull.java @@ -1,13 +1,8 @@ package com.laytonsmith.abstraction.entities; -import com.laytonsmith.abstraction.MCFireball; - -/** - * - * @author Veyyn - */ public interface MCWitherSkull extends MCFireball { - public boolean isCharged(); - public void setCharged(boolean charged); -} \ No newline at end of file + boolean isCharged(); + + void setCharged(boolean charged); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCWolf.java b/src/main/java/com/laytonsmith/abstraction/entities/MCWolf.java index 79a3171e6a..40db613ba3 100644 --- a/src/main/java/com/laytonsmith/abstraction/entities/MCWolf.java +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCWolf.java @@ -1,19 +1,38 @@ package com.laytonsmith.abstraction.entities; -import com.laytonsmith.abstraction.MCTameable; import com.laytonsmith.abstraction.enums.MCDyeColor; -/** - * - * @author jb_aero - */ public interface MCWolf extends MCTameable { MCDyeColor getCollarColor(); + boolean isAngry(); + boolean isSitting(); + void setAngry(boolean angry); + void setSitting(boolean sitting); + void setCollarColor(MCDyeColor color); - + + boolean isInterested(); + + void setInterested(boolean interested); + + Variant getWolfVariant(); + + void setWolfVariant(Variant variant); + + enum Variant { + ASHEN, + BLACK, + CHESTNUT, + PALE, + RUSTY, + SNOWY, + SPOTTED, + STRIPED, + WOODS + } } diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCZoglin.java b/src/main/java/com/laytonsmith/abstraction/entities/MCZoglin.java new file mode 100644 index 0000000000..5cbfaf7306 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCZoglin.java @@ -0,0 +1,4 @@ +package com.laytonsmith.abstraction.entities; + +public interface MCZoglin extends MCAgeable { +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCZombie.java b/src/main/java/com/laytonsmith/abstraction/entities/MCZombie.java index 0fe3b55d45..a742d89c48 100644 --- a/src/main/java/com/laytonsmith/abstraction/entities/MCZombie.java +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCZombie.java @@ -1,16 +1,8 @@ package com.laytonsmith.abstraction.entities; -import com.laytonsmith.abstraction.MCLivingEntity; +public interface MCZombie extends MCAgeable { -/** - * - * @author Hekta - */ -public interface MCZombie extends MCLivingEntity { + boolean canBreakDoors(); - public boolean isBaby(); - public void setBaby(boolean isBaby); - - public boolean isVillager(); - public void setVillager(boolean isVillager); -} \ No newline at end of file + void setCanBreakDoors(boolean canBreakDoors); +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCZombieHorse.java b/src/main/java/com/laytonsmith/abstraction/entities/MCZombieHorse.java new file mode 100644 index 0000000000..90fc3dd5bf --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCZombieHorse.java @@ -0,0 +1,4 @@ +package com.laytonsmith.abstraction.entities; + +public interface MCZombieHorse extends MCAbstractHorse { +} diff --git a/src/main/java/com/laytonsmith/abstraction/entities/MCZombieVillager.java b/src/main/java/com/laytonsmith/abstraction/entities/MCZombieVillager.java new file mode 100644 index 0000000000..9919ec9149 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/entities/MCZombieVillager.java @@ -0,0 +1,11 @@ +package com.laytonsmith.abstraction.entities; + +import com.laytonsmith.abstraction.enums.MCProfession; + +public interface MCZombieVillager extends MCZombie { + + MCProfession getProfession(); + + void setProfession(MCProfession profession); + +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/EnumConvertor.java b/src/main/java/com/laytonsmith/abstraction/enums/EnumConvertor.java index 81b83d2381..009112bf1a 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/EnumConvertor.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/EnumConvertor.java @@ -1,137 +1,102 @@ - package com.laytonsmith.abstraction.enums; import com.laytonsmith.annotations.abstractionenum; -import com.laytonsmith.core.CHLog; -import com.laytonsmith.core.LogLevel; +import com.laytonsmith.core.MSLog; import com.laytonsmith.core.constructs.Target; /** - * Maps an enum class to another enum class. By default, the converter uses a heuristic - * to convert the enums. The general convention is usually that enums are named the same, except - * in one off cases. If the enum isn't mapped 1:1, then the converter will need to provide the - * conversion information by overriding the get(Abstracted|Concrete)EnumCustom methods, which - * contain a switch (or similar) statement that does the proper conversion. Usually both - * or neither methods will need overriding. There is a runtime unit test, which checks - * the enums for missing values at runtime, and reports any values that can't be converted, allowing - * for easier and faster identification of failure conditions. This is not an ideal model, but - * does allow for enums themselves to be abstracted away from a particular platform. + * Maps an enum class to another enum class. By default, the converter uses a heuristic to convert the enums. The + * general convention is usually that enums are named the same, except in one off cases. If the enum isn't mapped 1:1, + * then the converter will need to provide the conversion information by overriding the + * get(Abstracted|Concrete)EnumCustom methods, which contain a switch (or similar) statement that does the proper + * conversion. Usually both or neither methods will need overriding. There is a runtime unit test, which checks the + * enums for missing values at runtime, and reports any values that can't be converted, allowing for easier and faster + * identification of failure conditions. This is not an ideal model, but does allow for enums themselves to be + * abstracted away from a particular platform. */ public abstract class EnumConvertor { - + private Class abstractedClass; private Class concreteClass; - - /** - * This is changed reflectively by the startup mechanism. Please do not - * change the name of this variable. - */ - private boolean useError = true; - protected EnumConvertor(){ + + protected EnumConvertor() { abstractionenum annotation = this.getClass().getAnnotation(abstractionenum.class); - if(annotation == null){ + if(annotation == null) { throw new Error(this.getClass() + " is not annotated with @abstractionenum."); } - - this.abstractedClass = (Class)annotation.forAbstractEnum(); - this.concreteClass = (Class)annotation.forConcreteEnum(); + + this.abstractedClass = (Class) annotation.forAbstractEnum(); + this.concreteClass = (Class) annotation.forConcreteEnum(); } - - /** - * If the default handling should be used in the conversion, this exception can - * be thrown to signal to the parent code to perform the default heuristic - * based conversion. This is meant to be thrown from the get(Abstracted|Concrete)EnumCustom - * methods, should they be overridden. - */ - protected static class UseDefault extends RuntimeException{ } - + /** - * Given a concrete Enum, returns the abstract version. This is generally - * called in platform specific code. The platform is given a platform specific - * enum, and it needs to return control to the abstract code, so it calls - * MyEnumConvertor.getConverter().getAbstractedEnum(PlatformSpecificEnum.VALUE), - * which in turn abstractly handles the conversion from platform to abstract enum. + * Given a concrete Enum, returns the abstract version. This is generally called in platform specific code. The + * platform is given a platform specific enum, and it needs to return control to the abstract code, so it calls + * MyEnumConvertor.getConverter().getAbstractedEnum(PlatformSpecificEnum.VALUE), which in turn abstractly handles + * the conversion from platform to abstract enum. + * * @param concrete The concrete, platform specific enum * @return The abstract, platform independent enum * @throws IllegalArgumentException If the enum lookup failed */ - public final Abstracted getAbstractedEnum(Concrete concrete) throws IllegalArgumentException { - try{ - try{ - return getAbstractedEnumCustom(concrete); - } catch(UseDefault e){ - if(concrete == null){ - return null; - } - return (Abstracted)Enum.valueOf(abstractedClass, concrete.name()); - } - } catch(IllegalArgumentException e){ + public final Abstracted getAbstractedEnum(Concrete concrete) { + if(concrete == null) { + return null; + } + try { + return getAbstractedEnumCustom(concrete); + } catch (IllegalArgumentException e) { doLog(concreteClass, abstractedClass, concrete); - throw e; + return null; } } - + /** - * Can be overridden by subclasses that have a non 1:1 mapping. It should - * return the abstract enum, given a concrete enum. This should be used in the case - * where the heuristic isn't valid. + * Can be overridden by subclasses that have a non 1:1 mapping. It should return the abstract enum, given a concrete + * enum. This should be used in the case where the heuristic isn't valid. + * * @param concrete The concrete enum - * @return The abstract enum - * @throws com.laytonsmith.abstraction.enums.EnumConvertor.UseDefault If the default action - * should be taken. + * @return The abstract enum should be taken. */ - protected Abstracted getAbstractedEnumCustom(Concrete concrete) throws UseDefault { - throw new UseDefault(); + protected Abstracted getAbstractedEnumCustom(Concrete concrete) throws IllegalArgumentException { + return (Abstracted) Enum.valueOf(abstractedClass, concrete.name()); } - + /** - * Given an abstract Enum, returns the concrete version. This is generally - * called in platform specific code. The platform is given an abstract - * enum, and it needs to convert to the platform specific enum, so it calls - * MyEnumConvertor.getConverter().getConcreteEnum(AbstractEnum.VALUE), - * which in turn abstractly handles the conversion from abstract to platform enum. + * Given an abstract Enum, returns the concrete version. This is generally called in platform specific code. The + * platform is given an abstract enum, and it needs to convert to the platform specific enum, so it calls + * MyEnumConvertor.getConverter().getConcreteEnum(AbstractEnum.VALUE), which in turn abstractly handles the + * conversion from abstract to platform enum. + * * @param abstracted The abstract, platform independent enum * @return The concrete, platform specific enum * @throws IllegalArgumentException If the enum lookup failed */ - public final Concrete getConcreteEnum(Abstracted abstracted){ - try{ - try{ - return getConcreteEnumCustom(abstracted); - } catch(UseDefault e){ - if(abstracted == null){ - return null; - } - return (Concrete)Enum.valueOf(concreteClass, abstracted.name()); - } - } catch(IllegalArgumentException e){ + public final Concrete getConcreteEnum(Abstracted abstracted) { + if(abstracted == null) { + return null; + } + try { + return getConcreteEnumCustom(abstracted); + } catch (IllegalArgumentException e) { doLog(abstractedClass, concreteClass, abstracted); - throw e; + return null; } } - + /** - * Can be overridden by subclasses that have a non 1:1 mapping. It should - * return the concrete enum, given an abstract enum. This should be used in the case - * where the heuristic isn't valid. + * Can be overridden by subclasses that have a non 1:1 mapping. It should return the concrete enum, given an + * abstract enum. This should be used in the case where the heuristic isn't valid. + * * @param abstracted The abstract enum - * @return The concrete enum - * @throws com.laytonsmith.abstraction.enums.EnumConvertor.UseDefault If the default action - * should be taken. + * @return The concrete enum should be taken. */ - protected Concrete getConcreteEnumCustom(Abstracted abstracted) throws UseDefault { - throw new UseDefault(); + protected Concrete getConcreteEnumCustom(Abstracted abstracted) throws IllegalArgumentException { + return (Concrete) Enum.valueOf(concreteClass, abstracted.name()); } - - private void doLog(Class from, Class to, Enum value){ - String message = "When trying to convert " + from.getName() + "." + value.name() + " to a " - + to.getName() + ", no match was found. This may be caused by an old plugin version, or a newer server version."; - LogLevel level = LogLevel.WARNING; - if(useError){ - level = LogLevel.ERROR; - } else { - message += " This may or may not cause further problems during runtime."; - } - CHLog.GetLogger().Log(CHLog.Tags.RUNTIME, level, message, Target.UNKNOWN); + + private void doLog(Class from, Class to, Enum value) { + MSLog.GetLogger().e(MSLog.Tags.RUNTIME, from.getSimpleName() + "." + value.name() + " missing a match in " + + to.getSimpleName(), Target.UNKNOWN); } } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCAction.java b/src/main/java/com/laytonsmith/abstraction/enums/MCAction.java index 3f4c176c00..ab50489688 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCAction.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCAction.java @@ -1,9 +1,9 @@ package com.laytonsmith.abstraction.enums; -/** - * - * - */ -public enum MCAction { - LEFT_CLICK_AIR, LEFT_CLICK_BLOCK, RIGHT_CLICK_BLOCK, RIGHT_CLICK_AIR, PHYSICAL; +public enum MCAction { + LEFT_CLICK_AIR, + LEFT_CLICK_BLOCK, + RIGHT_CLICK_BLOCK, + RIGHT_CLICK_AIR, + PHYSICAL } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCArt.java b/src/main/java/com/laytonsmith/abstraction/enums/MCArt.java index 444996053e..c2f61b4443 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCArt.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCArt.java @@ -1,36 +1,142 @@ package com.laytonsmith.abstraction.enums; -import com.laytonsmith.annotations.MEnum; - -/** - * - */ -@MEnum("Art") -public enum MCArt { - KEBAB, - AZTEC, - ALBAN, - AZTEC2, - BOMB, - PLANT, - WASTELAND, - POOL, - COURBET, - SEA, - SUNSET, - CREEBET, - WANDERER, - GRAHAM, - MATCH, - BUST, - STAGE, - VOID, - SKULL_AND_ROSES, - WITHER, - FIGHTERS, - POINTER, - PIGSCENE, - BURNINGSKULL, - SKELETON, - DONKEYKONG; +import com.laytonsmith.PureUtilities.ClassLoading.DynamicEnum; +import com.laytonsmith.abstraction.enums.MCArt.MCVanillaArt; +import com.laytonsmith.annotations.MDynamicEnum; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +@MDynamicEnum("com.commandhelper.Art") +public abstract class MCArt extends DynamicEnum { + + protected static final Map MAP = new HashMap<>(); + + public MCArt(MCVanillaArt mcVanillaArt, Concrete concrete) { + super(mcVanillaArt, concrete); + } + + public static MCArt valueOf(String test) throws IllegalArgumentException { + MCArt ret = MAP.get(test); + if(ret == null) { + throw new IllegalArgumentException("Unknown art: " + test); + } + return ret; + } + + public static Set types() { + if(MAP.isEmpty()) { // docs mode + Set dummy = new HashSet<>(); + for(final MCVanillaArt s : MCVanillaArt.values()) { + if(s != MCVanillaArt.UNKNOWN) { + dummy.add(s.name()); + } + } + return dummy; + } + return new TreeSet<>(MAP.keySet()); + } + + public static List values() { + if(MAP.isEmpty()) { // docs mode + ArrayList dummy = new ArrayList<>(); + for(final MCVanillaArt s : MCVanillaArt.values()) { + if(s == MCVanillaArt.UNKNOWN) { + continue; + } + dummy.add(new MCArt<>(s, null) { + @Override + public String name() { + return s.name(); + } + }); + } + return dummy; + } + return new ArrayList<>(MAP.values()); + } + + public enum MCVanillaArt { + // 1x1 + KEBAB, + AZTEC, + ALBAN, + AZTEC2, + BOMB, + PLANT, + WASTELAND, + MEDITATIVE(MCVersion.MC1_21), + // 2x1 + POOL, + COURBET, + SEA, + SUNSET, + CREEBET, + // 1x2 + WANDERER, + GRAHAM, + PRAIRIE_RIDE(MCVersion.MC1_21), + // 2x2 + MATCH, + BUST, + STAGE, + VOID, + SKULL_AND_ROSES, + WITHER, + BAROQUE(MCVersion.MC1_21), + HUMBLE(MCVersion.MC1_21), + EARTH(MCVersion.MC1_19), + WIND(MCVersion.MC1_19), + WATER(MCVersion.MC1_19), + FIRE(MCVersion.MC1_19), + // 4x2 + FIGHTERS, + CHANGING(MCVersion.MC1_21), + FINDING(MCVersion.MC1_21), + LOWMIST(MCVersion.MC1_21), + PASSAGE(MCVersion.MC1_21), + // 3x3 + BOUQUET(MCVersion.MC1_21), + CAVEBIRD(MCVersion.MC1_21), + COTAN(MCVersion.MC1_21), + ENDBOSS(MCVersion.MC1_21), + FERN(MCVersion.MC1_21), + OWLEMONS(MCVersion.MC1_21), + SUNFLOWERS(MCVersion.MC1_21), + TIDES(MCVersion.MC1_21), + DENNIS(MCVersion.MC1_21_7), + // 4x3 + SKELETON, + DONKEY_KONG, + // 3x4 + BACKYARD(MCVersion.MC1_21), + POND(MCVersion.MC1_21), + // 4x4 + POINTER, + PIGSCENE, + BURNING_SKULL, + ORB(MCVersion.MC1_21), + UNPACKED(MCVersion.MC1_21), + + UNKNOWN(MCVersion.NEVER); + + private final MCVersion since; + + MCVanillaArt() { + this(MCVersion.MC1_0); + } + + MCVanillaArt(MCVersion since) { + this.since = since; + } + + public boolean existsIn(MCVersion version) { + return version.gte(since); + } + } } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCAttribute.java b/src/main/java/com/laytonsmith/abstraction/enums/MCAttribute.java new file mode 100644 index 0000000000..9dd0e7ea6b --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCAttribute.java @@ -0,0 +1,131 @@ +package com.laytonsmith.abstraction.enums; + +import com.laytonsmith.PureUtilities.ClassLoading.DynamicEnum; +import com.laytonsmith.annotations.MDynamicEnum; +import com.laytonsmith.core.MSLog; +import com.laytonsmith.core.constructs.Target; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +@MDynamicEnum("com.commandhelper.Attribute") +public abstract class MCAttribute extends DynamicEnum { + + protected static final Map MAP = new HashMap<>(); + + public MCAttribute(MCVanillaAttribute mcVanillaAttribute, Concrete concrete) { + super(mcVanillaAttribute, concrete); + } + + public static MCAttribute valueOf(String test) throws IllegalArgumentException { + MCAttribute ret = MAP.get(test); + if(ret == null) { + if(test.equals("HORSE_JUMP_STRENGTH")) { + MSLog.GetLogger().e(MSLog.Tags.GENERAL, + "HORSE_JUMP_STRENGTH attribute changed to GENERIC_JUMP_STRENGTH.", Target.UNKNOWN); + return MAP.get("GENERIC_JUMP_STRENGTH"); + } + throw new IllegalArgumentException("Unknown attribute: " + test); + } + return ret; + } + + public static Set types() { + if(MAP.isEmpty()) { // docs mode + Set dummy = new HashSet<>(); + for(final MCVanillaAttribute s : MCVanillaAttribute.values()) { + if(s.existsIn(MCVersion.CURRENT)) { + dummy.add(s.name()); + } + } + return dummy; + } + return new TreeSet<>(MAP.keySet()); + } + + public static List values() { + if(MAP.isEmpty()) { // docs mode + ArrayList dummy = new ArrayList<>(); + for(final MCVanillaAttribute s : MCVanillaAttribute.values()) { + if(!s.existsIn(MCVersion.CURRENT)) { + continue; + } + dummy.add(new MCAttribute<>(s, null) { + @Override + public String name() { + return s.name(); + } + }); + } + return dummy; + } + return new ArrayList<>(MAP.values()); + } + + public enum MCVanillaAttribute { + GENERIC_ARMOR, + GENERIC_ARMOR_TOUGHNESS, + GENERIC_ATTACK_DAMAGE, + GENERIC_ATTACK_KNOCKBACK, + GENERIC_ATTACK_SPEED, + GENERIC_FLYING_SPEED, + GENERIC_FOLLOW_RANGE, + GENERIC_KNOCKBACK_RESISTANCE, + GENERIC_LUCK, + GENERIC_MAX_HEALTH, + GENERIC_MOVEMENT_SPEED, + HORSE_JUMP_STRENGTH(MCVersion.MC1_6, MCVersion.MC1_20_4), // changed to GENERIC_JUMP_STRENGTH + ZOMBIE_SPAWN_REINFORCEMENTS, + GENERIC_MAX_ABSORPTION(MCVersion.MC1_20_2), + GENERIC_FALL_DAMAGE_MULTIPLIER(MCVersion.MC1_20_6), + GENERIC_GRAVITY(MCVersion.MC1_20_6), + GENERIC_JUMP_STRENGTH(MCVersion.MC1_20_6), + GENERIC_SAFE_FALL_DISTANCE(MCVersion.MC1_20_6), + GENERIC_SCALE(MCVersion.MC1_20_6), + GENERIC_STEP_HEIGHT(MCVersion.MC1_20_6), + PLAYER_BLOCK_BREAK_SPEED(MCVersion.MC1_20_6), + PLAYER_BLOCK_INTERACTION_RANGE(MCVersion.MC1_20_6), + PLAYER_ENTITY_INTERACTION_RANGE(MCVersion.MC1_20_6), + GENERIC_BURNING_TIME(MCVersion.MC1_21), + GENERIC_EXPLOSION_KNOCKBACK_RESISTANCE(MCVersion.MC1_21), + GENERIC_MOVEMENT_EFFICIENCY(MCVersion.MC1_21), + GENERIC_OXYGEN_BONUS(MCVersion.MC1_21), + GENERIC_WATER_MOVEMENT_EFFICIENCY(MCVersion.MC1_21), + PLAYER_MINING_EFFICIENCY(MCVersion.MC1_21), + PLAYER_SNEAKING_SPEED(MCVersion.MC1_21), + PLAYER_SUBMERGED_MINING_SPEED(MCVersion.MC1_21), + PLAYER_SWEEPING_DAMAGE_RATIO(MCVersion.MC1_21), + TEMPT_RANGE(MCVersion.MC1_21_3), + CAMERA_DISTANCE(MCVersion.MC1_21_6), + WAYPOINT_TRANSMIT_RANGE(MCVersion.MC1_21_6), + WAYPOINT_RECEIVE_RANGE(MCVersion.MC1_21_6), + + UNKNOWN(MCVersion.NEVER); + + private final MCVersion since; + private final MCVersion until; + + MCVanillaAttribute() { + this(MCVersion.MC1_6); + } + + MCVanillaAttribute(MCVersion since) { + this.since = since; + this.until = MCVersion.FUTURE; + } + + MCVanillaAttribute(MCVersion since, MCVersion until) { + this.since = since; + this.until = until; + } + + public boolean existsIn(MCVersion version) { + return version.gte(since) && version.lte(until); + } + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCAxolotlType.java b/src/main/java/com/laytonsmith/abstraction/enums/MCAxolotlType.java new file mode 100644 index 0000000000..85ca431d7c --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCAxolotlType.java @@ -0,0 +1,12 @@ +package com.laytonsmith.abstraction.enums; + +import com.laytonsmith.annotations.MEnum; + +@MEnum("com.commandhelper.AxolotlType") +public enum MCAxolotlType { + BLUE, + CYAN, + GOLD, + LUCY, + WILD +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCBarColor.java b/src/main/java/com/laytonsmith/abstraction/enums/MCBarColor.java new file mode 100644 index 0000000000..f5867b4f4b --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCBarColor.java @@ -0,0 +1,14 @@ +package com.laytonsmith.abstraction.enums; + +import com.laytonsmith.annotations.MEnum; + +@MEnum("com.commandhelper.BarColor") +public enum MCBarColor { + PINK, + BLUE, + RED, + GREEN, + YELLOW, + PURPLE, + WHITE +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCBarStyle.java b/src/main/java/com/laytonsmith/abstraction/enums/MCBarStyle.java new file mode 100644 index 0000000000..1a647f71dd --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCBarStyle.java @@ -0,0 +1,12 @@ +package com.laytonsmith.abstraction.enums; + +import com.laytonsmith.annotations.MEnum; + +@MEnum("com.commandhelper.BarStyle") +public enum MCBarStyle { + SOLID, + SEGMENTED_6, + SEGMENTED_10, + SEGMENTED_12, + SEGMENTED_20 +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCBiomeType.java b/src/main/java/com/laytonsmith/abstraction/enums/MCBiomeType.java index d3124da06d..4add258eeb 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCBiomeType.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCBiomeType.java @@ -1,74 +1,211 @@ package com.laytonsmith.abstraction.enums; -import com.laytonsmith.annotations.MEnum; +import com.laytonsmith.PureUtilities.ClassLoading.DynamicEnum; +import com.laytonsmith.annotations.MDynamicEnum; +import com.laytonsmith.core.MSLog; +import com.laytonsmith.core.constructs.Target; -/** - * - * - */ -@MEnum("BiomeType") -public enum MCBiomeType { +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; - OCEAN, - PLAINS, - DESERT, - EXTREME_HILLS, - FOREST, - TAIGA, - SWAMPLAND, - RIVER, - HELL, - SKY, - FROZEN_OCEAN, - FROZEN_RIVER, - ICE_PLAINS, - ICE_MOUNTAINS, - MUSHROOM_ISLAND, - MUSHROOM_SHORE, - BEACH, - DESERT_HILLS, - FOREST_HILLS, - TAIGA_HILLS, - SMALL_MOUNTAINS, - JUNGLE, - JUNGLE_HILLS, - JUNGLE_EDGE, +@MDynamicEnum("com.commandhelper.BiomeType") +public abstract class MCBiomeType extends DynamicEnum { + + protected static final Map MAP = new HashMap<>(); + + public MCBiomeType(MCVanillaBiomeType mcVanillaBiomeType, Concrete concrete) { + super(mcVanillaBiomeType, concrete); + } + + public static MCBiomeType valueOf(String test) throws IllegalArgumentException { + MCBiomeType ret = MAP.get(test); + if(ret == null) { + MCVanillaBiomeType oldType = MCVanillaBiomeType.valueOf(test); + if(oldType.newType != null) { + MSLog.GetLogger().e(MSLog.Tags.GENERAL, test + " biome type was renamed after " + oldType.until.name() + + ". Converted to " + oldType.newType, Target.UNKNOWN); + return MAP.get(oldType.newType); + } + throw new IllegalArgumentException("Unknown biome type: " + test); + } + return ret; + } + + /** + * @return Names of available biome types + */ + public static Set types() { + if(MAP.isEmpty()) { // docs mode + Set dummy = new HashSet<>(); + for(final MCVanillaBiomeType t : MCVanillaBiomeType.values()) { + if(t.existsIn(MCVersion.CURRENT)) { + dummy.add(t.name()); + } + } + return dummy; + } + return MAP.keySet(); + } + + /** + * @return Our own MCBiomeType list + */ + public static List values() { + if(MAP.isEmpty()) { // docs mode + ArrayList dummy = new ArrayList<>(); + for(final MCVanillaBiomeType t : MCVanillaBiomeType.values()) { + if(!t.existsIn(MCVersion.CURRENT)) { + continue; + } + dummy.add(new MCBiomeType<>(t, null) { + @Override + public String name() { + return t.name(); + } + }); + } + return dummy; + } + return new ArrayList<>(MAP.values()); + } + + public enum MCVanillaBiomeType { + OCEAN, + PLAINS, + DESERT, + MOUNTAINS(MCVersion.MC1_0, MCVersion.MC1_17_X, "WINDSWEPT_HILLS"), + FOREST, + TAIGA, + SWAMP, + RIVER, + NETHER(MCVersion.MC1_0, MCVersion.MC1_15_X, "NETHER_WASTES"), + THE_END, + FROZEN_OCEAN, + FROZEN_RIVER, + SNOWY_TUNDRA(MCVersion.MC1_0, MCVersion.MC1_17_X, "SNOWY_PLAINS"), + SNOWY_MOUNTAINS(MCVersion.MC1_0, MCVersion.MC1_17_X), + MUSHROOM_FIELDS, + MUSHROOM_FIELD_SHORE(MCVersion.MC1_0, MCVersion.MC1_17_X), + BEACH, + DESERT_HILLS(MCVersion.MC1_1, MCVersion.MC1_17_X), + WOODED_HILLS(MCVersion.MC1_13, MCVersion.MC1_17_X), + TAIGA_HILLS(MCVersion.MC1_1, MCVersion.MC1_17_X), + MOUNTAIN_EDGE(MCVersion.MC1_1, MCVersion.MC1_17_X), + JUNGLE, + JUNGLE_HILLS(MCVersion.MC1_2, MCVersion.MC1_17_X), + JUNGLE_EDGE(MCVersion.MC1_7_2, MCVersion.MC1_17_X, "SPARSE_JUNGLE"), DEEP_OCEAN, - STONE_BEACH, - COLD_BEACH, + STONE_SHORE(MCVersion.MC1_0, MCVersion.MC1_17_X, "STONY_SHORE"), + SNOWY_BEACH, BIRCH_FOREST, - BIRCH_FOREST_HILLS, - ROOFED_FOREST, - COLD_TAIGA, - COLD_TAIGA_HILLS, - MEGA_TAIGA, - MEGA_TAIGA_HILLS, - EXTREME_HILLS_PLUS, + BIRCH_FOREST_HILLS(MCVersion.MC1_7_2, MCVersion.MC1_17_X), + DARK_FOREST, + SNOWY_TAIGA, + SNOWY_TAIGA_HILLS(MCVersion.MC1_7_2, MCVersion.MC1_17_X), + GIANT_TREE_TAIGA(MCVersion.MC1_7_2, MCVersion.MC1_17_X, "OLD_GROWTH_PINE_TAIGA"), + GIANT_TREE_TAIGA_HILLS(MCVersion.MC1_7_2, MCVersion.MC1_17_X), + WOODED_MOUNTAINS(MCVersion.MC1_7_2, MCVersion.MC1_17_X, "WINDSWEPT_FOREST"), SAVANNA, SAVANNA_PLATEAU, - MESA, - MESA_PLATEAU_FOREST, - MESA_PLATEAU, + BADLANDS, + WOODED_BADLANDS_PLATEAU(MCVersion.MC1_7_2, MCVersion.MC1_17_X, "WOODED_BADLANDS"), + BADLANDS_PLATEAU(MCVersion.MC1_7_2, MCVersion.MC1_17_X), SUNFLOWER_PLAINS, - DESERT_MOUNTAINS, + DESERT_LAKES(MCVersion.MC1_7_2, MCVersion.MC1_17_X), FLOWER_FOREST, - TAIGA_MOUNTAINS, - ICE_PLAINS_SPIKES, - JUNGLE_MOUNTAINS, - JUNGLE_EDGE_MOUNTAINS, - COLD_TAIGA_MOUNTAINS, - SAVANNA_MOUNTAINS, - SAVANNA_PLATEAU_MOUNTAINS, - MESA_BRYCE, - MESA_PLATEAU_FOREST_MOUNTAINS, - MESA_PLATEAU_MOUNTAINS, - BIRCH_FOREST_MOUNTAINS, - BIRCH_FOREST_HILLS_MOUNTAINS, - ROOFED_FOREST_MOUNTAINS, - MEGA_SPRUCE_TAIGA, - EXTREME_HILLS_MOUNTAINS, - EXTREME_HILLS_PLUS_MOUNTAINS, - SWAMPLAND_MOUNTAINS, - MEGA_SPRUCE_TAIGA_HILLS; + TAIGA_MOUNTAINS(MCVersion.MC1_1, MCVersion.MC1_17_X), + ICE_SPIKES, + MODIFIED_JUNGLE(MCVersion.MC1_7_2, MCVersion.MC1_17_X), + MODIFIED_JUNGLE_EDGE(MCVersion.MC1_7_2, MCVersion.MC1_17_X), + SNOWY_TAIGA_MOUNTAINS(MCVersion.MC1_7_2, MCVersion.MC1_17_X), + SHATTERED_SAVANNA(MCVersion.MC1_7_2, MCVersion.MC1_17_X, "WINDSWEPT_SAVANNA"), + SHATTERED_SAVANNA_PLATEAU(MCVersion.MC1_7_2, MCVersion.MC1_17_X), + ERODED_BADLANDS, + MODIFIED_WOODED_BADLANDS_PLATEAU(MCVersion.MC1_7_2, MCVersion.MC1_17_X), + MODIFIED_BADLANDS_PLATEAU(MCVersion.MC1_7_2, MCVersion.MC1_17_X), + TALL_BIRCH_FOREST(MCVersion.MC1_7_2, MCVersion.MC1_17_X, "OLD_GROWTH_BIRCH_FOREST"), + TALL_BIRCH_HILLS(MCVersion.MC1_7_2, MCVersion.MC1_17_X), + DARK_FOREST_HILLS(MCVersion.MC1_7_2, MCVersion.MC1_17_X), + GIANT_SPRUCE_TAIGA(MCVersion.MC1_7_2, MCVersion.MC1_17_X, "OLD_GROWTH_SPRUCE_TAIGA"), + GRAVELLY_MOUNTAINS(MCVersion.MC1_7_2, MCVersion.MC1_17_X, "WINDSWEPT_GRAVELLY_HILLS"), + MODIFIED_GRAVELLY_MOUNTAINS(MCVersion.MC1_7_2, MCVersion.MC1_17_X), + SWAMP_HILLS(MCVersion.MC1_7_2, MCVersion.MC1_17_X), + GIANT_SPRUCE_TAIGA_HILLS(MCVersion.MC1_7_2, MCVersion.MC1_17_X), + THE_VOID, + SMALL_END_ISLANDS, + END_MIDLANDS, + END_HIGHLANDS, + END_BARRENS, + WARM_OCEAN, + LUKEWARM_OCEAN, + COLD_OCEAN, + DEEP_WARM_OCEAN(MCVersion.MC1_13, MCVersion.MC1_17_X), + DEEP_LUKEWARM_OCEAN, + DEEP_COLD_OCEAN, + DEEP_FROZEN_OCEAN, + BAMBOO_JUNGLE(MCVersion.MC1_14), + BAMBOO_JUNGLE_HILLS(MCVersion.MC1_14, MCVersion.MC1_17_X), + NETHER_WASTES(MCVersion.MC1_16), + SOUL_SAND_VALLEY(MCVersion.MC1_16), + CRIMSON_FOREST(MCVersion.MC1_16), + WARPED_FOREST(MCVersion.MC1_16), + BASALT_DELTAS(MCVersion.MC1_16), + CUSTOM(MCVersion.MC1_16_X, MCVersion.MC1_21_1), + DRIPSTONE_CAVES(MCVersion.MC1_17), + LUSH_CAVES(MCVersion.MC1_17), + FROZEN_PEAKS(MCVersion.MC1_18), + GROVE(MCVersion.MC1_18), + JAGGED_PEAKS(MCVersion.MC1_18), + MEADOW(MCVersion.MC1_18), + OLD_GROWTH_BIRCH_FOREST(MCVersion.MC1_18), + OLD_GROWTH_PINE_TAIGA(MCVersion.MC1_18), + OLD_GROWTH_SPRUCE_TAIGA(MCVersion.MC1_18), + SNOWY_PLAINS(MCVersion.MC1_18), + SNOWY_SLOPES(MCVersion.MC1_18), + SPARSE_JUNGLE(MCVersion.MC1_18), + STONY_PEAKS(MCVersion.MC1_18), + STONY_SHORE(MCVersion.MC1_18), + WINDSWEPT_FOREST(MCVersion.MC1_18), + WINDSWEPT_GRAVELLY_HILLS(MCVersion.MC1_18), + WINDSWEPT_HILLS(MCVersion.MC1_18), + WINDSWEPT_SAVANNA(MCVersion.MC1_18), + WOODED_BADLANDS(MCVersion.MC1_18), + MANGROVE_SWAMP(MCVersion.MC1_19), + DEEP_DARK(MCVersion.MC1_19), + CHERRY_GROVE(MCVersion.MC1_19_4), + PALE_GARDEN(MCVersion.MC1_21_3), + UNKNOWN(MCVersion.NEVER); + + private final MCVersion since; + private final MCVersion until; + private final String newType; + + MCVanillaBiomeType() { + this(MCVersion.MC1_0); + } + + MCVanillaBiomeType(MCVersion since) { + this(since, MCVersion.FUTURE); + } + + MCVanillaBiomeType(MCVersion since, MCVersion until) { + this.since = since; + this.until = until; + this.newType = null; + } + + MCVanillaBiomeType(MCVersion since, MCVersion until, String newType) { + this.since = since; + this.until = until; + this.newType = newType; + } + public boolean existsIn(MCVersion version) { + return version.gte(since) && version.lte(until); + } + } } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCBodyPart.java b/src/main/java/com/laytonsmith/abstraction/enums/MCBodyPart.java new file mode 100644 index 0000000000..9baad37ada --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCBodyPart.java @@ -0,0 +1,56 @@ +package com.laytonsmith.abstraction.enums; + +import java.util.ArrayList; +import java.util.List; + +/** + * This enum intends to be forward thinking, in anticipation of the ability to control body parts of mobs. + */ +public enum MCBodyPart { + ArmLeft(true), + ArmLeft1(false), + ArmLeft2(false), + ArmLeft3(false), + ArmRight(true), + ArmRight1(false), + ArmRight2(false), + ArmRight3(false), + Head(true), + LegLeft(true), + LegLeft1(false), + LegLeft2(false), + LegLeft3(false), + LegRight(true), + LegRight1(false), + LegRight2(false), + LegRight3(false), + Mouth(false), + Tail(false), + Torso(true), + WingLeft(false), + WingRight(false); + + private final boolean existsForHumanoids; + + MCBodyPart(boolean existsForHumanoids) { + this.existsForHumanoids = existsForHumanoids; + } + + public boolean isHumanoidPart() { + return existsForHumanoids; + } + + public static boolean isHumanoidPart(MCBodyPart part) { + return part.existsForHumanoids; + } + + public static List humanoidParts() { + List bodyPartArrayList = new ArrayList(); + for(MCBodyPart part : values()) { + if(part.isHumanoidPart()) { + bodyPartArrayList.add(part); + } + } + return bodyPartArrayList; + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCCatType.java b/src/main/java/com/laytonsmith/abstraction/enums/MCCatType.java new file mode 100644 index 0000000000..1f07f7a5e4 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCCatType.java @@ -0,0 +1,18 @@ +package com.laytonsmith.abstraction.enums; + +import com.laytonsmith.annotations.MEnum; + +@MEnum("com.commandhelper.CatType") +public enum MCCatType { + ALL_BLACK, + BLACK, + BRITISH_SHORTHAIR, + CALICO, + JELLIE, + PERSIAN, + RAGDOLL, + RED, + SIAMESE, + TABBY, + WHITE +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCChatColor.java b/src/main/java/com/laytonsmith/abstraction/enums/MCChatColor.java index afe6d69011..4f28bc334f 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCChatColor.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCChatColor.java @@ -7,169 +7,151 @@ /** * All supported color values for chat */ -@MEnum("ChatColor") +@MEnum("com.commandhelper.ChatColor") public enum MCChatColor { - /** - * Represents black - */ - BLACK(0x0), - /** - * Represents dark blue - */ - DARK_BLUE(0x1), - /** - * Represents dark green - */ - DARK_GREEN(0x2), - /** - * Represents dark blue (aqua) - */ - DARK_AQUA(0x3), - /** - * Represents dark red - */ - DARK_RED(0x4), - /** - * Represents dark purple - */ - DARK_PURPLE(0x5), - /** - * Represents gold - */ - GOLD(0x6), - /** - * Represents gray - */ - GRAY(0x7), - /** - * Represents dark gray - */ - DARK_GRAY(0x8), - /** - * Represents blue - */ - BLUE(0x9), - /** - * Represents green - */ - GREEN(0xA), - /** - * Represents aqua - */ - AQUA(0xB), - /** - * Represents red - */ - RED(0xC), - /** - * Represents light purple - */ - LIGHT_PURPLE(0xD), - /** - * Represents yellow - */ - YELLOW(0xE), - /** - * Represents white - */ - WHITE(0xF), - - //Styles - /** - * Represents the random style - */ - RANDOM('k'), - - /** - * Represents the bold style - */ - BOLD('l'), - - /** - * Represents the strikethrough style - */ - STRIKETHROUGH('m'), - - /** - * Represents the underline style - */ - UNDERLINE('n'), - - /** - * Represents the italic style - */ - ITALIC('o'), - - /** - * Represents the plain white style - */ - PLAIN_WHITE('r'); + /** + * Represents black + */ + BLACK(0x0), + /** + * Represents dark blue + */ + DARK_BLUE(0x1), + /** + * Represents dark green + */ + DARK_GREEN(0x2), + /** + * Represents dark blue (aqua) + */ + DARK_AQUA(0x3), + /** + * Represents dark red + */ + DARK_RED(0x4), + /** + * Represents dark purple + */ + DARK_PURPLE(0x5), + /** + * Represents gold + */ + GOLD(0x6), + /** + * Represents gray + */ + GRAY(0x7), + /** + * Represents dark gray + */ + DARK_GRAY(0x8), + /** + * Represents blue + */ + BLUE(0x9), + /** + * Represents green + */ + GREEN(0xA), + /** + * Represents aqua + */ + AQUA(0xB), + /** + * Represents red + */ + RED(0xC), + /** + * Represents light purple + */ + LIGHT_PURPLE(0xD), + /** + * Represents yellow + */ + YELLOW(0xE), + /** + * Represents white + */ + WHITE(0xF), + //Styles + /** + * Represents the random style + */ + RANDOM('k'), + /** + * Represents the bold style + */ + BOLD('l'), + /** + * Represents the strikethrough style + */ + STRIKETHROUGH('m'), + /** + * Represents the underline style + */ + UNDERLINE('n'), + /** + * Represents the italic style + */ + ITALIC('o'), + /** + * Represents the plain white style + */ + PLAIN_WHITE('r'); - private final char code; - private final static Map colors = new HashMap(); - private final static Map charColors = new HashMap(); + private final char code; + private static final Map CHAR_COLORS = new HashMap<>(); - private MCChatColor(char code){ - this.code = code; - } - private MCChatColor(int code) { - this.code = Integer.toHexString(code).toLowerCase().charAt(0); - } + MCChatColor(char code) { + this.code = code; + } - /** - * Gets the data value associated with this color - * - * @return An integer value of this color code - * @deprecated Use getChar in favor of this method - */ - public int getCode() { - return code; - } - - public char getChar(){ - return code; - } + MCChatColor(int code) { + this.code = Integer.toHexString(code).toLowerCase().charAt(0); + } - @Override - public String toString() { - return String.format("\u00A7%s", code); - } + public char getChar() { + return code; + } - /** - * Gets the color represented by the specified color code - * - * @param code Code to check - * @return Associative {@link com.laytonsmith.abstraction.MCChatColor} with the given code, or null if it doesn't exist - * @deprecated This should not be used, in favor of the char lookup - */ - public static MCChatColor getByCode(final int code) { - return colors.get(code); - } - - public static MCChatColor getByChar(char code){ - return charColors.get(Character.valueOf(code)); - } - + @Override + public String toString() { + return String.format("\u00A7%s", code); + } - /** - * Strips the given message of all color codes - * - * @param input String to strip of color - * @return A copy of the input string, without any coloring - */ - public static String stripColor(final String input) { - if (input == null) { - return null; - } + public static MCChatColor getByChar(char code) { + return CHAR_COLORS.get(code); + } - return input.replaceAll("(?i)\u00A7[0-9A-Fklmnor]", ""); - } + public static String fromRGBValue(String hexString) { + if(hexString.length() > 7) { + hexString = hexString.substring(0, 7); + } + if(!hexString.matches("(?i)^#[0-9A-F]{6}$")) { + return null; + } - static { - for (MCChatColor color : MCChatColor.values()) { - colors.put(color.getCode(), color); - charColors.put(color.getChar(), color); - } - } + return "\u00A7x\u00A7" + String.join("\u00A7", hexString.substring(1).split("")); + } + + /** + * Strips the given message of all color codes + * + * @param input String to strip of color + * @return A copy of the input string, without any coloring + */ + public static String stripColor(final String input) { + if(input == null) { + return null; + } + + return input.replaceAll("(?i)\u00A7[0-9A-Fklmnorx]", ""); + } + + static { + for(MCChatColor color : MCChatColor.values()) { + CHAR_COLORS.put(color.getChar(), color); + } + } } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCClickType.java b/src/main/java/com/laytonsmith/abstraction/enums/MCClickType.java index 3a961d42df..97d5ac49b9 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCClickType.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCClickType.java @@ -2,69 +2,66 @@ import com.laytonsmith.annotations.MEnum; -/** - * - * @author Riking - */ -@MEnum("ClickType") +@MEnum("com.commandhelper.ClickType") public enum MCClickType { /** - * The left (or primary) mouse button. - */ - LEFT, - /** - * Holding shift while pressing the left mouse button. - */ - SHIFT_LEFT, - /** - * The right mouse button. - */ - RIGHT, - /** - * Holding shift while pressing the right mouse button. - */ - SHIFT_RIGHT, - /** - * Clicking the left mouse button on the grey area around the - * inventory. - */ - WINDOW_BORDER_LEFT, - /** - * Clicking the right mouse button on the grey area around the - * inventory. - */ - WINDOW_BORDER_RIGHT, - /** - * The middle mouse button, or a "scrollwheel click". - */ - MIDDLE, - /** - * One of the number keys 1-9, correspond to slots on the hotbar. - */ - NUMBER_KEY, - /** - * Pressing the left mouse button twice in quick succession. - */ - DOUBLE_CLICK, - /** - * The "Drop" key (defaults to Q). - */ - DROP, - /** - * Holding Ctrl while pressing the "Drop" key (defaults to Q). - */ - CONTROL_DROP, - /** - * Any action done with the Creative inventory open. - */ - CREATIVE, - /** - * A type of inventory manipulation not yet recognized by Bukkit. - * This is only for transitional purposes on a new Minecraft update, - * and should never be relied upon. - *

- * Any ClickType.UNKNOWN is called on a best-effort basis. - */ - UNKNOWN, - ; + * The left (or primary) mouse button. + */ + LEFT, + /** + * Holding shift while pressing the left mouse button. + */ + SHIFT_LEFT, + /** + * The right mouse button. + */ + RIGHT, + /** + * Holding shift while pressing the right mouse button. + */ + SHIFT_RIGHT, + /** + * Clicking the left mouse button on the grey area around the inventory. + */ + WINDOW_BORDER_LEFT, + /** + * Clicking the right mouse button on the grey area around the inventory. + */ + WINDOW_BORDER_RIGHT, + /** + * The middle mouse button, or a "scrollwheel click". + */ + MIDDLE, + /** + * One of the number keys 1-9, correspond to slots on the hotbar. + */ + NUMBER_KEY, + /** + * Pressing the left mouse button twice in quick succession. + */ + DOUBLE_CLICK, + /** + * The "Drop" key (defaults to Q). + */ + DROP, + /** + * Holding Ctrl while pressing the "Drop" key (defaults to Q). + */ + CONTROL_DROP, + /** + * Any action done with the Creative inventory open. + */ + CREATIVE, + /** + * The "swap item with offhand" key (defaults to F). + * Added in 1.16 + */ + SWAP_OFFHAND, + /** + * A type of inventory manipulation not yet recognized by Bukkit. This is only for transitional purposes on a new + * Minecraft update, and should never be relied upon. + *

+ * Any ClickType.UNKNOWN is called on a best-effort basis. + */ + UNKNOWN,; } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCCollisionType.java b/src/main/java/com/laytonsmith/abstraction/enums/MCCollisionType.java index e6f0958622..5e02791d89 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCCollisionType.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCCollisionType.java @@ -2,7 +2,7 @@ import com.laytonsmith.annotations.MEnum; -@MEnum("CollisionType") +@MEnum("com.commandhelper.CollisionType") public enum MCCollisionType { BLOCK, ENTITY diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCCreeperType.java b/src/main/java/com/laytonsmith/abstraction/enums/MCCreeperType.java index 5a64d634cc..b710a86703 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCCreeperType.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCCreeperType.java @@ -2,11 +2,7 @@ import com.laytonsmith.annotations.MEnum; -/** - * - * @author jb_aero - */ -@MEnum("CreeperType") +@MEnum("com.commandhelper.CreeperType") public enum MCCreeperType { POWERED -} \ No newline at end of file +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCCriteria.java b/src/main/java/com/laytonsmith/abstraction/enums/MCCriteria.java index f3f48bdfe3..05c91d0a9e 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCCriteria.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCCriteria.java @@ -4,23 +4,22 @@ /** * Criteria names which trigger an objective to be modified by actions in-game - * - * @author jb_aero + * */ -@MEnum("Criteria") +@MEnum("com.commandhelper.Criteria") public enum MCCriteria { DEATHCOUNT("deathCount"), HEALTH("health"), PLAYERKILLCOUNT("playerKillCount"), TOTALKILLCOUNT("totalKillCount"), DUMMY("dummy"); - - private String criteria; - - private MCCriteria(String crit) { + + private final String criteria; + + MCCriteria(String crit) { criteria = crit; } - + public String getCriteria() { return criteria; } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCDamageCause.java b/src/main/java/com/laytonsmith/abstraction/enums/MCDamageCause.java index 1f24646194..154cc0a49a 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCDamageCause.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCDamageCause.java @@ -1,121 +1,136 @@ package com.laytonsmith.abstraction.enums; -/** - * - * - */ public enum MCDamageCause { - /** - * Damage caused when an entity contacts a block such as a Cactus. - *

- * Damage: 1 (Cactus) - */ - CONTACT, - /** - * Damage caused when an entity attacks another entity. - *

- * Damage: variable - */ - ENTITY_ATTACK, - /** - * Damage caused when attacked by a projectile. - *

- * Damage: variable - */ - PROJECTILE, - /** - * Damage caused by being put in a block - *

- * Damage: 1 - */ - SUFFOCATION, - /** - * Damage caused when an entity falls a distance greater than 3 blocks - *

- * Damage: fall height - 3.0 - */ - FALL, - /** - * Damage caused by direct exposure to fire - *

- * Damage: 1 - */ - FIRE, - /** - * Damage caused due to burns caused by fire - *

- * Damage: 1 - */ - FIRE_TICK, - /** - * Damage caused by direct exposure to lava - *

- * Damage: 4 - */ - LAVA, - /** - * Damage caused by running out of air while in water - *

- * Damage: 2 - */ - DROWNING, - /** - * Damage caused by being in the area when a block explodes. - *

- * Damage: variable - */ - BLOCK_EXPLOSION, - /** - * Damage caused by being in the area when an entity, such as a Creeper, explodes. - *

- * Damage: variable - */ - ENTITY_EXPLOSION, - /** - * Damage caused by falling into the void - *

- * Damage: 4 for players - */ - VOID, - /** - * Damage caused by being struck by lightning - *

- * Damage: 5 - */ - LIGHTNING, - /** - * Damage caused by committing suicide using the command "/kill" - *

- * Damage: 1000 - */ - SUICIDE, - /** - * Damage caused by starving due to having an empty hunger bar - *

- * Damage: 1 - */ - STARVATION, - /** - * Damage caused due to an ongoing poison effect - * - * Damage: 1 - */ - POISON, - /** - * Damage caused by being hit by a damage potion or spell - * - * Damage: variable - */ - MAGIC, - - MELTING, - WITHER, - FALLING_BLOCK, - THORNS, - /** - * Custom damage. - *

- * Damage: variable - */ - CUSTOM + /** + * Damage caused when an entity contacts a block such as a Cactus. + */ + CONTACT, + /** + * Damage caused when an entity attacks another entity. + */ + ENTITY_ATTACK, + /** + * Damage caused when attacked by a projectile. + */ + PROJECTILE, + /** + * Damage caused by being put in a block + */ + SUFFOCATION, + /** + * Damage caused when an entity falls a distance greater than 3 blocks + */ + FALL, + /** + * Damage caused by direct exposure to fire + */ + FIRE, + /** + * Damage caused due to burns caused by fire + */ + FIRE_TICK, + /** + * Damage caused by direct exposure to lava + */ + LAVA, + /** + * Damage caused by running out of air while in water + */ + DROWNING, + /** + * Damage caused by being in the area when a block explodes. + */ + BLOCK_EXPLOSION, + /** + * Damage caused by being in the area when an entity, such as a Creeper, explodes. + */ + ENTITY_EXPLOSION, + /** + * Damage caused by falling into the void + */ + VOID, + /** + * Damage caused by being struck by lightning + */ + LIGHTNING, + /** + * Damage caused by committing suicide using the command "/kill" + */ + SUICIDE, + /** + * Damage caused by starving due to having an empty hunger bar + */ + STARVATION, + /** + * Damage caused due to an ongoing poison effect + */ + POISON, + /** + * Damage caused by being hit by a damage potion or spell + */ + MAGIC, + /** + * Damage caused due to a snowman melting + */ + MELTING, + /** + * Damage caused by Wither potion effect + */ + WITHER, + /** + * Damage caused by being hit by a falling block which deals damage + */ + FALLING_BLOCK, + /** + * Damage caused in retaliation to another attack by the Thorns enchantment. + */ + THORNS, + /** + * Damage caused by a dragon breathing fire. + */ + DRAGON_BREATH, + /** + * Damage caused when an entity runs into a wall. + */ + FLY_INTO_WALL, + /** + * Damage caused when an entity steps on MAGMA. + */ + HOT_FLOOR, + /** + * Damage caused when an entity is on top a campfire. + */ + CAMPFIRE, + /** + * Damage caused when an entity is colliding with too many entities due to the maxEntityCramming game rule. + */ + CRAMMING, + /** + * Damage caused when an entity attacks another entity in a sweep attack. + */ + ENTITY_SWEEP_ATTACK, + /** + * Damage caused when an entity that should be in water is not. + */ + DRYOUT, + /** + * Damage caused from freezing. + */ + FREEZE, + /** + * Damage caused by the Sonic Boom attack from Warden + */ + SONIC_BOOM, + /** + * Damage caused by /kill command + */ + KILL, + /** + * Damage caused by the World Border + */ + WORLD_BORDER, + /** + * Custom damage. + */ + CUSTOM } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCDifficulty.java b/src/main/java/com/laytonsmith/abstraction/enums/MCDifficulty.java index 243bce438e..439d421521 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCDifficulty.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCDifficulty.java @@ -2,11 +2,7 @@ import com.laytonsmith.annotations.MEnum; -/** - * - * @author Hekta - */ -@MEnum("Difficulty") +@MEnum("com.commandhelper.Difficulty") public enum MCDifficulty { PEACEFUL(0), EASY(1), @@ -19,7 +15,7 @@ public enum MCDifficulty { this.value = value; } - public int getValue() { - return this.value; - } -} \ No newline at end of file + public int getValue() { + return this.value; + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCDisplaySlot.java b/src/main/java/com/laytonsmith/abstraction/enums/MCDisplaySlot.java index 1068d631dd..a30836db7e 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCDisplaySlot.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCDisplaySlot.java @@ -4,12 +4,27 @@ /** * Scoreboard displayslots - * - * @author jb_aero + * */ -@MEnum("DisplaySlot") +@MEnum("com.commandhelper.DisplaySlot") public enum MCDisplaySlot { BELOW_NAME, PLAYER_LIST, - SIDEBAR + SIDEBAR, + SIDEBAR_TEAM_BLACK, + SIDEBAR_TEAM_DARK_BLUE, + SIDEBAR_TEAM_DARK_GREEN, + SIDEBAR_TEAM_DARK_AQUA, + SIDEBAR_TEAM_DARK_RED, + SIDEBAR_TEAM_DARK_PURPLE, + SIDEBAR_TEAM_GOLD, + SIDEBAR_TEAM_GRAY, + SIDEBAR_TEAM_DARK_GRAY, + SIDEBAR_TEAM_BLUE, + SIDEBAR_TEAM_GREEN, + SIDEBAR_TEAM_AQUA, + SIDEBAR_TEAM_RED, + SIDEBAR_TEAM_LIGHT_PURPLE, + SIDEBAR_TEAM_YELLOW, + SIDEBAR_TEAM_WHITE } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCDragType.java b/src/main/java/com/laytonsmith/abstraction/enums/MCDragType.java index 254411f5ca..7302c327c1 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCDragType.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCDragType.java @@ -2,12 +2,8 @@ import com.laytonsmith.annotations.MEnum; -/** - * - * @author MariuszT - */ -@MEnum("DragType") +@MEnum("com.commandhelper.DragType") public enum MCDragType { SINGLE, - EVEN; + EVEN } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCDyeColor.java b/src/main/java/com/laytonsmith/abstraction/enums/MCDyeColor.java index b1748cc3f0..18714eed54 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCDyeColor.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCDyeColor.java @@ -1,28 +1,23 @@ - package com.laytonsmith.abstraction.enums; import com.laytonsmith.annotations.MEnum; -/** - * - * - */ -@MEnum("DyeColor") +@MEnum("com.commandhelper.DyeColor") public enum MCDyeColor { WHITE, - ORANGE, - MAGENTA, - LIGHT_BLUE, - YELLOW, - LIME, - PINK, - GRAY, - SILVER, - CYAN, - PURPLE, - BLUE, - BROWN, - GREEN, - RED, - BLACK + ORANGE, + MAGENTA, + LIGHT_BLUE, + YELLOW, + LIME, + PINK, + GRAY, + LIGHT_GRAY, + CYAN, + PURPLE, + BLUE, + BROWN, + GREEN, + RED, + BLACK } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCEffect.java b/src/main/java/com/laytonsmith/abstraction/enums/MCEffect.java index fdbb07f8c0..8aa67a2629 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCEffect.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCEffect.java @@ -1,48 +1,104 @@ - package com.laytonsmith.abstraction.enums; import com.laytonsmith.annotations.MEnum; -/** - * - * - */ -@MEnum("Effect") +@MEnum("com.commandhelper.Effect") public enum MCEffect { - BOW_FIRE(1002), - CLICK1(1001), - CLICK2(1000), - DOOR_TOGGLE(1003), - EXTINGUISH(1004), - RECORD_PLAY(1005), - GHAST_SHRIEK(1007), - GHAST_SHOOT(1008), - BLAZE_SHOOT(1009), - SMOKE(2000), - STEP_SOUND(2001), - POTION_BREAK(2002), - ENDER_SIGNAL(2003), /** - * Sound of zombies chewing on wooden doors. - */ - ZOMBIE_CHEW_WOODEN_DOOR(1010), - /** - * Sound of zombies chewing on iron doors. - */ - ZOMBIE_CHEW_IRON_DOOR(1011), - /** - * Sound of zombies destroying a door. - */ - ZOMBIE_DESTROY_DOOR(1012), - MOBSPAWNER_FLAMES(2004); - - private final int id; - - MCEffect(int id) { - this.id = id; - } - - public int getId() { - return this.id; - } + * VISUAL + */ + BEE_GROWTH, // integer, Paper, added in 1.20.6, partially replaces VILLAGER_PLANT_GROW + BONE_MEAL_USE, // integer + COMPOSTER_FILL_ATTEMPT, // boolean for success + COPPER_WAX_OFF, + COPPER_WAX_ON, + DRAGON_BREATH, + DRIPPING_DRIPSTONE, + ELECTRIC_SPARK, // Axis at which particles are shown + ENDER_DRAGON_DESTROY_BLOCK, + END_GATEWAY_SPAWN, + END_PORTAL_FRAME_FILL, + ENDER_SIGNAL, + INSTANT_POTION_BREAK, // Color of particles + LAVA_INTERACT, + MOBSPAWNER_FLAMES, + OXIDISED_COPPER_SCRAPE, + PARTICLES_EGG_CRACK, // Paper, added 1.20 - 1.20.4 + PARTICLES_AND_SOUND_BRUSH_BLOCK_COMPLETE, // BlockData, Paper, added 1.20 - 1.20.4 + PARTICLES_SCULK_CHARGE, // integer, Paper, added 1.20 - 1.20.4 + POTION_BREAK, // Color + REDSTONE_TORCH_BURNOUT, + SHOOT_WHITE_SMOKE, // BlockFace for the direction, Paper, added 1.20 - 1.20.4 + SMASH_ATTACK, // integer, Paper, added in 1.20.5 + SMOKE, // BlockFace for the direction of the smoke particles + SPAWN_COBWEB, // Paper, added in 1.20.6 + SPONGE_DRY, + TRIAL_SPAWNER_BECOME_OMINOUS, // boolean, Paper, added 1.20.6 + TRIAL_SPAWNER_DETECT_PLAYER, // integer, Paper, added 1.20.4 + TRIAL_SPAWNER_DETECT_PLAYER_OMINOUS, // integer, Paper, added 1.20.6 + TRIAL_SPAWNER_EJECT_ITEM, // Paper, added 1.20.4 + TRIAL_SPAWNER_SPAWN, // boolean, Paper, added 1.20.4 + TRIAL_SPAWNER_SPAWN_ITEM, // boolean, Paper, added 1.20.6 + TRIAL_SPAWNER_SPAWN_MOB_AT, // boolean, Paper, added 1.20.4 + TURTLE_EGG_PLACEMENT, // integer, Paper, added in 1.20.6 + VAULT_ACTIVATE, // boolean, Paper, added in 1.20.6 + VAULT_DEACTIVATE, // boolean, Paper, added in 1.20.6 + VAULT_EJECT_ITEM, // Paper, added in 1.20.6 + VILLAGER_PLANT_GROW, // integer, deprecated in 1.20.6, partially replaced by BEE_GROWTH + /** + * SOUND + */ + ANVIL_BREAK, + ANVIL_LAND, + ANVIL_USE, + BAT_TAKEOFF, + BLAZE_SHOOT, + BOOK_PAGE_TURN, + BOW_FIRE, + BREWING_STAND_BREW, + CHORUS_FLOWER_DEATH, + CHORUS_FLOWER_GROW, + CLICK1, + CLICK2, + CRAFTER_CRAFT, // Paper, added 1.20.4 + CRAFTER_FAIL, // Paper, added 1.20.4 + DOOR_CLOSE, // deprecated in 1.19.3 + DOOR_TOGGLE, // deprecated in 1.19.3 + ENDERDRAGON_GROWL, + ENDERDRAGON_SHOOT, + ENDEREYE_LAUNCH, // deprecated in 1.21 + EXTINGUISH, + FENCE_GATE_CLOSE, // deprecated in 1.19.3 + FENCE_GATE_TOGGLE, // deprecated in 1.19.3 + FIREWORK_SHOOT, + GHAST_SHOOT, + GHAST_SHRIEK, + GRINDSTONE_USE, + HUSK_CONVERTED_TO_ZOMBIE, + IRON_DOOR_CLOSE, // deprecated in 1.19.3 + IRON_DOOR_TOGGLE, // deprecated in 1.19.3 + IRON_TRAPDOOR_CLOSE, // deprecated in 1.19.3 + IRON_TRAPDOOR_TOGGLE, // deprecated in 1.19.3 + PARTICLES_SCULK_SHRIEK, // Paper, added 1.20 - 1.20.4 + PHANTOM_BITE, + POINTED_DRIPSTONE_DRIP_LAVA_INTO_CAULDRON, + POINTED_DRIPSTONE_DRIP_WATER_INTO_CAULDRON, + POINTED_DRIPSTONE_LAND, + PORTAL_TRAVEL, + RECORD_PLAY, // Material for record item + SKELETON_CONVERTED_TO_STRAY, + SMITHING_TABLE_USE, + SOUND_STOP_JUKEBOX_SONG, // Paper, added 1.20 - 1.20.4 + SOUND_WITH_CHARGE_SHOT, // Paper, added 1.21 + STEP_SOUND, // Material for block type stepped on + TRAPDOOR_CLOSE, // deprecated in 1.19.3 + TRAPDOOR_TOGGLE, // deprecated in 1.19.3 + ZOMBIE_CHEW_WOODEN_DOOR, + ZOMBIE_CHEW_IRON_DOOR, + ZOMBIE_DESTROY_DOOR, + WITHER_BREAK_BLOCK, + WITHER_SHOOT, + ZOMBIE_CONVERTED_TO_DROWNED, + ZOMBIE_CONVERTED_VILLAGER, + ZOMBIE_INFECT } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCEnchantment.java b/src/main/java/com/laytonsmith/abstraction/enums/MCEnchantment.java new file mode 100644 index 0000000000..adf5e2d9c9 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCEnchantment.java @@ -0,0 +1,174 @@ +package com.laytonsmith.abstraction.enums; + +import com.laytonsmith.PureUtilities.ClassLoading.DynamicEnum; +import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.annotations.MDynamicEnum; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +@MDynamicEnum("com.commandhelper.Enchantment") +public abstract class MCEnchantment extends DynamicEnum { + + protected static final Map MAP = new HashMap<>(); + + public MCEnchantment(MCVanillaEnchantment mcVanillaType, Concrete concrete) { + super(mcVanillaType, concrete); + } + + public static MCEnchantment valueOf(String test) throws IllegalArgumentException { + MCEnchantment ret = MAP.get(test); + if(ret == null) { + MCVanillaEnchantment oldType = MCVanillaEnchantment.valueOf(test); + if(oldType.rename != null) { + return MAP.get(oldType.rename); + } + throw new IllegalArgumentException("Unknown enchantment type: " + test); + } + return ret; + } + + public abstract boolean canEnchantItem(MCItemStack is); + + public abstract int getMaxLevel(); + + public static Set types() { + if(MAP.isEmpty()) { // docs mode + Set dummy = new HashSet<>(); + for(final MCVanillaEnchantment s : MCVanillaEnchantment.values()) { + if(s.existsIn(MCVersion.CURRENT)) { + dummy.add(s.name()); + } + } + return dummy; + } + return new TreeSet<>(MAP.keySet()); + } + + public static List values() { + if(MAP.isEmpty()) { // docs mode + ArrayList dummy = new ArrayList<>(); + for(final MCVanillaEnchantment s : MCVanillaEnchantment.values()) { + if(!s.existsIn(MCVersion.CURRENT)) { + continue; + } + dummy.add(new MCEnchantment<>(s, null) { + @Override + public boolean canEnchantItem(MCItemStack is) { + return false; + } + + @Override + public int getMaxLevel() { + return 1; + } + + @Override + public String name() { + return s.name(); + } + }); + } + return dummy; + } + return new ArrayList<>(MAP.values()); + } + + public enum MCVanillaEnchantment { + DAMAGE_ALL(MCVersion.MC1_0, MCVersion.MC1_12_X, "SHARPNESS"), + DAMAGE_ARTHROPODS(MCVersion.MC1_0, MCVersion.MC1_12_X, "BANE_OF_ARTHROPODS"), + DAMAGE_UNDEAD(MCVersion.MC1_0, MCVersion.MC1_12_X, "SMITE"), + DIG_SPEED(MCVersion.MC1_0, MCVersion.MC1_12_X, "EFFICIENCY"), + DURABILITY(MCVersion.MC1_0, MCVersion.MC1_12_X, "UNBREAKING"), + LOOT_BONUS_BLOCKS(MCVersion.MC1_0, MCVersion.MC1_12_X, "FORTUNE"), + LOOT_BONUS_MOBS(MCVersion.MC1_0, MCVersion.MC1_12_X, "LOOTING"), + LUCK(MCVersion.MC1_0, MCVersion.MC1_12_X, "LUCK_OF_THE_SEA"), + OXYGEN(MCVersion.MC1_0, MCVersion.MC1_12_X, "RESPIRATION"), + PROTECTION_ENVIRONMENTAL(MCVersion.MC1_0, MCVersion.MC1_12_X, "PROTECTION"), + PROTECTION_FIRE(MCVersion.MC1_0, MCVersion.MC1_12_X, "FIRE_PROTECTION"), + PROTECTION_FALL(MCVersion.MC1_0, MCVersion.MC1_12_X, "FEATHER_FALLING"), + PROTECTION_EXPLOSIONS(MCVersion.MC1_0, MCVersion.MC1_12_X, "BLAST_PROTECTION"), + PROTECTION_PROJECTILE(MCVersion.MC1_0, MCVersion.MC1_12_X, "PROJECTILE_PROTECTION"), + WATER_WORKER(MCVersion.MC1_0, MCVersion.MC1_12_X, "AQUA_AFFINITY"), + ARROW_DAMAGE(MCVersion.MC1_1, MCVersion.MC1_12_X, "POWER"), + ARROW_KNOCKBACK(MCVersion.MC1_1, MCVersion.MC1_12_X, "PUNCH"), + ARROW_FIRE(MCVersion.MC1_1, MCVersion.MC1_12_X, "FLAME"), + ARROW_INFINITE(MCVersion.MC1_1, MCVersion.MC1_12_X, "INFINITY"), + SWEEPING_EDGE(MCVersion.MC1_11_X, MCVersion.MC1_12_X, "SWEEPING"), + AQUA_AFFINITY, + BANE_OF_ARTHROPODS, + BINDING_CURSE, + BLAST_PROTECTION, + CHANNELING, + DEPTH_STRIDER, + EFFICIENCY, + FEATHER_FALLING, + FIRE_ASPECT, + FIRE_PROTECTION, + FLAME, + FORTUNE, + FROST_WALKER, + IMPALING, + INFINITY, + KNOCKBACK, + LOOTING, + LOYALTY, + LUCK_OF_THE_SEA, + LURE, + MENDING, + POWER, + PROJECTILE_PROTECTION, + PROTECTION, + PUNCH, + RESPIRATION, + RIPTIDE, + SHARPNESS, + SILK_TOUCH, + SMITE, + THORNS, + UNBREAKING, + VANISHING_CURSE, + SWEEPING, + MULTISHOT(MCVersion.MC1_14), + PIERCING(MCVersion.MC1_14), + QUICK_CHARGE(MCVersion.MC1_14), + SOUL_SPEED(MCVersion.MC1_16_1), + SWIFT_SNEAK(MCVersion.MC1_19), + BREACH(MCVersion.MC1_20_6), + DENSITY(MCVersion.MC1_20_6), + WIND_BURST(MCVersion.MC1_20_6), + LUNGE(MCVersion.MC1_21_11), + UNKNOWN(MCVersion.NEVER); + + private final MCVersion since; + private final MCVersion until; + private final String rename; + + MCVanillaEnchantment() { + this(MCVersion.MC1_13); + } + + MCVanillaEnchantment(MCVersion since) { + this(since, MCVersion.FUTURE); + } + + MCVanillaEnchantment(MCVersion since, MCVersion until) { + this(since, until, null); + } + + MCVanillaEnchantment(MCVersion since, MCVersion until, String rename) { + this.since = since; + this.until = until; + this.rename = rename; + } + + public boolean existsIn(MCVersion version) { + return version.gte(since) && version.lte(until); + } + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCEnderDragonPhase.java b/src/main/java/com/laytonsmith/abstraction/enums/MCEnderDragonPhase.java new file mode 100644 index 0000000000..3429a3c6ab --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCEnderDragonPhase.java @@ -0,0 +1,17 @@ +package com.laytonsmith.abstraction.enums; + +public enum MCEnderDragonPhase { + + BREATH_ATTACK, + CHARGE_PLAYER, + CIRCLING, + DYING, + FLY_TO_PORTAL, + HOVER, + LAND_ON_PORTAL, + LEAVE_PORTAL, + ROAR_BEFORE_ATTACK, + SEARCH_FOR_BREATH_ATTACK_TARGET, + STRAFING + +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCEnterBedResult.java b/src/main/java/com/laytonsmith/abstraction/enums/MCEnterBedResult.java new file mode 100644 index 0000000000..124a0456dc --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCEnterBedResult.java @@ -0,0 +1,15 @@ +package com.laytonsmith.abstraction.enums; + +import com.laytonsmith.annotations.MEnum; + +@MEnum("com.commandhelper.EnterBedResult") +public enum MCEnterBedResult { + NOT_POSSIBLE_HERE, + NOT_POSSIBLE_NOW, + NOT_SAFE, + OK, + OTHER_PROBLEM, + TOO_FAR_AWAY, + OBSTRUCTED, // Paper + EXPLOSION, // Paper +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCEntityEffect.java b/src/main/java/com/laytonsmith/abstraction/enums/MCEntityEffect.java index 29d797c8fe..e55a769b57 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCEntityEffect.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCEntityEffect.java @@ -2,19 +2,81 @@ import com.laytonsmith.annotations.MEnum; -@MEnum("EntityEffect") +@MEnum("com.commandhelper.EntityEffect") public enum MCEntityEffect { - DEATH, + ARROW_PARTICLES, + RABBIT_JUMP, + DEATH, // deprecated for EGG_BREAK, SNOWBALL_BREAK, ENTITY_DEATH + EGG_BREAK, // deprecated for PROJECTILE_CRACK + SNOWBALL_BREAK, // deprecated for PROJECTILE_CRACK + PROJECTILE_CRACK, + ENTITY_DEATH, + HIT, HURT, - SHEEP_EAT, - WOLF_HEARTS, + SHEEP_EAT, // deprecated for SHEEP_EAT_GRASS, TNT_MINECART_IGNITE + SHEEP_EAT_GRASS, + TNT_MINECART_IGNITE, + WOLF_HEARTS, // deprecated for TAMING_SUCCEEDED WOLF_SHAKE, - WOLF_SMOKE, + WOLF_SMOKE, // deprecated for TAMING_FAILED + TAMING_FAILED, + TAMING_SUCCEEDED, IRON_GOLEM_ROSE, VILLAGER_HEART, VILLAGER_ANGRY, VILLAGER_HAPPY, WITCH_MAGIC, ZOMBIE_TRANSFORM, - FIREWORK_EXPLODE + FIREWORK_EXPLODE, + LOVE_HEARTS, + SQUID_ROTATE, + ENTITY_POOF, + GUARDIAN_TARGET, + SHIELD_BLOCK, // deprecated 1.21.5 + SHIELD_BREAK, // deprecated 1.21.5 + ARMOR_STAND_HIT, + THORNS_HURT, + IRON_GOLEM_SHEATH, + TOTEM_RESURRECT, // deprecated for PROTECTED_FROM_DEATH + PROTECTED_FROM_DEATH, + HURT_DROWN, + HURT_EXPLOSION, + DOLPHIN_FED, + RAVAGER_STUNNED, + RAVAGER_ROARED, + VILLAGER_SPLASH, + PLAYER_BAD_OMEN_RAID, + HURT_BERRY_BUSH, + FOX_CHEW, + TELEPORT_ENDER, + BREAK_EQUIPMENT_MAIN_HAND, + BREAK_EQUIPMENT_OFF_HAND, + BREAK_EQUIPMENT_HELMET, + BREAK_EQUIPMENT_CHESTPLATE, + BREAK_EQUIPMENT_LEGGINGS, + BREAK_EQUIPMENT_BOOTS, + BREAK_EQUIPMENT_BODY, + BREAK_EQUIPMENT_SADDLE, + RESET_SPAWNER_MINECART_DELAY, + FANG_ATTACK, // deprecated for ENTITY_ATTACK + HOGLIN_ATTACK, // deprecated for ENTITY_ATTACK + RAVAGER_ATTACK, // deprecated for ENTITY_ATTACK + WARDEN_ATTACK, // deprecated for ENTITY_ATTACK + ZOGLIN_ATTACK, // deprecated for ENTITY_ATTACK + ENTITY_ATTACK, + HONEY_BLOCK_SLIDE_PARTICLES, + HONEY_BLOCK_FALL_PARTICLES, + SWAP_HAND_ITEMS, + WOLF_SHAKE_STOP, + GOAT_LOWER_HEAD, + GOAT_RAISE_HEAD, + SPAWN_DEATH_SMOKE, + WARDEN_TENDRIL_SHAKE, + WARDEN_SONIC_ATTACK, + SNIFFER_DIG, + ARMADILLO_PEEK, + DROWN_PARTICLES, + SHAKE, + TRUSTING_FAILED, + TRUSTING_SUCCEEDED, } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCEntityType.java b/src/main/java/com/laytonsmith/abstraction/enums/MCEntityType.java index 8a1d6fa000..1f82d2f424 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCEntityType.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCEntityType.java @@ -1,101 +1,351 @@ - package com.laytonsmith.abstraction.enums; -import com.laytonsmith.annotations.MEnum; +import com.laytonsmith.PureUtilities.ClassLoading.DynamicEnum; +import com.laytonsmith.abstraction.MCEntity; +import com.laytonsmith.annotations.MDynamicEnum; +import com.laytonsmith.core.MSLog; +import com.laytonsmith.core.MSLog.Tags; +import com.laytonsmith.core.constructs.Target; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@MDynamicEnum("com.commandhelper.EntityType") +public abstract class MCEntityType extends DynamicEnum { + + protected static final Map MAP = new HashMap<>(); + protected static final Map VANILLA_MAP = new HashMap<>(); + + protected Class wrapperClass; + + public MCEntityType(MCVanillaEntityType abstractedType, Concrete concreteType) { + super(abstractedType, concreteType); + } -/** - * - * - */ -@MEnum("EntityType") -public enum MCEntityType { - /** - * Spawn with {@link World#dropItem}} - */ - DROPPED_ITEM(true), - EXPERIENCE_ORB(true), - PAINTING(false), - ARROW(true), - SNOWBALL(true), - FIREBALL(true), - SMALL_FIREBALL(true), - ITEM_FRAME(false), - WITHER_SKULL(true), - WITHER(true), - BAT(true), - WITCH(true), - ENDER_PEARL(false), - ENDER_SIGNAL(false), - THROWN_EXP_BOTTLE(true), - PRIMED_TNT(true), - /** - * Spawn with {@link World#spawnFallingBlock}} - */ - FALLING_BLOCK(true), - MINECART(true), - BOAT(true), - CREEPER(true), - SKELETON(true), - SPIDER(true), - GIANT(true), - ZOMBIE(true), - SLIME(true), - GHAST(true), - PIG_ZOMBIE(true), - ENDERMAN(true), - CAVE_SPIDER(true), - SILVERFISH(true), - BLAZE(true), - MAGMA_CUBE(true), - ENDER_DRAGON(true), - PIG(true), - SHEEP(true), - COW(true), - CHICKEN(true), - SQUID(true), - WOLF(true), - MUSHROOM_COW(true), - SNOWMAN(true), - OCELOT(true), - IRON_GOLEM(true), - VILLAGER(true), - HORSE(true), - LEASH_HITCH(false), - ENDER_CRYSTAL(true), - // These don't have an entity ID in nms.EntityTypes. - SPLASH_POTION(true), - EGG(true), - FISHING_HOOK(false), /** - * Spawn with {@link World#strikeLightning}. + * Utility method for spawn_entity + * + * @return whether the implemented api can spawn this entity */ - LIGHTNING(true), - WEATHER(true), - PLAYER(false), - COMPLEX_PART(false), - FIREWORK(true), - MINECART_CHEST(true), - MINECART_FURNACE(true), - MINECART_TNT(true), - MINECART_HOPPER(true), - MINECART_MOB_SPAWNER(true), - MINECART_COMMAND(false), + public abstract boolean isSpawnable(); + + public abstract boolean isProjectile(); + + @Override + public MCVanillaEntityType getAbstracted() { + return super.getAbstracted(); + } + + public Class getWrapperClass() { + return wrapperClass; + } + + public static MCEntityType valueOf(String test) throws IllegalArgumentException { + MCEntityType ret = MAP.get(test); + if(ret == null) { + switch(test) { + case "TIPPED_ARROW": + MSLog.GetLogger().e(MSLog.Tags.GENERAL, + "TIPPED_ARROW entity type was removed in 1.14. Converted to ARROW.", + Target.UNKNOWN); + return MAP.get("ARROW"); + case "LINGERING_POTION": + MSLog.GetLogger().e(MSLog.Tags.GENERAL, + "LINGERING_POTION entity type was removed from 1.14 to 1.21.4. Converted to SPLASH_POTION.", + Target.UNKNOWN); + return MAP.get("SPLASH_POTION"); + case "PIG_ZOMBIE": + MSLog.GetLogger().e(MSLog.Tags.GENERAL, + "PIG_ZOMBIE entity type was renamed in 1.16. Converted to ZOMBIFIED_PIGLIN.", + Target.UNKNOWN); + return MAP.get("ZOMBIFIED_PIGLIN"); + case "BOAT": + MSLog.GetLogger().e(Tags.GENERAL, + "BOAT entity type was split by wood types in 1.21.3. Converted to OAK_BOAT.", + Target.UNKNOWN); + return MAP.get("OAK_BOAT"); + case "CHEST_BOAT": + MSLog.GetLogger().e(Tags.GENERAL, + "CHEST_BOAT entity type was split by wood types in 1.21.3. Converted to OAK_CHEST_BOAT.", + Target.UNKNOWN); + return MAP.get("OAK_CHEST_BOAT"); + } + throw new IllegalArgumentException("Unknown entity type: " + test); + } + return ret; + } + + public static MCEntityType valueOfVanillaType(MCVanillaEntityType type) { + return VANILLA_MAP.get(type); + } + /** - * An unknown entity without an Entity Class + * @return Names of available entity types */ - UNKNOWN(false); - - private boolean apiCanSpawn; - + public static Set types() { + if(MAP.isEmpty()) { // docs mode + Set dummy = new HashSet<>(); + for(final MCVanillaEntityType t : MCVanillaEntityType.values()) { + if(t.existsIn(MCVersion.CURRENT)) { + dummy.add(t.name()); + } + } + return dummy; + } + return MAP.keySet(); + } + /** - * - * @param spawnable true if the entity is spawnable + * @return Our own EntityType list */ - MCEntityType(boolean spawnable) { - this.apiCanSpawn = spawnable; + public static List values() { + if(MAP.isEmpty()) { // docs mode + ArrayList dummy = new ArrayList<>(); + for(final MCVanillaEntityType t : MCVanillaEntityType.values()) { + if(!t.existsIn(MCVersion.CURRENT)) { + continue; + } + dummy.add(new MCEntityType<>(t, null) { + @Override + public String name() { + return t.name(); + } + + @Override + public boolean isSpawnable() { + return t.isSpawnable(); + } + + @Override + public boolean isProjectile() { + return false; + } + }); + } + return dummy; + } + return new ArrayList<>(MAP.values()); } - - public boolean isSpawnable() { - return this.apiCanSpawn; + + public enum MCVanillaEntityType { + ALLAY(true, MCVersion.MC1_19), + AREA_EFFECT_CLOUD, + ARMADILLO(true, MCVersion.MC1_20_6), + ARMOR_STAND, + ARROW, + AXOLOTL(true, MCVersion.MC1_17), + BAT, + BEE(true, MCVersion.MC1_15), + BLAZE, + BLOCK_DISPLAY(true, MCVersion.MC1_19_4), + + // Boat types + BOAT(true, MCVersion.MC1_0, MCVersion.MC1_21_1), + CHEST_BOAT(true, MCVersion.MC1_19, MCVersion.MC1_21_1), + ACACIA_BOAT(true, MCVersion.MC1_21_3), + ACACIA_CHEST_BOAT(true, MCVersion.MC1_21_3), + BAMBOO_RAFT(true, MCVersion.MC1_21_3), + BAMBOO_CHEST_RAFT(true, MCVersion.MC1_21_3), + BIRCH_BOAT(true, MCVersion.MC1_21_3), + BIRCH_CHEST_BOAT(true, MCVersion.MC1_21_3), + CHERRY_BOAT(true, MCVersion.MC1_21_3), + CHERRY_CHEST_BOAT(true, MCVersion.MC1_21_3), + DARK_OAK_BOAT(true, MCVersion.MC1_21_3), + DARK_OAK_CHEST_BOAT(true, MCVersion.MC1_21_3), + JUNGLE_BOAT(true, MCVersion.MC1_21_3), + JUNGLE_CHEST_BOAT(true, MCVersion.MC1_21_3), + MANGROVE_BOAT(true, MCVersion.MC1_21_3), + MANGROVE_CHEST_BOAT(true, MCVersion.MC1_21_3), + OAK_BOAT(true, MCVersion.MC1_21_3), + OAK_CHEST_BOAT(true, MCVersion.MC1_21_3), + PALE_OAK_BOAT(true, MCVersion.MC1_21_3), + PALE_OAK_CHEST_BOAT(true, MCVersion.MC1_21_3), + SPRUCE_BOAT(true, MCVersion.MC1_21_3), + SPRUCE_CHEST_BOAT(true, MCVersion.MC1_21_3), + + BOGGED(true, MCVersion.MC1_20_6), + BREEZE(true, MCVersion.MC1_20_4), + BREEZE_WIND_CHARGE(true, MCVersion.MC1_20_6), + CAMEL(true, MCVersion.MC1_19_3), + CAMEL_HUSK(true, MCVersion.MC1_21_11), + CAT(true, MCVersion.MC1_14), + CAVE_SPIDER, + CHICKEN, + COD, + COPPER_GOLEM(true, MCVersion.MC1_21_9), + COW, + CREAKING(true, MCVersion.MC1_21_3), + CREEPER, + DOLPHIN, + DRAGON_FIREBALL, + DROPPED_ITEM, + DROWNED, + DONKEY, + EGG, + ELDER_GUARDIAN, + ENDERMAN, + ENDERMITE, + ENDER_CRYSTAL, + ENDER_DRAGON, + ENDER_EYE, + ENDER_PEARL, + EVOKER, + EVOKER_FANGS, + EXPERIENCE_ORB, + FALLING_BLOCK, + FIREBALL, + FIREWORK, + FISHING_HOOK(false), + FOX(true, MCVersion.MC1_14), + FROG(true, MCVersion.MC1_19), + GHAST, + GIANT, + GLOW_ITEM_FRAME(true, MCVersion.MC1_17), + GLOW_SQUID(true, MCVersion.MC1_17), + GOAT(true, MCVersion.MC1_17), + GUARDIAN, + HAPPY_GHAST(true, MCVersion.MC1_21_6), + HOGLIN(true, MCVersion.MC1_16), + HORSE, + HUSK, + ILLUSIONER, + INTERACTION(true, MCVersion.MC1_19_4), + IRON_GOLEM, + ITEM_DISPLAY(true, MCVersion.MC1_19_4), + ITEM_FRAME, + LLAMA, + LLAMA_SPIT, + LEASH_HITCH, + LIGHTNING, + LINGERING_POTION(true, MCVersion.MC1_21_5), + MAGMA_CUBE, + MANNEQUIN(true, MCVersion.MC1_21_9), + MARKER(true, MCVersion.MC1_17), + MINECART, + MINECART_CHEST, + MINECART_COMMAND, + MINECART_FURNACE, + MINECART_HOPPER, + MINECART_MOB_SPAWNER, + MINECART_TNT, + MULE, + MUSHROOM_COW, + NAUTILUS(true, MCVersion.MC1_21_11), + OCELOT, + OMINOUS_ITEM_SPAWNER(true, MCVersion.MC1_20_6), + PAINTING, + PANDA(true, MCVersion.MC1_14), + PARCHED(true, MCVersion.MC1_21_11), + PARROT, + PHANTOM, + PIG, + PIGLIN(true, MCVersion.MC1_16), + PIGLIN_BRUTE(true, MCVersion.MC1_16_X), + PILLAGER(true, MCVersion.MC1_14), + PLAYER(false), + POLAR_BEAR, + PRIMED_TNT, + PUFFERFISH, + RABBIT, + RAVAGER(true, MCVersion.MC1_14), + SALMON, + SHEEP, + SILVERFISH, + SKELETON, + SHULKER, + SHULKER_BULLET, + SKELETON_HORSE, + SLIME, + SMALL_FIREBALL, + SNIFFER(true, MCVersion.MC1_19_4), + SNOWBALL, + SNOWMAN, + SQUID, + SPECTRAL_ARROW, + SPIDER, + SPLASH_POTION, + STRAY, + STRIDER(true, MCVersion.MC1_16), + TADPOLE(true, MCVersion.MC1_19), + TEXT_DISPLAY(true, MCVersion.MC1_19_4), + THROWN_EXP_BOTTLE, + TRADER_LLAMA(true, MCVersion.MC1_14), + TRIDENT, + TROPICAL_FISH, + TURTLE, + VEX, + VILLAGER, + VINDICATOR, + WANDERING_TRADER(true, MCVersion.MC1_14), + WARDEN(true, MCVersion.MC1_19), + WIND_CHARGE(true, MCVersion.MC1_20_4), + WITCH, + WITHER, + WITHER_SKELETON, + WITHER_SKULL, + WOLF, + ZOGLIN(true, MCVersion.MC1_16), + ZOMBIE, + ZOMBIE_HORSE, + ZOMBIE_NAUTILUS(true, MCVersion.MC1_21_11), + ZOMBIE_VILLAGER, + ZOMBIFIED_PIGLIN(true, MCVersion.MC1_16), + /** + * An unknown entity without an Entity Class + */ + UNKNOWN(false); + + private final boolean canSpawn; + private final MCVersion from; + private final MCVersion to; + + MCVanillaEntityType() { + this.canSpawn = true; + this.from = MCVersion.MC1_0; + this.to = MCVersion.FUTURE; + } + + /** + * @param spawnable true if the entity is spawnable + */ + MCVanillaEntityType(boolean spawnable) { + this.canSpawn = spawnable; + this.from = MCVersion.MC1_0; + this.to = MCVersion.FUTURE; + } + + /** + * @param spawnable true if the entity is spawnable + * @param added the version this entity was added + */ + MCVanillaEntityType(boolean spawnable, MCVersion added) { + this.canSpawn = spawnable; + this.from = added; + this.to = MCVersion.FUTURE; + } + + /** + * @param spawnable true if the entity is spawnable + * @param added the version this entity was added + * @param removed the version this entity was removed or renamed + */ + MCVanillaEntityType(boolean spawnable, MCVersion added, MCVersion removed) { + this.canSpawn = spawnable; + this.from = added; + this.to = removed; + } + + // This is here only for site-based documentation of some functions + public boolean isSpawnable() { + return this.canSpawn; + } + + public boolean existsIn(MCVersion version) { + return version.gte(from) && version.lte(to); + } } } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCEquipmentSlot.java b/src/main/java/com/laytonsmith/abstraction/enums/MCEquipmentSlot.java index a66ce79fa8..fd3e9ee259 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCEquipmentSlot.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCEquipmentSlot.java @@ -2,15 +2,14 @@ import com.laytonsmith.annotations.MEnum; -/** - * - * @author jb_aero - */ -@MEnum("EquipmentSlot") +@MEnum("com.commandhelper.EquipmentSlot") public enum MCEquipmentSlot { WEAPON, + OFF_HAND, BOOTS, LEGGINGS, CHESTPLATE, - HELMET + HELMET, + BODY, + SADDLE, } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCEquipmentSlotGroup.java b/src/main/java/com/laytonsmith/abstraction/enums/MCEquipmentSlotGroup.java new file mode 100644 index 0000000000..25a1063c60 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCEquipmentSlotGroup.java @@ -0,0 +1,9 @@ +package com.laytonsmith.abstraction.enums; + +public enum MCEquipmentSlotGroup { + ANY, + HAND, + ARMOR, + BODY, + SADDLE, +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCFireworkType.java b/src/main/java/com/laytonsmith/abstraction/enums/MCFireworkType.java index 94874b42eb..d836c8b1ab 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCFireworkType.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCFireworkType.java @@ -1,20 +1,12 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ package com.laytonsmith.abstraction.enums; import com.laytonsmith.annotations.MEnum; -/** - * - * - */ -@MEnum("FireworkType") +@MEnum("com.commandhelper.FireworkType") public enum MCFireworkType { BALL, BALL_LARGE, STAR, BURST, - CREEPER; + CREEPER } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCFishingState.java b/src/main/java/com/laytonsmith/abstraction/enums/MCFishingState.java index ea97788e95..cc0870ff9f 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCFishingState.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCFishingState.java @@ -2,11 +2,14 @@ import com.laytonsmith.annotations.MEnum; -@MEnum("FishingState") +@MEnum("com.commandhelper.FishingState") public enum MCFishingState { CAUGHT_ENTITY, CAUGHT_FISH, FAILED_ATTEMPT, FISHING, - IN_GROUND + IN_GROUND, + BITE, + REEL_IN, + LURED } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCFoxType.java b/src/main/java/com/laytonsmith/abstraction/enums/MCFoxType.java new file mode 100644 index 0000000000..314ec99145 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCFoxType.java @@ -0,0 +1,9 @@ +package com.laytonsmith.abstraction.enums; + +import com.laytonsmith.annotations.MEnum; + +@MEnum("com.commandhelper.FoxType") +public enum MCFoxType { + RED, + SNOW +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCGameMode.java b/src/main/java/com/laytonsmith/abstraction/enums/MCGameMode.java index 7ce25faad8..23b2ddab6b 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCGameMode.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCGameMode.java @@ -1,13 +1,11 @@ - package com.laytonsmith.abstraction.enums; import com.laytonsmith.annotations.MEnum; -/** - * - * - */ -@MEnum("GameMode") +@MEnum("com.commandhelper.GameMode") public enum MCGameMode { - SURVIVAL, CREATIVE, ADVENTURE + SURVIVAL, + CREATIVE, + ADVENTURE, + SPECTATOR } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCGameRule.java b/src/main/java/com/laytonsmith/abstraction/enums/MCGameRule.java index aa7cd5ffa1..7f8e3ca853 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCGameRule.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCGameRule.java @@ -1,31 +1,136 @@ package com.laytonsmith.abstraction.enums; import com.laytonsmith.annotations.MEnum; +import com.laytonsmith.core.Static; -/** - * Gamerule names - * - * @author Hekta - */ -@MEnum("GameRule") +import java.util.HashMap; +import java.util.Map; + +@MEnum("com.commandhelper.GameRule") public enum MCGameRule { - COMMANDBLOCKOUTPUT("commandBlockOutput"), - DODAYLIGHTCYCLE("doDaylightCycle"), - DOFIRETICK("doFireTick"), - DOMOBLOOT("doMobLoot"), - DOMOBSPAWNING("doMobSpawning"), - DOTILEDROPS("doTileDrops"), - KEEPINVENTORY("keepInventory"), - MOBGRIEFING("mobGriefing"), - NATURALREGENERATION("naturalRegeneration"); - - private final String gameRule; - - MCGameRule(String gameRule) { - this.gameRule = gameRule; + ADVANCE_TIME("doDaylightCycle"), + ADVANCE_WEATHER("doWeatherCycle"), + BLOCK_DROPS("doTileDrops"), + ALLOW_ENTERING_NETHER_USING_PORTALS("allowEnteringNetherUsingPortals"), + BLOCK_EXPLOSION_DROP_DECAY("blockExplosionDropDecay"), + COMMAND_BLOCK_OUTPUT("commandBlockOutput"), + COMMAND_BLOCKS_WORK("commandBlocksEnabled"), + DROWNING_DAMAGE("drowningDamage"), + ELYTRA_MOVEMENT_CHECK("disableElytraMovementCheck"), + ENDER_PEARLS_VANISH_ON_DEATH("enderPearlsVanishOnDeath"), + ENTITY_DROPS("doEntityDrops"), + FALL_DAMAGE("fallDamage"), + FIRE_DAMAGE("fireDamage"), + FIRE_SPREAD_RADIUS_AROUND_PLAYER, + FORGIVE_DEAD_PLAYERS("forgiveDeadPlayers"), + FREEZE_DAMAGE("freezeDamage"), + GLOBAL_SOUND_EVENTS("globalSoundEvents"), + IMMEDIATE_RESPAWN("doImmediateRespawn"), + KEEP_INVENTORY("keepInventory"), + LAVA_SOURCE_CONVERSION("lavaSourceConversion"), + LIMITED_CRAFTING("doLimitedCrafting"), + LOCATOR_BAR("locatorBar"), + LOG_ADMIN_COMMANDS("logAdminCommands"), + MAX_BLOCK_MODIFICATIONS("commandModificationBlockLimit"), + MAX_COMMAND_FORKS("maxCommandForkCount"), + MAX_COMMAND_SEQUENCE_LENGTH("maxCommandChainLength"), + MAX_ENTITY_CRAMMING("maxEntityCramming"), + MAX_MINECART_SPEED("minecartMaxSpeed"), + MAX_SNOW_ACCUMULATION_HEIGHT("snowAccumulationHeight"), + MOB_DROPS("doMobLoot"), + MOB_EXPLOSION_DROP_DECAY("mobExplosionDropDecay"), + MOB_GRIEFING("mobGriefing"), + NATURAL_HEALTH_REGENERATION("naturalRegeneration"), + PLAYER_MOVEMENT_CHECK("disablePlayerMovementCheck"), + PLAYERS_NETHER_PORTAL_CREATIVE_DELAY("playersNetherPortalCreativeDelay"), + PLAYERS_NETHER_PORTAL_DEFAULT_DELAY("playersNetherPortalDefaultDelay"), + PLAYERS_SLEEPING_PERCENTAGE("playersSleepingPercentage"), + PROJECTILES_CAN_BREAK_BLOCKS("projectilesCanBreakBlocks"), + PVP("pvp"), + RAIDS("disableRaids"), + RANDOM_TICK_SPEED("randomTickSpeed"), + REDUCED_DEBUG_INFO("reducedDebugInfo"), + RESPAWN_RADIUS("spawnRadius"), + SEND_COMMAND_FEEDBACK("sendCommandFeedback"), + SHOW_ADVANCEMENT_MESSAGES("announceAdvancements"), + SHOW_DEATH_MESSAGES("showDeathMessages"), + SPAWN_MOBS("doMobSpawning"), + SPAWN_MONSTERS("spawnMonsters"), + SPAWN_PATROLS("doPatrolSpawning"), + SPAWN_PHANTOMS("doInsomnia"), + SPAWN_WANDERING_TRADERS("doTraderSpawning"), + SPAWN_WARDENS("doWardenSpawning"), + SPAWNER_BLOCKS_WORK("spawnerBlocksEnabled"), + SPECTATORS_GENERATE_CHUNKS("spectatorsGenerateChunks"), + SPREAD_VINES("doVinesSpread"), + TNT_EXPLODES("tntExplodes"), + TNT_EXPLOSION_DROP_DECAY("tntExplosionDropDecay"), + UNIVERSAL_ANGER("universalAnger"), + WATER_SOURCE_CONVERSION("waterSourceConversion"); + + private static final Map BY_LEGACY_NAME = new HashMap<>(); + + public static String getByLegacyName(String name) { + if(BY_LEGACY_NAME.isEmpty()) { + for(MCGameRule rule : MCGameRule.values()) { + if(rule.legacyName != null) { + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_21_11)) { + BY_LEGACY_NAME.put(rule.legacyName.toLowerCase(), rule.getRuleName()); + } else { + BY_LEGACY_NAME.put(rule.legacyName.toLowerCase(), rule.legacyName); + } + } + } + } + + name = name.toLowerCase(); + String ruleName = BY_LEGACY_NAME.get(name); + if(ruleName != null) { + return ruleName; + } + // try removed game rules + MCVersion version = Static.getServer().getMinecraftVersion(); + if(version.lt(MCVersion.MC1_21_11)) { + if(name.equals("allowfireticksawayfromplayer")) { + return "allowFireTicksAwayFromPlayer"; + } else if(name.equals("dofiretick")) { + return "doFireTick"; + } + if(version.lt(MCVersion.MC1_21_9)) { + if(name.equals("spawnchunkradius")) { + return "spawnChunkRadius"; + } + } + } + return null; + } + + public static boolean isBoolInvertedFromLegacy(String name) { + return name.equals("elytra_movement_check") + || name.equals("player_movement_check") + || name.equals("raids"); + } + + private final String legacyName; + + MCGameRule() { + this.legacyName = null; + } + + MCGameRule(String legacyName) { + this.legacyName = legacyName; } - public String getGameRule() { - return this.gameRule; - } -} \ No newline at end of file + public static String[] getGameRules() { + MCGameRule[] values = MCGameRule.values(); + String[] names = new String[values.length]; + for(int i = 0; i < values.length; i++) { + names[i] = values[i].getRuleName(); + } + return names; + } + + public String getRuleName() { + return this.name().toLowerCase(); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCIgniteCause.java b/src/main/java/com/laytonsmith/abstraction/enums/MCIgniteCause.java index 31cc0fe552..51ef49c30d 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCIgniteCause.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCIgniteCause.java @@ -2,18 +2,14 @@ import com.laytonsmith.annotations.MEnum; -/** - * - * @author MariuszT - */ -@MEnum("IgniteCause") +@MEnum("com.commandhelper.IgniteCause") public enum MCIgniteCause { - LAVA, FLINT_AND_STEEL, SPREAD, LIGHTNING, FIREBALL, ENDER_CRYSTAL, - EXPLOSION; + EXPLOSION, + ARROW } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCInstrument.java b/src/main/java/com/laytonsmith/abstraction/enums/MCInstrument.java index 6d087da1c4..92f6b1ad8d 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCInstrument.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCInstrument.java @@ -1,17 +1,30 @@ - package com.laytonsmith.abstraction.enums; import com.laytonsmith.annotations.MEnum; -/** - * - * - */ -@MEnum("Instrument") +@MEnum("com.commandhelper.Instrument") public enum MCInstrument { PIANO, - BASS_DRUM, - SNARE_DRUM, - STICKS, - BASS_GUITAR + BASS_DRUM, + SNARE_DRUM, + STICKS, + BASS_GUITAR, + FLUTE, + BELL, + GUITAR, + CHIME, + XYLOPHONE, + IRON_XYLOPHONE, + COW_BELL, + DIDGERIDOO, + BIT, + BANJO, + PLING, + ZOMBIE, + SKELETON, + CREEPER, + DRAGON, + WITHER_SKELETON, + PIGLIN, + CUSTOM_HEAD } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCInventoryAction.java b/src/main/java/com/laytonsmith/abstraction/enums/MCInventoryAction.java index ab469fc730..680771390a 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCInventoryAction.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCInventoryAction.java @@ -4,91 +4,109 @@ /** * An estimation of what the result will be - * @author Riking */ -@MEnum("InventoryAction") +@MEnum("com.commandhelper.InventoryAction") public enum MCInventoryAction { /** - * Nothing will happen from the click. - * There may be cases where nothing will happen and this is value is - * not provided, but it is guaranteed that this value is accurate - * when given. - */ - NOTHING, - /** - * All of the items on the clicked slot are moved to the cursor. - */ - PICKUP_ALL, - /** - * Some of the items on the clicked slot are moved to the cursor. - */ - PICKUP_SOME, - /** - * Half of the items on the clicked slot are moved to the cursor. - */ - PICKUP_HALF, - /** - * One of the items on the clicked slot are moved to the cursor. - */ - PICKUP_ONE, - /** - * All of the items on the cursor are moved to the clicked slot. - */ - PLACE_ALL, - /** - * Some of the items from the cursor are moved to the clicked slot - * (usually up to the max stack size). - */ - PLACE_SOME, - /** - * A single item from the cursor is moved to the clicked slot. - */ - PLACE_ONE, - /** - * The clicked item and the cursor are exchanged. - */ - SWAP_WITH_CURSOR, - /** - * The entire cursor item is dropped. - */ - DROP_ALL_CURSOR, - /** - * One item is dropped from the cursor. - */ - DROP_ONE_CURSOR, - /** - * The entire clicked slot is dropped. - */ - DROP_ALL_SLOT, - /** - * One item is dropped from the clicked slot. - */ - DROP_ONE_SLOT, - /** - * The item is moved to the opposite inventory if a space is found. - */ - MOVE_TO_OTHER_INVENTORY, - /** - * The clicked item is moved to the hotbar, and the item currently - * there is re-added to the player's inventory. - */ - HOTBAR_MOVE_AND_READD, - /** - * The clicked slot and the picked hotbar slot are swapped. - */ - HOTBAR_SWAP, - /** - * A max-size stack of the clicked item is put on the cursor. - */ - CLONE_STACK, - /** - * The inventory is searched for the same material, and they are put - * on the cursor up to {@link org.bukkit.Material#getMaxStackSize()}. - */ - COLLECT_TO_CURSOR, - /** - * An unrecognized ClickType. - */ - UNKNOWN, - ; + * Nothing will happen from the click. There may be cases where nothing will happen and this is value is not + * provided, but it is guaranteed that this value is accurate when given. + */ + NOTHING, + /** + * All of the items on the clicked slot are moved to the cursor. + */ + PICKUP_ALL, + /** + * Some of the items on the clicked slot are moved to the cursor. + */ + PICKUP_SOME, + /** + * Half of the items on the clicked slot are moved to the cursor. + */ + PICKUP_HALF, + /** + * One of the items on the clicked slot are moved to the cursor. + */ + PICKUP_ONE, + /** + * All of the items on the cursor are moved to the clicked slot. + */ + PLACE_ALL, + /** + * Some of the items from the cursor are moved to the clicked slot (usually up to the max stack size). + */ + PLACE_SOME, + /** + * A single item from the cursor is moved to the clicked slot. + */ + PLACE_ONE, + /** + * The clicked item and the cursor are exchanged. + */ + SWAP_WITH_CURSOR, + /** + * The entire cursor item is dropped. + */ + DROP_ALL_CURSOR, + /** + * One item is dropped from the cursor. + */ + DROP_ONE_CURSOR, + /** + * The entire clicked slot is dropped. + */ + DROP_ALL_SLOT, + /** + * One item is dropped from the clicked slot. + */ + DROP_ONE_SLOT, + /** + * The item is moved to the opposite inventory if a space is found. + */ + MOVE_TO_OTHER_INVENTORY, + /** + * The clicked item is moved to the hotbar, and the item currently there is re-added to the player's inventory. + */ + HOTBAR_MOVE_AND_READD, + /** + * The clicked slot and the picked hotbar slot are swapped. + */ + HOTBAR_SWAP, + /** + * A max-size stack of the clicked item is put on the cursor. + */ + CLONE_STACK, + /** + * The inventory is searched for the same material, and they are put on the cursor up to + * {@link org.bukkit.Material#getMaxStackSize()}. + */ + COLLECT_TO_CURSOR, + /** + * The first stack of items in the clicked bundle is moved to the cursor. + */ + PICKUP_FROM_BUNDLE, + /** + * All of the items on the clicked slot are moved into the bundle on the cursor. + */ + PICKUP_ALL_INTO_BUNDLE, + /** + * Some of the items on the clicked slot are moved into the bundle on the cursor. + */ + PICKUP_SOME_INTO_BUNDLE, + /** + * The first stack of items is moved to the clicked slot. + */ + PLACE_FROM_BUNDLE, + /** + * All of the items on the cursor are moved into the bundle in the clicked slot. + */ + PLACE_ALL_INTO_BUNDLE, + /** + * Some of the items on the cursor are moved into the bundle in the clicked slot. + */ + PLACE_SOME_INTO_BUNDLE, + /** + * An unrecognized ClickType. + */ + UNKNOWN,; } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCInventoryType.java b/src/main/java/com/laytonsmith/abstraction/enums/MCInventoryType.java index b892f3acdc..e040771754 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCInventoryType.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCInventoryType.java @@ -1,27 +1,53 @@ - package com.laytonsmith.abstraction.enums; import com.laytonsmith.annotations.MEnum; -/** - * - * - */ -@MEnum("InventoryType") +@MEnum("com.commandhelper.InventoryType") public enum MCInventoryType { BREWING, CHEST, - CRAFTING, - CREATIVE, + COMPOSTER(false), // no inventory view + CRAFTING(false), + CREATIVE(false), DISPENSER, DROPPER, - ENCHANTING, + ENCHANTING(false), // non-functional when virtualized ENDER_CHEST, FURNACE, HOPPER, - MERCHANT, + JUKEBOX(false), + MERCHANT(false), PLAYER, WORKBENCH, ANVIL, - BEACON + SMITHING, + BEACON(false), + SHULKER_BOX, + BARREL, + BLAST_FURNACE, + LECTERN(false), + SMOKER, + LOOM(false), + CARTOGRAPHY(false), + GRINDSTONE(false), + STONECUTTER(false), + CHISELED_BOOKSHELF(false), + CRAFTER(false), + DECORATED_POT(false), + SHELF(false); + + // Whether or not this inventory type can be created and used virtually + private final boolean canVirtualize; + + MCInventoryType() { + this.canVirtualize = true; + } + + MCInventoryType(boolean virtual) { + this.canVirtualize = virtual; + } + + public boolean canVirtualize() { + return this.canVirtualize; + } } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCItemFlag.java b/src/main/java/com/laytonsmith/abstraction/enums/MCItemFlag.java new file mode 100644 index 0000000000..796642601c --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCItemFlag.java @@ -0,0 +1,51 @@ +package com.laytonsmith.abstraction.enums; + +import com.laytonsmith.annotations.MEnum; + +/** + * A MCItemFlag can hide some Attributes from MCItemStacks, through MCItemMeta. + */ +@MEnum("com.commandhelper.ItemFlag") +public enum MCItemFlag { + /** + * Hides enchantments + */ + HIDE_ENCHANTS, + /** + * Hides Attributes like Damage + */ + HIDE_ATTRIBUTES, + /** + * Hides unbreakable state + */ + HIDE_UNBREAKABLE, + /** + * Hides what the ItemStack can break/destroy + */ + HIDE_DESTROYS, + /** + * Hides where this ItemStack can be built/placed on + */ + HIDE_PLACED_ON, + /** + * Hides potion effects (and additional tooltips) + * Deprecated for HIDE_ADDITIONAL_TOOLTIP + */ + HIDE_POTION_EFFECTS, + /** + * Hides any additional tooltips (like HIDE_POTION_EFFECTS) + */ + HIDE_ADDITIONAL_TOOLTIP, + /** + * Hide dyes from coloured leather armour + */ + HIDE_DYE, + /** + * Hides armor trim + */ + HIDE_ARMOR_TRIM, + /** + * Hides enchantments on enchanted books + */ + HIDE_STORED_ENCHANTS +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCItemRarity.java b/src/main/java/com/laytonsmith/abstraction/enums/MCItemRarity.java new file mode 100644 index 0000000000..f41329ae1c --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCItemRarity.java @@ -0,0 +1,11 @@ +package com.laytonsmith.abstraction.enums; + +import com.laytonsmith.annotations.MEnum; + +@MEnum("com.commandhelper.ItemRarity") +public enum MCItemRarity { + COMMON, + UNCOMMON, + RARE, + EPIC +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCMobs.java b/src/main/java/com/laytonsmith/abstraction/enums/MCMobs.java deleted file mode 100644 index 0bfd0b3393..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCMobs.java +++ /dev/null @@ -1,44 +0,0 @@ - -package com.laytonsmith.abstraction.enums; - -import com.laytonsmith.annotations.MEnum; - -/** - * These don't directly map to entity types, because this is mostly used for - * spawining mobs. - * - */ -@MEnum("Mobs") -public enum MCMobs { - BAT, - BLAZE, - CAVESPIDER, - CHICKEN, - COW, - CREEPER, - ENDERDRAGON, - ENDERMAN, - GHAST, - GIANT, - HORSE, - IRONGOLEM, - MAGMACUBE, - MOOSHROOM, - OCELOT, - PIG, - PIGZOMBIE, - SHEEP, - SILVERFISH, - SKELETON, - SLIME, - SNOWGOLEM, - SPIDER, - SPIDERJOCKEY, - SQUID, - VILLAGER, - WITCH, - WITHER, - WITHER_SKULL, - WOLF, - ZOMBIE -} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCMushroomCowType.java b/src/main/java/com/laytonsmith/abstraction/enums/MCMushroomCowType.java new file mode 100644 index 0000000000..cb6977173f --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCMushroomCowType.java @@ -0,0 +1,9 @@ +package com.laytonsmith.abstraction.enums; + +import com.laytonsmith.annotations.MEnum; + +@MEnum("com.commandhelper.MushroomCowType") +public enum MCMushroomCowType { + RED, + BROWN +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCOcelotType.java b/src/main/java/com/laytonsmith/abstraction/enums/MCOcelotType.java deleted file mode 100644 index ebba754119..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCOcelotType.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.laytonsmith.abstraction.enums; - -import com.laytonsmith.annotations.MEnum; - -/** - * - * @author jb_aero - */ -@MEnum("OcelotType") -public enum MCOcelotType { - BLACK_CAT, - RED_CAT, - SIAMESE_CAT, - WILD_OCELOT -} \ No newline at end of file diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCOption.java b/src/main/java/com/laytonsmith/abstraction/enums/MCOption.java new file mode 100644 index 0000000000..7037c5e8cd --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCOption.java @@ -0,0 +1,10 @@ +package com.laytonsmith.abstraction.enums; + +import com.laytonsmith.annotations.MEnum; + +@MEnum("com.commandhelper.Option") +public enum MCOption { + COLLISION_RULE, + DEATH_MESSAGE_VISIBILITY, + NAME_TAG_VISIBILITY +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCOptionStatus.java b/src/main/java/com/laytonsmith/abstraction/enums/MCOptionStatus.java new file mode 100644 index 0000000000..ff88ba5b3c --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCOptionStatus.java @@ -0,0 +1,11 @@ +package com.laytonsmith.abstraction.enums; + +import com.laytonsmith.annotations.MEnum; + +@MEnum("com.commandhelper.OptionStatus") +public enum MCOptionStatus { + ALWAYS, + FOR_OTHER_TEAMS, + FOR_OWN_TEAM, + NEVER +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCParrotType.java b/src/main/java/com/laytonsmith/abstraction/enums/MCParrotType.java new file mode 100644 index 0000000000..10c2956589 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCParrotType.java @@ -0,0 +1,12 @@ +package com.laytonsmith.abstraction.enums; + +import com.laytonsmith.annotations.MEnum; + +@MEnum("com.commandhelper.ParrotType") +public enum MCParrotType { + RED, + BLUE, + GREEN, + CYAN, + GRAY +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCParticle.java b/src/main/java/com/laytonsmith/abstraction/enums/MCParticle.java new file mode 100644 index 0000000000..1fb580a4c1 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCParticle.java @@ -0,0 +1,251 @@ +package com.laytonsmith.abstraction.enums; + +import com.laytonsmith.PureUtilities.ClassLoading.DynamicEnum; +import com.laytonsmith.annotations.MDynamicEnum; +import com.laytonsmith.core.MSLog; +import com.laytonsmith.core.constructs.Target; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@MDynamicEnum("com.commandhelper.Particle") +public abstract class MCParticle extends DynamicEnum { + + protected static final Map MAP = new HashMap<>(); + + public MCParticle(MCVanillaParticle mcVanillaParticle, Concrete concrete) { + super(mcVanillaParticle, concrete); + } + + @Override + public MCVanillaParticle getAbstracted() { + return super.getAbstracted(); + } + + public static MCParticle valueOf(String test) throws IllegalArgumentException { + MCParticle ret = MAP.get(test); + if(ret == null) { + switch(test) { + case "BARRIER": + case "LIGHT": + MSLog.GetLogger().e(MSLog.Tags.GENERAL, + test + " particle type was changed in 1.18. Converted to BLOCK_MARKER.", Target.UNKNOWN); + return MAP.get("BLOCK_MARKER"); + case "SPELL_MOB_AMBIENT": + MSLog.GetLogger().e(MSLog.Tags.GENERAL, + test + " particle type was removed in 1.20.5. Converted to SPELL_MOB.", Target.UNKNOWN); + return MAP.get("SPELL_MOB"); + + // The following only existed briefly as experimental API + case "DRIPPING_CHERRY_LEAVES": + case "FALLING_CHERRY_LEAVES": + case "LANDING_CHERRY_LEAVES": + MSLog.GetLogger().e(MSLog.Tags.GENERAL, + test + " particle type was changed in 1.20. Converted to CHERRY_LEAVES.", Target.UNKNOWN); + return MAP.get("CHERRY_LEAVES"); + case "GUST_EMITTER": + MSLog.GetLogger().e(MSLog.Tags.GENERAL, + test + " particle type was changed in 1.20.5. Converted to GUST_EMITTER_LARGE.", Target.UNKNOWN); + return MAP.get("GUST_EMITTER_LARGE"); + case "GUST_DUST": + MSLog.GetLogger().e(MSLog.Tags.GENERAL, + test + " particle type was removed in 1.20.5. Converted to SMALL_GUST.", Target.UNKNOWN); + return MAP.get("SMALL_GUST"); + } + throw new IllegalArgumentException("Unknown particle type: " + test); + } + return ret; + } + + /** + * @return Names of available particles + */ + public static Set types() { + if(MAP.isEmpty()) { // docs mode + Set dummy = new HashSet<>(); + for(final MCVanillaParticle t : MCVanillaParticle.values()) { + if(t.existsIn(MCVersion.CURRENT)) { + dummy.add(t.name()); + } + } + return dummy; + } + return MAP.keySet(); + } + + /** + * @return Our own MCParticle list + */ + public static List values() { + if(MAP.isEmpty()) { // docs mode + ArrayList dummy = new ArrayList<>(); + for(final MCParticle.MCVanillaParticle p : MCParticle.MCVanillaParticle.values()) { + if(!p.existsIn(MCVersion.CURRENT)) { + continue; + } + dummy.add(new MCParticle<>(p, null) { + @Override + public String name() { + return p.name(); + } + }); + } + return dummy; + } + return new ArrayList<>(MAP.values()); + } + + public enum MCVanillaParticle { + EXPLOSION_NORMAL, + EXPLOSION_LARGE, + EXPLOSION_HUGE, + FIREWORKS_SPARK, + WATER_BUBBLE, + WATER_SPLASH, + WATER_WAKE, + SUSPENDED, + SUSPENDED_DEPTH, + CRIT, + CRIT_MAGIC, + SMOKE_NORMAL, + SMOKE_LARGE, + SPELL, + SPELL_INSTANT, + SPELL_MOB, + SPELL_MOB_AMBIENT(MCVersion.MC1_0, MCVersion.MC1_20_4), + SPELL_WITCH, + DRIP_WATER, + DRIP_LAVA, + VILLAGER_ANGRY, + VILLAGER_HAPPY, + TOWN_AURA, + NOTE, + PORTAL, + ENCHANTMENT_TABLE, + FLAME, + LAVA, + CLOUD, + REDSTONE, + SNOWBALL, + SNOW_SHOVEL, + SLIME, + HEART, + BARRIER(MCVersion.MC1_8, MCVersion.MC1_17_X), + ITEM_CRACK, + BLOCK_CRACK, + BLOCK_DUST, + WATER_DROP, + MOB_APPEARANCE, + DRAGON_BREATH, + END_ROD, + DAMAGE_INDICATOR, + SWEEP_ATTACK, + FALLING_DUST, + TOTEM, + SPIT, + SQUID_INK, + BUBBLE_POP, + CURRENT_DOWN, + BUBBLE_COLUMN_UP, + NAUTILUS, + DOLPHIN, + SNEEZE(MCVersion.MC1_14), + CAMPFIRE_COSY_SMOKE(MCVersion.MC1_14), + CAMPFIRE_SIGNAL_SMOKE(MCVersion.MC1_14), + COMPOSTER(MCVersion.MC1_14), + FLASH(MCVersion.MC1_14), + FALLING_LAVA(MCVersion.MC1_14), + LANDING_LAVA(MCVersion.MC1_14), + FALLING_WATER(MCVersion.MC1_14), + DRIPPING_HONEY(MCVersion.MC1_15), + FALLING_HONEY(MCVersion.MC1_15), + FALLING_NECTAR(MCVersion.MC1_15), + LANDING_HONEY(MCVersion.MC1_15), + SOUL_FIRE_FLAME(MCVersion.MC1_16), + ASH(MCVersion.MC1_16), + CRIMSON_SPORE(MCVersion.MC1_16), + WARPED_SPORE(MCVersion.MC1_16), + SOUL(MCVersion.MC1_16), + DRIPPING_OBSIDIAN_TEAR(MCVersion.MC1_16), + FALLING_OBSIDIAN_TEAR(MCVersion.MC1_16), + LANDING_OBSIDIAN_TEAR(MCVersion.MC1_16), + REVERSE_PORTAL(MCVersion.MC1_16), + WHITE_ASH(MCVersion.MC1_16), + LIGHT(MCVersion.MC1_17, MCVersion.MC1_17_X), + DUST_COLOR_TRANSITION(MCVersion.MC1_17), + VIBRATION(MCVersion.MC1_17), + FALLING_SPORE_BLOSSOM(MCVersion.MC1_17), + SPORE_BLOSSOM_AIR(MCVersion.MC1_17), + SMALL_FLAME(MCVersion.MC1_17), + SNOWFLAKE(MCVersion.MC1_17), + DRIPPING_DRIPSTONE_LAVA(MCVersion.MC1_17), + FALLING_DRIPSTONE_LAVA(MCVersion.MC1_17), + DRIPPING_DRIPSTONE_WATER(MCVersion.MC1_17), + FALLING_DRIPSTONE_WATER(MCVersion.MC1_17), + GLOW_SQUID_INK(MCVersion.MC1_17), + GLOW(MCVersion.MC1_17), + WAX_ON(MCVersion.MC1_17), + WAX_OFF(MCVersion.MC1_17), + ELECTRIC_SPARK(MCVersion.MC1_17), + SCRAPE(MCVersion.MC1_17), + BLOCK_MARKER(MCVersion.MC1_18), + SHRIEK(MCVersion.MC1_19), + SCULK_CHARGE(MCVersion.MC1_19), + SCULK_CHARGE_POP(MCVersion.MC1_19), + SCULK_SOUL(MCVersion.MC1_19), + SONIC_BOOM(MCVersion.MC1_19), + DRIPPING_CHERRY_LEAVES(MCVersion.MC1_19_4, MCVersion.MC1_19_4), + FALLING_CHERRY_LEAVES(MCVersion.MC1_19_4, MCVersion.MC1_19_4), + LANDING_CHERRY_LEAVES(MCVersion.MC1_19_4, MCVersion.MC1_19_4), + CHERRY_LEAVES(MCVersion.MC1_20), + EGG_CRACK(MCVersion.MC1_20), + DUST_PLUME(MCVersion.MC1_20_4), + WHITE_SMOKE(MCVersion.MC1_20_4), + GUST(MCVersion.MC1_20_4), + GUST_EMITTER(MCVersion.MC1_20_4, MCVersion.MC1_20_4), + GUST_DUST(MCVersion.MC1_20_4, MCVersion.MC1_20_4), + SMALL_GUST(MCVersion.MC1_20_6), + GUST_EMITTER_LARGE(MCVersion.MC1_20_6), + GUST_EMITTER_SMALL(MCVersion.MC1_20_6), + TRIAL_SPAWNER_DETECTION(MCVersion.MC1_20_4), + TRIAL_SPAWNER_DETECTION_OMINOUS(MCVersion.MC1_20_6), + VAULT_CONNECTION(MCVersion.MC1_20_6), + INFESTED(MCVersion.MC1_20_6), + ITEM_COBWEB(MCVersion.MC1_20_6), + DUST_PILLAR(MCVersion.MC1_20_6), + OMINOUS_SPAWNING(MCVersion.MC1_20_6), + RAID_OMEN(MCVersion.MC1_20_6), + TRIAL_OMEN(MCVersion.MC1_20_6), + BLOCK_CRUMBLE(MCVersion.MC1_21_3), + TRAIL(MCVersion.MC1_21_3), + PALE_OAK_LEAVES(MCVersion.MC1_21_4), + FIREFLY(MCVersion.MC1_21_5), + TINTED_LEAVES(MCVersion.MC1_21_5), + COPPER_FIRE_FLAME(MCVersion.MC1_21_9), + UNKNOWN(MCVersion.NEVER); + + private final MCVersion since; + private final MCVersion until; + + MCVanillaParticle() { + this(MCVersion.MC1_0); + } + + MCVanillaParticle(MCVersion since) { + this(since, MCVersion.CURRENT); + } + + MCVanillaParticle(MCVersion since, MCVersion until) { + this.since = since; + this.until = until; + } + + public boolean existsIn(MCVersion version) { + return version.gte(since) && version.lte(until); + } + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCPatternShape.java b/src/main/java/com/laytonsmith/abstraction/enums/MCPatternShape.java new file mode 100644 index 0000000000..f5016614ea --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCPatternShape.java @@ -0,0 +1,128 @@ +package com.laytonsmith.abstraction.enums; + +import com.laytonsmith.PureUtilities.ClassLoading.DynamicEnum; +import com.laytonsmith.annotations.MDynamicEnum; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@MDynamicEnum("com.commandhelper.PatternShape") +public abstract class MCPatternShape extends DynamicEnum { + + protected static final Map MAP = new HashMap<>(); + + public MCPatternShape(MCVanillaPatternShape mcVanillaPatternShape, Concrete concrete) { + super(mcVanillaPatternShape, concrete); + } + + public static MCPatternShape valueOf(String test) throws IllegalArgumentException { + MCPatternShape shape = MAP.get(test); + if(shape == null) { + throw new IllegalArgumentException("Unknown pattern shape: " + test); + } + return shape; + } + + /** + * @return Names of available patterns + */ + public static Set types() { + if(MAP.isEmpty()) { // docs mode + Set dummy = new HashSet<>(); + for(final MCVanillaPatternShape t : MCVanillaPatternShape.values()) { + if(t.existsIn(MCVersion.CURRENT)) { + dummy.add(t.name()); + } + } + return dummy; + } + return MAP.keySet(); + } + + /** + * @return Our own MCPatternShape list + */ + public static List values() { + if(MAP.isEmpty()) { // docs mode + ArrayList dummy = new ArrayList<>(); + for(final MCPatternShape.MCVanillaPatternShape p : MCPatternShape.MCVanillaPatternShape.values()) { + if(!p.existsIn(MCVersion.CURRENT)) { + continue; + } + dummy.add(new MCPatternShape<>(p, null) { + @Override + public String name() { + return p.name(); + } + }); + } + return dummy; + } + return new ArrayList<>(MAP.values()); + } + + public enum MCVanillaPatternShape { + BASE, + BORDER, + BRICKS, + CIRCLE_MIDDLE, + CREEPER, + CROSS, + CURLY_BORDER, + DIAGONAL_LEFT, + DIAGONAL_LEFT_MIRROR, + DIAGONAL_RIGHT, + DIAGONAL_RIGHT_MIRROR, + FLOW(MCVersion.MC1_21), + FLOWER, + GLOBE, + GRADIENT, + GRADIENT_UP, + GUSTER(MCVersion.MC1_21), + HALF_HORIZONTAL, + HALF_HORIZONTAL_MIRROR, + HALF_VERTICAL, + HALF_VERTICAL_MIRROR, + MOJANG, + PIGLIN, + RHOMBUS_MIDDLE, + SKULL, + SQUARE_BOTTOM_LEFT, + SQUARE_BOTTOM_RIGHT, + SQUARE_TOP_LEFT, + SQUARE_TOP_RIGHT, + STRAIGHT_CROSS, + STRIPE_BOTTOM, + STRIPE_CENTER, + STRIPE_DOWNLEFT, + STRIPE_DOWNRIGHT, + STRIPE_LEFT, + STRIPE_MIDDLE, + STRIPE_RIGHT, + STRIPE_SMALL, + STRIPE_TOP, + TRIANGLE_BOTTOM, + TRIANGLE_TOP, + TRIANGLES_BOTTOM, + TRIANGLES_TOP, + UNKNOWN(MCVersion.NEVER); + + final MCVersion added; + + MCVanillaPatternShape() { + this.added = MCVersion.MC1_8; + } + + MCVanillaPatternShape(MCVersion version) { + this.added = version; + } + + public boolean existsIn(MCVersion version) { + return version.gte(added); + } + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCPigType.java b/src/main/java/com/laytonsmith/abstraction/enums/MCPigType.java index 1be2ecfcc3..df8a3e6adf 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCPigType.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCPigType.java @@ -2,11 +2,7 @@ import com.laytonsmith.annotations.MEnum; -/** - * - * @author jb_aero - */ -@MEnum("PigType") +@MEnum("com.commandhelper.PigType") public enum MCPigType { SADDLED -} \ No newline at end of file +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCPlayerStatistic.java b/src/main/java/com/laytonsmith/abstraction/enums/MCPlayerStatistic.java new file mode 100644 index 0000000000..50322b1a35 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCPlayerStatistic.java @@ -0,0 +1,124 @@ +package com.laytonsmith.abstraction.enums; + +import com.laytonsmith.annotations.MEnum; +import com.laytonsmith.core.Static; + +@MEnum("com.commandhelper.PlayerStatistic") +public enum MCPlayerStatistic { + DAMAGE_DEALT, + DAMAGE_TAKEN, + DEATHS, + MOB_KILLS, + PLAYER_KILLS, + FISH_CAUGHT, + ANIMALS_BRED, + LEAVE_GAME, + JUMP, + DROP_COUNT, + DROP(Type.ITEM), + PICKUP(Type.ITEM), + PLAY_ONE_MINUTE, + TOTAL_WORLD_TIME, + WALK_ONE_CM, + WALK_ON_WATER_ONE_CM, + FALL_ONE_CM, + SNEAK_TIME, + CLIMB_ONE_CM, + FLY_ONE_CM, + WALK_UNDER_WATER_ONE_CM, + MINECART_ONE_CM, + BOAT_ONE_CM, + PIG_ONE_CM, + HORSE_ONE_CM, + SPRINT_ONE_CM, + CROUCH_ONE_CM, + AVIATE_ONE_CM, + MINE_BLOCK(Type.BLOCK), + USE_ITEM(Type.ITEM), + BREAK_ITEM(Type.ITEM), + CRAFT_ITEM(Type.ITEM), + KILL_ENTITY(Type.ENTITY), + ENTITY_KILLED_BY(Type.ENTITY), + TIME_SINCE_DEATH, + TALKED_TO_VILLAGER, + TRADED_WITH_VILLAGER, + CAKE_SLICES_EATEN, + CAULDRON_FILLED, + CAULDRON_USED, + ARMOR_CLEANED, + BANNER_CLEANED, + BREWINGSTAND_INTERACTION, + BEACON_INTERACTION, + DROPPER_INSPECTED, + HOPPER_INSPECTED, + DISPENSER_INSPECTED, + NOTEBLOCK_PLAYED, + NOTEBLOCK_TUNED, + FLOWER_POTTED, + TRAPPED_CHEST_TRIGGERED, + ENDERCHEST_OPENED, + ITEM_ENCHANTED, + RECORD_PLAYED, + FURNACE_INTERACTION, + CRAFTING_TABLE_INTERACTION, + CHEST_OPENED, + SLEEP_IN_BED, + SHULKER_BOX_OPENED, + TIME_SINCE_REST, + SWIM_ONE_CM, + DAMAGE_DEALT_ABSORBED, + DAMAGE_DEALT_RESISTED, + DAMAGE_BLOCKED_BY_SHIELD, + DAMAGE_ABSORBED, + DAMAGE_RESISTED, + CLEAN_SHULKER_BOX, + OPEN_BARREL, + INTERACT_WITH_BLAST_FURNACE, + INTERACT_WITH_SMOKER, + INTERACT_WITH_LECTERN, + INTERACT_WITH_CAMPFIRE, + INTERACT_WITH_CARTOGRAPHY_TABLE, + INTERACT_WITH_LOOM, + INTERACT_WITH_STONECUTTER, + BELL_RING, + RAID_TRIGGER, + RAID_WIN, + INTERACT_WITH_ANVIL(Type.NONE, MCVersion.MC1_15), + INTERACT_WITH_GRINDSTONE(Type.NONE, MCVersion.MC1_15), + TARGET_HIT(Type.NONE, MCVersion.MC1_16), + INTERACT_WITH_SMITHING_TABLE(Type.NONE, MCVersion.MC1_16), + STRIDER_ONE_CM(Type.NONE, MCVersion.MC1_16), + HAPPY_GHAST_ONE_CM(Type.NONE, MCVersion.MC1_21_6), + NAUTILUS_ONE_CM(Type.NONE, MCVersion.MC1_21_11); + + private final Type type; + private final MCVersion since; + + MCPlayerStatistic() { + this(Type.NONE); + } + + MCPlayerStatistic(Type type) { + this(type, MCVersion.MC1_0); + } + + MCPlayerStatistic(Type type, MCVersion since) { + this.type = type; + this.since = since; + } + + public Type getType() { + return type; + } + + public enum Type { + BLOCK, + ENTITY, + ITEM, + NONE + } + + public boolean existsInCurrent() { + return Static.getServer().getMinecraftVersion().gte(since); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCPose.java b/src/main/java/com/laytonsmith/abstraction/enums/MCPose.java new file mode 100644 index 0000000000..031db42f0a --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCPose.java @@ -0,0 +1,25 @@ +package com.laytonsmith.abstraction.enums; + +import com.laytonsmith.annotations.MEnum; + +@MEnum("com.commandhelper.Pose") +public enum MCPose { + CROAKING, + DIGGING, + DYING, + EMERGING, + FALL_FLYING, + INHALING, + LONG_JUMPING, + ROARING, + SHOOTING, + SITTING, + SLEEPING, + SLIDING, + SNEAKING, + SNIFFING, + SPIN_ATTACK, + STANDING, + SWIMMING, + USING_TONGUE +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCPotionAction.java b/src/main/java/com/laytonsmith/abstraction/enums/MCPotionAction.java new file mode 100644 index 0000000000..61e43140c7 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCPotionAction.java @@ -0,0 +1,8 @@ +package com.laytonsmith.abstraction.enums; + +public enum MCPotionAction { + ADDED, + CHANGED, + CLEARED, + REMOVED +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCPotionCause.java b/src/main/java/com/laytonsmith/abstraction/enums/MCPotionCause.java new file mode 100644 index 0000000000..cbe8843e15 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCPotionCause.java @@ -0,0 +1,30 @@ +package com.laytonsmith.abstraction.enums; + +public enum MCPotionCause { + AREA_EFFECT_CLOUD, + ARROW, + ATTACK, + AXOLOTL, + BEACON, + COMMAND, + CONDUIT, + CONVERSION, + DEATH, + DOLPHIN, + EXPIRATION, + FOOD, + ILLUSION, + MILK, + NAUTILUS, + PATROL_CAPTAIN, // unused as of 1.21 + PLUGIN, + POTION_DRINK, + POTION_SPLASH, + SPIDER_SPAWN, + TOTEM, + TURTLE_HELMET, + UNKNOWN, + VILLAGER_TRADE, + WARDEN, + WITHER_ROSE +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCPotionEffectType.java b/src/main/java/com/laytonsmith/abstraction/enums/MCPotionEffectType.java new file mode 100644 index 0000000000..b8e95666a9 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCPotionEffectType.java @@ -0,0 +1,140 @@ +package com.laytonsmith.abstraction.enums; + +import com.laytonsmith.PureUtilities.ClassLoading.DynamicEnum; +import com.laytonsmith.annotations.MDynamicEnum; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +@MDynamicEnum("com.commandhelper.PotionEffectType") +public abstract class MCPotionEffectType extends DynamicEnum { + + protected static final Map MAP = new HashMap<>(); + protected static final Map ID_MAP = new HashMap<>(); + + public MCPotionEffectType(MCVanillaPotionEffectType mcVanillaEffect, Concrete concrete) { + super(mcVanillaEffect, concrete); + } + + public static MCPotionEffectType valueOf(String test) throws IllegalArgumentException { + MCPotionEffectType ret = MAP.get(test); + if(ret == null) { + throw new IllegalArgumentException("Unknown potion effect type: " + test); + } + return ret; + } + + public static Set types() { + if(MAP.isEmpty()) { // docs mode + Set dummy = new HashSet<>(); + for(final MCVanillaPotionEffectType s : MCVanillaPotionEffectType.values()) { + if(!s.equals(MCVanillaPotionEffectType.UNKNOWN)) { + dummy.add(s.name()); + } + } + return dummy; + } + return new TreeSet<>(MAP.keySet()); + } + + public static List values() { + if(MAP.isEmpty()) { // docs mode + ArrayList dummy = new ArrayList<>(); + for(final MCVanillaPotionEffectType s : MCVanillaPotionEffectType.values()) { + if(s.equals(MCVanillaPotionEffectType.UNKNOWN)) { + continue; + } + dummy.add(new MCPotionEffectType<>(s, null) { + @Override + public String name() { + return s.name(); + } + }); + } + return dummy; + } + return new ArrayList<>(MAP.values()); + } + + public static MCPotionEffectType getById(int id) { + MCPotionEffectType ret = ID_MAP.get(id); + if(ret == null) { + throw new IllegalArgumentException("Unknown potion effect id: " + id); + } + return ret; + } + + public int getId() { + return getAbstracted().getId(); + } + + public enum MCVanillaPotionEffectType { + UNKNOWN(0, MCVersion.NEVER), + SPEED(1), + SLOWNESS(2), + HASTE(3), + MINING_FATIGUE(4), + STRENGTH(5), + INSTANT_HEALTH(6), + INSTANT_DAMAGE(7), + JUMP_BOOST(8), + NAUSEA(9), + REGENERATION(10), + RESISTANCE(11), + FIRE_RESISTANCE(12), + WATER_BREATHING(13), + INVISIBILITY(14), + BLINDNESS(15), + NIGHT_VISION(16), + HUNGER(17), + WEAKNESS(18), + POISON(19), + WITHER(20), + HEALTH_BOOST(21), + ABSORPTION(22), + SATURATION(23), + GLOWING(24), + LEVITATION(25), + LUCK(26), + BAD_LUCK(27), + SLOW_FALLING(28), + CONDUIT_POWER(29), + DOLPHINS_GRACE(30, MCVersion.MC1_14), + BAD_OMEN(31, MCVersion.MC1_14), + HERO_OF_THE_VILLAGE(32, MCVersion.MC1_14), + DARKNESS(33, MCVersion.MC1_19), + TRIAL_OMEN(34, MCVersion.MC1_20_6), + RAID_OMEN(35, MCVersion.MC1_20_6), + WIND_CHARGED(36, MCVersion.MC1_20_6), + WEAVING(37, MCVersion.MC1_20_6), + OOZING(38, MCVersion.MC1_20_6), + INFESTED(39, MCVersion.MC1_20_6), + BREATH_OF_THE_NAUTILUS(40, MCVersion.MC1_21_11); + + private final int id; + private final MCVersion since; + + MCVanillaPotionEffectType(int id) { + this.id = id; + this.since = MCVersion.MC1_0; + } + + MCVanillaPotionEffectType(int id, MCVersion version) { + this.id = id; + this.since = version; + } + + public int getId() { + return this.id; + } + + public boolean existsIn(MCVersion version) { + return version.gte(since); + } + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCPotionType.java b/src/main/java/com/laytonsmith/abstraction/enums/MCPotionType.java new file mode 100644 index 0000000000..a5147a5955 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCPotionType.java @@ -0,0 +1,150 @@ +package com.laytonsmith.abstraction.enums; + +import com.laytonsmith.PureUtilities.ClassLoading.DynamicEnum; +import com.laytonsmith.annotations.MDynamicEnum; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +@MDynamicEnum("com.commandhelper.PotionType") +public abstract class MCPotionType extends DynamicEnum { + + protected static final Map MAP = new HashMap<>(); + + public MCPotionType(MCVanillaPotionType mcVanillaType, Concrete concrete) { + super(mcVanillaType, concrete); + } + + public static MCPotionType valueOf(String test) throws IllegalArgumentException { + MCPotionType ret = MAP.get(test); + if(ret == null) { + MCVanillaPotionType oldType = MCVanillaPotionType.valueOf(test); + if(oldType.rename != null) { + return MAP.get(oldType.rename); + } else if(oldType == MCVanillaPotionType.UNCRAFTABLE) { + return null; + } + throw new IllegalArgumentException("Unknown potion type: " + test); + } + return ret; + } + + public static Set types() { + if(MAP.isEmpty()) { // docs mode + Set dummy = new HashSet<>(); + for(final MCVanillaPotionType s : MCVanillaPotionType.values()) { + if(!s.equals(MCVanillaPotionType.UNCRAFTABLE) && s.existsIn(MCVersion.CURRENT)) { + dummy.add(s.name()); + } + } + return dummy; + } + return new TreeSet<>(MAP.keySet()); + } + + public static List values() { + if(MAP.isEmpty()) { // docs mode + ArrayList dummy = new ArrayList<>(); + for(final MCVanillaPotionType s : MCVanillaPotionType.values()) { + if(s.equals(MCVanillaPotionType.UNCRAFTABLE) || !s.existsIn(MCVersion.CURRENT)) { + continue; + } + dummy.add(new MCPotionType<>(s, null) { + @Override + public String name() { + return s.name(); + } + }); + } + return dummy; + } + return new ArrayList<>(MAP.values()); + } + + public enum MCVanillaPotionType { + AWKWARD, + FIRE_RESISTANCE, + LONG_FIRE_RESISTANCE(MCVersion.MC1_20_2), + INFESTED(MCVersion.MC1_20_6), + INSTANT_DAMAGE(MCVersion.MC1_0, MCVersion.MC1_20_4, "HARMING"), + HARMING(MCVersion.MC1_20_6), + STRONG_HARMING(MCVersion.MC1_20_2), + INSTANT_HEAL(MCVersion.MC1_0, MCVersion.MC1_20_4, "HEALING"), + HEALING(MCVersion.MC1_20_6), + STRONG_HEALING(MCVersion.MC1_20_2), + INVISIBILITY, + LONG_INVISIBILITY(MCVersion.MC1_20_2), + JUMP(MCVersion.MC1_0, MCVersion.MC1_20_4, "LEAPING"), + LEAPING(MCVersion.MC1_20_6), + LONG_LEAPING(MCVersion.MC1_20_2), + STRONG_LEAPING(MCVersion.MC1_20_2), + LUCK, + MUNDANE, + NIGHT_VISION, + LONG_NIGHT_VISION(MCVersion.MC1_20_2), + OOZING(MCVersion.MC1_20_6), + POISON, + LONG_POISON(MCVersion.MC1_20_2), + STRONG_POISON(MCVersion.MC1_20_2), + REGEN(MCVersion.MC1_0, MCVersion.MC1_20_4, "REGENERATION"), + REGENERATION(MCVersion.MC1_20_6), + LONG_REGENERATION(MCVersion.MC1_20_2), + STRONG_REGENERATION(MCVersion.MC1_20_2), + SLOWNESS, + LONG_SLOWNESS(MCVersion.MC1_20_2), + STRONG_SLOWNESS(MCVersion.MC1_20_2), + SLOW_FALLING, + LONG_SLOW_FALLING(MCVersion.MC1_20_2), + SPEED(MCVersion.MC1_0, MCVersion.MC1_20_4, "SWIFTNESS"), + SWIFTNESS(MCVersion.MC1_20_6), + LONG_SWIFTNESS(MCVersion.MC1_20_2), + STRONG_SWIFTNESS(MCVersion.MC1_20_2), + STRENGTH, + LONG_STRENGTH(MCVersion.MC1_20_2), + STRONG_STRENGTH(MCVersion.MC1_20_2), + THICK, + TURTLE_MASTER, + LONG_TURTLE_MASTER(MCVersion.MC1_20_2), + STRONG_TURTLE_MASTER(MCVersion.MC1_20_2), + UNCRAFTABLE(MCVersion.MC1_0, MCVersion.MC1_20_4), + WATER, + WATER_BREATHING, + LONG_WATER_BREATHING(MCVersion.MC1_20_2), + WEAKNESS, + LONG_WEAKNESS(MCVersion.MC1_20_2), + WEAVING(MCVersion.MC1_20_6), + WIND_CHARGED(MCVersion.MC1_20_6), + UNKNOWN(MCVersion.NEVER); + + private final MCVersion since; + private final MCVersion until; + private final String rename; + + MCVanillaPotionType() { + this(MCVersion.MC1_0); + } + + MCVanillaPotionType(MCVersion since) { + this(since, MCVersion.FUTURE); + } + + MCVanillaPotionType(MCVersion since, MCVersion until) { + this(since, until, null); + } + + MCVanillaPotionType(MCVersion since, MCVersion until, String rename) { + this.since = since; + this.until = until; + this.rename = rename; + } + + public boolean existsIn(MCVersion version) { + return version.gte(since) && version.lte(until); + } + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCProfession.java b/src/main/java/com/laytonsmith/abstraction/enums/MCProfession.java index a5fcba6ec0..4c65e28e2f 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCProfession.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCProfession.java @@ -1,16 +1,103 @@ package com.laytonsmith.abstraction.enums; -import com.laytonsmith.annotations.MEnum; - -/** - * - * @author jb_aero - */ -@MEnum("Profession") -public enum MCProfession { - BLACKSMITH, - BUTCHER, - FARMER, - LIBRARIAN, - PRIEST -} \ No newline at end of file +import com.laytonsmith.PureUtilities.ClassLoading.DynamicEnum; +import com.laytonsmith.annotations.MDynamicEnum; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +@MDynamicEnum("com.commandhelper.Profession") +public abstract class MCProfession extends DynamicEnum { + + protected static final Map MAP = new HashMap<>(); + + public MCProfession(MCVanillaProfession mcVanillaProfession, Concrete concrete) { + super(mcVanillaProfession, concrete); + } + + public static MCProfession valueOf(String test) throws IllegalArgumentException { + MCProfession ret = MAP.get(test); + if(ret == null) { + throw new IllegalArgumentException("Unknown villager profession type: " + test); + } + return ret; + } + + public static Set types() { + if(MAP.isEmpty()) { // docs mode + Set dummy = new HashSet<>(); + for(final MCVanillaProfession s : MCVanillaProfession.values()) { + if(s.existsIn(MCVersion.CURRENT)) { + dummy.add(s.name()); + } + } + return dummy; + } + return new TreeSet<>(MAP.keySet()); + } + + public static List values() { + if(MAP.isEmpty()) { // docs mode + ArrayList dummy = new ArrayList<>(); + for(final MCVanillaProfession s : MCVanillaProfession.values()) { + if(!s.existsIn(MCVersion.CURRENT)) { + continue; + } + dummy.add(new MCProfession(s, null) { + @Override + public String name() { + return s.name(); + } + }); + } + return dummy; + } + return new ArrayList<>(MAP.values()); + } + + public enum MCVanillaProfession { + BUTCHER, + FARMER, + LIBRARIAN, + NITWIT, + ARMORER(MCVersion.MC1_14), + CARTOGRAPHER(MCVersion.MC1_14), + CLERIC(MCVersion.MC1_14), + FISHERMAN(MCVersion.MC1_14), + FLETCHER(MCVersion.MC1_14), + LEATHERWORKER(MCVersion.MC1_14), + MASON(MCVersion.MC1_14), + NONE(MCVersion.MC1_14), + SHEPHERD(MCVersion.MC1_14), + TOOLSMITH(MCVersion.MC1_14), + WEAPONSMITH(MCVersion.MC1_14), + UNKNOWN(MCVersion.NEVER); + + private final MCVersion from; + private final MCVersion to; + + MCVanillaProfession() { + this.from = MCVersion.MC1_12_X; + this.to = MCVersion.FUTURE; + } + + MCVanillaProfession(MCVersion version) { + this.from = version; + this.to = MCVersion.FUTURE; + } + + MCVanillaProfession(MCVersion fromVersion, MCVersion toVersion) { + this.from = fromVersion; + this.to = toVersion; + } + + public boolean existsIn(MCVersion version) { + return version.gte(from) && version.lte(to); + } + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCProjectileType.java b/src/main/java/com/laytonsmith/abstraction/enums/MCProjectileType.java deleted file mode 100644 index 58f17b19a6..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCProjectileType.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.laytonsmith.abstraction.enums; - -import com.laytonsmith.annotations.MEnum; - -/** - * - * @author jb_aero - */ -@MEnum("ProjectileType") -public enum MCProjectileType { - ARROW, - EGG, - ENDER_PEARL, - FIREBALL, - //FISHING_HOOK, - SMALL_FIREBALL, - SNOWBALL, - SPLASH_POTION, - //THROWN_EXP_BOTTLE, - WITHER_SKULL -} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCRabbitType.java b/src/main/java/com/laytonsmith/abstraction/enums/MCRabbitType.java new file mode 100644 index 0000000000..c80c79173a --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCRabbitType.java @@ -0,0 +1,14 @@ +package com.laytonsmith.abstraction.enums; + +import com.laytonsmith.annotations.MEnum; + +@MEnum("com.commandhelper.RabbitType") +public enum MCRabbitType { + BROWN, + WHITE, + BLACK, + BLACK_AND_WHITE, + GOLD, + SALT_AND_PEPPER, + THE_KILLER_BUNNY +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCRecipeType.java b/src/main/java/com/laytonsmith/abstraction/enums/MCRecipeType.java index 6fa2e675dd..e4fb0bddef 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCRecipeType.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCRecipeType.java @@ -1,7 +1,17 @@ package com.laytonsmith.abstraction.enums; +import com.laytonsmith.annotations.MEnum; + +@MEnum("com.commandhelper.RecipeType") public enum MCRecipeType { + BLASTING, + CAMPFIRE, + COMPLEX, FURNACE, + MERCHANT, SHAPED, - SHAPELESS + SHAPELESS, + SMITHING, + SMOKING, + STONECUTTING } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCRegainReason.java b/src/main/java/com/laytonsmith/abstraction/enums/MCRegainReason.java new file mode 100644 index 0000000000..e4dca67af5 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCRegainReason.java @@ -0,0 +1,13 @@ +package com.laytonsmith.abstraction.enums; + +public enum MCRegainReason { + REGEN, + SATIATED, + EATING, + ENDER_CRYSTAL, + MAGIC, + MAGIC_REGEN, + WITHER_SPAWN, + WITHER, + CUSTOM +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCRemoveCause.java b/src/main/java/com/laytonsmith/abstraction/enums/MCRemoveCause.java index 1cd6fe4ac3..35912fa972 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCRemoveCause.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCRemoveCause.java @@ -2,11 +2,7 @@ import com.laytonsmith.annotations.MEnum; -/** - * - * @author Hekta - */ -@MEnum("RemoveCause") +@MEnum("com.commandhelper.RemoveCause") public enum MCRemoveCause { DEFAULT, ENTITY, diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCResourcePackStatus.java b/src/main/java/com/laytonsmith/abstraction/enums/MCResourcePackStatus.java new file mode 100644 index 0000000000..17ba5ec03e --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCResourcePackStatus.java @@ -0,0 +1,15 @@ +package com.laytonsmith.abstraction.enums; + +import com.laytonsmith.annotations.MEnum; + +@MEnum("com.commandhelper.ResourcePackStatus") +public enum MCResourcePackStatus { + ACCEPTED, + DECLINED, + DISCARDED, + DOWNLOADED, + FAILED_DOWNLOAD, + FAILED_RELOAD, + INVALID_URL, + SUCCESSFULLY_LOADED +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCResult.java b/src/main/java/com/laytonsmith/abstraction/enums/MCResult.java index 49ab986efa..97f485351a 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCResult.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCResult.java @@ -1,11 +1,7 @@ package com.laytonsmith.abstraction.enums; -/** - * - * @author MariuszT - */ public enum MCResult { ALLOW, DENY, - DEFAULT; + DEFAULT } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCRotation.java b/src/main/java/com/laytonsmith/abstraction/enums/MCRotation.java index 3b6957967b..b7aec154f8 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCRotation.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCRotation.java @@ -1,16 +1,15 @@ - package com.laytonsmith.abstraction.enums; import com.laytonsmith.annotations.MEnum; -/** - * - * @author Jason Unger - */ -@MEnum("Rotation") +@MEnum("com.commandhelper.Rotation") public enum MCRotation { CLOCKWISE, + CLOCKWISE_135, + CLOCKWISE_45, COUNTER_CLOCKWISE, + COUNTER_CLOCKWISE_45, FLIPPED, + FLIPPED_45, NONE } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCSkeletonType.java b/src/main/java/com/laytonsmith/abstraction/enums/MCSkeletonType.java deleted file mode 100644 index c7f1c72d08..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCSkeletonType.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.laytonsmith.abstraction.enums; - -import com.laytonsmith.annotations.MEnum; - -/** - * - * @author jb_aero - */ -@MEnum("SkeletonType") -public enum MCSkeletonType { - NORMAL, - WITHER -} \ No newline at end of file diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCSlotType.java b/src/main/java/com/laytonsmith/abstraction/enums/MCSlotType.java index 77d47f5349..2ce7368807 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCSlotType.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCSlotType.java @@ -2,11 +2,7 @@ import com.laytonsmith.annotations.MEnum; -/** - * - * @author jb_aero - */ -@MEnum("SlotType") +@MEnum("com.commandhelper.SlotType") public enum MCSlotType { /** * An armour slot in the player's inventory. @@ -17,7 +13,8 @@ public enum MCSlotType { */ CONTAINER, /** - * A slot in the crafting matrix, or the input slot in a furnace inventory, the potion slot in the brewing stand, or the enchanting slot. + * A slot in the crafting matrix, or the input slot in a furnace inventory, the potion slot in the brewing stand, or + * the enchanting slot. */ CRAFTING, /** diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCSound.java b/src/main/java/com/laytonsmith/abstraction/enums/MCSound.java index 01acba963b..69e6008140 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCSound.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCSound.java @@ -1,206 +1,1984 @@ package com.laytonsmith.abstraction.enums; -import com.laytonsmith.annotations.MEnum; - -@MEnum("Sound") -public enum MCSound { - AMBIENCE_CAVE, - AMBIENCE_RAIN, - AMBIENCE_THUNDER, - ANVIL_BREAK, - ANVIL_LAND, - ANVIL_USE, - ARROW_HIT, - BURP, - CHEST_CLOSE, - CHEST_OPEN, - CLICK, - DOOR_CLOSE, - DOOR_OPEN, - DRINK, - EAT, - EXPLODE, - FALL_BIG, - FALL_SMALL, - FIRE, - FIRE_IGNITE, - FIZZ, - FUSE, - GLASS, - HURT_FLESH, - ITEM_BREAK, - ITEM_PICKUP, - LAVA, - LAVA_POP, - LEVEL_UP, - MINECART_BASE, - MINECART_INSIDE, - NOTE_BASS, - NOTE_PIANO, - NOTE_BASS_DRUM, - NOTE_STICKS, - NOTE_BASS_GUITAR, - NOTE_SNARE_DRUM, - NOTE_PLING, - ORB_PICKUP, - PISTON_EXTEND, - PISTON_RETRACT, - PORTAL, - PORTAL_TRAVEL, - PORTAL_TRIGGER, - SHOOT_ARROW, - SPLASH, - SPLASH2, - STEP_GRASS, - STEP_GRAVEL, - STEP_LADDER, - STEP_SAND, - STEP_SNOW, - STEP_STONE, - STEP_WOOD, - STEP_WOOL, - SWIM, - WATER, - WOOD_CLICK, - // Mob sounds - BAT_DEATH, - BAT_HURT, - BAT_IDLE, - BAT_LOOP, - BAT_TAKEOFF, - BLAZE_BREATH, - BLAZE_DEATH, - BLAZE_HIT, - CAT_HISS, - CAT_HIT, - CAT_MEOW, - CAT_PURR, - CAT_PURREOW, - CHICKEN_IDLE, - CHICKEN_HURT, - CHICKEN_EGG_POP, - CHICKEN_WALK, - COW_IDLE, - COW_HURT, - COW_WALK, - CREEPER_HISS, - CREEPER_DEATH, - ENDERDRAGON_DEATH, - ENDERDRAGON_GROWL, - ENDERDRAGON_HIT, - ENDERDRAGON_WINGS, - ENDERMAN_DEATH, - ENDERMAN_HIT, - ENDERMAN_IDLE, - ENDERMAN_TELEPORT, - ENDERMAN_SCREAM, - ENDERMAN_STARE, - GHAST_SCREAM, - GHAST_SCREAM2, - GHAST_CHARGE, - GHAST_DEATH, - GHAST_FIREBALL, - GHAST_MOAN, - HORSE_DEATH, - HORSE_SKELETON_HIT, - IRONGOLEM_DEATH, - IRONGOLEM_HIT, - IRONGOLEM_THROW, - IRONGOLEM_WALK, - MAGMACUBE_WALK, - MAGMACUBE_WALK2, - MAGMACUBE_JUMP, - PIG_IDLE, - PIG_DEATH, - PIG_WALK, - SHEEP_IDLE, - SHEEP_SHEAR, - SHEEP_WALK, - SILVERFISH_HIT, - SILVERFISH_KILL, - SILVERFISH_IDLE, - SILVERFISH_WALK, - SKELETON_IDLE, - SKELETON_DEATH, - SKELETON_HURT, - SKELETON_WALK, - SLIME_ATTACK, - SLIME_WALK, - SLIME_WALK2, - SPIDER_IDLE, - SPIDER_DEATH, - SPIDER_WALK, - WITHER_DEATH, - WITHER_HURT, - WITHER_IDLE, - WITHER_SHOOT, - WITHER_SPAWN, - WOLF_BARK, - WOLF_DEATH, - WOLF_GROWL, - WOLF_HOWL, - WOLF_HURT, - WOLF_PANT, - WOLF_SHAKE, - WOLF_WALK, - WOLF_WHINE, - ZOMBIE_METAL, - ZOMBIE_WALK, - ZOMBIE_WOOD, - ZOMBIE_WOODBREAK, - ZOMBIE_IDLE, - ZOMBIE_DEATH, - ZOMBIE_HURT, - ZOMBIE_INFECT, - ZOMBIE_UNFECT, - ZOMBIE_REMEDY, - ZOMBIE_PIG_IDLE, - ZOMBIE_PIG_ANGRY, - ZOMBIE_PIG_DEATH, - ZOMBIE_PIG_HURT, - // Dig Sounds - DIG_WOOL, - DIG_GRASS, - DIG_GRAVEL, - DIG_SAND, - DIG_SNOW, - DIG_STONE, - DIG_WOOD, - // Fireworks - FIREWORK_BLAST, - FIREWORK_BLAST2, - FIREWORK_LARGE_BLAST, - FIREWORK_LARGE_BLAST2, - FIREWORK_TWINKLE, - FIREWORK_TWINKLE2, - FIREWORK_LAUNCH, - SUCCESSFUL_HIT, - // Horses - HORSE_ANGRY, - HORSE_ARMOR, - HORSE_BREATHE, - HORSE_GALLOP, - HORSE_HIT, - HORSE_IDLE, - HORSE_JUMP, - HORSE_LAND, - HORSE_SADDLE, - HORSE_SOFT, - HORSE_WOOD, - DONKEY_ANGRY, - DONKEY_DEATH, - DONKEY_HIT, - DONKEY_IDLE, - HORSE_SKELETON_DEATH, - HORSE_SKELETON_IDLE, - HORSE_ZOMBIE_DEATH, - HORSE_ZOMBIE_HIT, - HORSE_ZOMBIE_IDLE, - // Villager - VILLAGER_DEATH, - VILLAGER_HAGGLE, - VILLAGER_HIT, - VILLAGER_IDLE, - VILLAGER_NO, - VILLAGER_YES +import com.laytonsmith.PureUtilities.ClassLoading.DynamicEnum; +import com.laytonsmith.annotations.MDynamicEnum; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +@MDynamicEnum("com.commandhelper.Sound") +public abstract class MCSound extends DynamicEnum { + + protected static final Map MAP = new HashMap<>(); + + public MCSound(MCVanillaSound mcVanillaSound, Concrete concrete) { + super(mcVanillaSound, concrete); + } + + public static MCSound valueOf(String test) throws IllegalArgumentException { + MCSound ret = MAP.get(test); + if(ret == null) { + throw new IllegalArgumentException("Unknown sound: " + test); + } + return ret; + } + + public static Set types() { + if(MAP.isEmpty()) { // docs mode + Set dummy = new HashSet<>(); + for(final MCVanillaSound s : MCVanillaSound.values()) { + if(s.existsIn(MCVersion.CURRENT)) { + dummy.add(s.name()); + } + } + return dummy; + } + return new TreeSet<>(MAP.keySet()); + } + + public static List values() { + if(MAP.isEmpty()) { // docs mode + ArrayList dummy = new ArrayList<>(); + for(final MCVanillaSound s : MCVanillaSound.values()) { + if(!s.existsIn(MCVersion.CURRENT)) { + continue; + } + dummy.add(new MCSound(s, null) { + @Override + public String name() { + return s.name(); + } + }); + } + return dummy; + } + return new ArrayList<>(MAP.values()); + } + + public enum MCVanillaSound { + // After 1.9 + AMBIENT_CAVE, + BLOCK_ANVIL_BREAK, + BLOCK_ANVIL_DESTROY, + BLOCK_ANVIL_FALL, + BLOCK_ANVIL_HIT, + BLOCK_ANVIL_LAND, + BLOCK_ANVIL_PLACE, + BLOCK_ANVIL_STEP, + BLOCK_ANVIL_USE, + BLOCK_BREWING_STAND_BREW, + BLOCK_CHEST_CLOSE, + BLOCK_CHEST_LOCKED, + BLOCK_CHEST_OPEN, + BLOCK_CHORUS_FLOWER_DEATH, + BLOCK_CHORUS_FLOWER_GROW, + BLOCK_COMPARATOR_CLICK, + BLOCK_DISPENSER_DISPENSE, + BLOCK_DISPENSER_FAIL, + BLOCK_DISPENSER_LAUNCH, + BLOCK_ENCHANTMENT_TABLE_USE, + BLOCK_END_GATEWAY_SPAWN, + BLOCK_END_PORTAL_FRAME_FILL, + BLOCK_END_PORTAL_SPAWN, + BLOCK_FENCE_GATE_CLOSE, + BLOCK_FENCE_GATE_OPEN, + BLOCK_FIRE_AMBIENT, + BLOCK_FIRE_EXTINGUISH, + BLOCK_FURNACE_FIRE_CRACKLE, + BLOCK_GLASS_BREAK, + BLOCK_GLASS_FALL, + BLOCK_GLASS_HIT, + BLOCK_GLASS_PLACE, + BLOCK_GLASS_STEP, + BLOCK_GRASS_BREAK, + BLOCK_GRASS_FALL, + BLOCK_GRASS_HIT, + BLOCK_GRASS_PLACE, + BLOCK_GRASS_STEP, + BLOCK_GRAVEL_BREAK, + BLOCK_GRAVEL_FALL, + BLOCK_GRAVEL_HIT, + BLOCK_GRAVEL_PLACE, + BLOCK_GRAVEL_STEP, + BLOCK_IRON_DOOR_CLOSE, + BLOCK_IRON_DOOR_OPEN, + BLOCK_IRON_TRAPDOOR_CLOSE, + BLOCK_IRON_TRAPDOOR_OPEN, + BLOCK_LADDER_BREAK, + BLOCK_LADDER_FALL, + BLOCK_LADDER_HIT, + BLOCK_LADDER_PLACE, + BLOCK_LADDER_STEP, + BLOCK_LAVA_AMBIENT, + BLOCK_LAVA_EXTINGUISH, + BLOCK_LAVA_POP, + BLOCK_LEVER_CLICK, + BLOCK_METAL_BREAK, + BLOCK_METAL_FALL, + BLOCK_METAL_HIT, + BLOCK_METAL_PLACE, + BLOCK_METAL_STEP, + BLOCK_PISTON_CONTRACT, + BLOCK_PISTON_EXTEND, + BLOCK_PORTAL_AMBIENT, + BLOCK_PORTAL_TRAVEL, + BLOCK_PORTAL_TRIGGER, + BLOCK_REDSTONE_TORCH_BURNOUT, + BLOCK_SAND_BREAK, + BLOCK_SAND_FALL, + BLOCK_SAND_HIT, + BLOCK_SAND_PLACE, + BLOCK_SAND_STEP, + BLOCK_SHULKER_BOX_CLOSE, + BLOCK_SHULKER_BOX_OPEN, + BLOCK_SNOW_BREAK, + BLOCK_SNOW_FALL, + BLOCK_SNOW_HIT, + BLOCK_SNOW_PLACE, + BLOCK_SNOW_STEP, + BLOCK_STONE_BREAK, + BLOCK_STONE_BUTTON_CLICK_OFF, + BLOCK_STONE_BUTTON_CLICK_ON, + BLOCK_STONE_FALL, + BLOCK_STONE_HIT, + BLOCK_STONE_PLACE, + BLOCK_STONE_STEP, + BLOCK_TRIPWIRE_ATTACH, + BLOCK_TRIPWIRE_CLICK_OFF, + BLOCK_TRIPWIRE_CLICK_ON, + BLOCK_TRIPWIRE_DETACH, + BLOCK_WATER_AMBIENT, + BLOCK_WOODEN_DOOR_CLOSE, + BLOCK_WOODEN_DOOR_OPEN, + BLOCK_WOODEN_TRAPDOOR_CLOSE, + BLOCK_WOODEN_TRAPDOOR_OPEN, + BLOCK_WOOD_BREAK, + BLOCK_WOOD_FALL, + BLOCK_WOOD_HIT, + BLOCK_WOOD_PLACE, + BLOCK_WOOD_STEP, + ENCHANT_THORNS_HIT, + ENTITY_ARROW_HIT, + ENTITY_ARROW_HIT_PLAYER, + ENTITY_ARROW_SHOOT, + ENTITY_BAT_AMBIENT, + ENTITY_BAT_DEATH, + ENTITY_BAT_HURT, + ENTITY_BAT_LOOP, + ENTITY_BAT_TAKEOFF, + ENTITY_BLAZE_AMBIENT, + ENTITY_BLAZE_BURN, + ENTITY_BLAZE_DEATH, + ENTITY_BLAZE_HURT, + ENTITY_BLAZE_SHOOT, + ENTITY_BOAT_PADDLE_LAND, + ENTITY_BOAT_PADDLE_WATER, + ENTITY_CAT_AMBIENT, + ENTITY_CAT_DEATH, + ENTITY_CAT_HISS, + ENTITY_CAT_HURT, + ENTITY_CAT_PURR, + ENTITY_CAT_PURREOW, + ENTITY_CHICKEN_AMBIENT, + ENTITY_CHICKEN_DEATH, + ENTITY_CHICKEN_EGG, + ENTITY_CHICKEN_HURT, + ENTITY_CHICKEN_STEP, + ENTITY_COW_AMBIENT, + ENTITY_COW_DEATH, + ENTITY_COW_HURT, + ENTITY_COW_MILK, + ENTITY_COW_STEP, + ENTITY_CREEPER_DEATH, + ENTITY_CREEPER_HURT, + ENTITY_CREEPER_PRIMED, + ENTITY_DONKEY_AMBIENT, + ENTITY_DONKEY_ANGRY, + ENTITY_DONKEY_CHEST, + ENTITY_DONKEY_DEATH, + ENTITY_DONKEY_HURT, + ENTITY_EGG_THROW, + ENTITY_ELDER_GUARDIAN_AMBIENT, + ENTITY_ELDER_GUARDIAN_AMBIENT_LAND, + ENTITY_ELDER_GUARDIAN_CURSE, + ENTITY_ELDER_GUARDIAN_DEATH, + ENTITY_ELDER_GUARDIAN_DEATH_LAND, + ENTITY_ELDER_GUARDIAN_FLOP, + ENTITY_ELDER_GUARDIAN_HURT, + ENTITY_ELDER_GUARDIAN_HURT_LAND, + ENTITY_ENDERMITE_AMBIENT, + ENTITY_ENDERMITE_DEATH, + ENTITY_ENDERMITE_HURT, + ENTITY_ENDERMITE_STEP, + ENTITY_EXPERIENCE_BOTTLE_THROW, + ENTITY_EXPERIENCE_ORB_PICKUP, + ENTITY_GENERIC_BIG_FALL, + ENTITY_GENERIC_BURN, + ENTITY_GENERIC_DEATH, + ENTITY_GENERIC_DRINK, + ENTITY_GENERIC_EAT, + ENTITY_GENERIC_EXPLODE, + ENTITY_GENERIC_EXTINGUISH_FIRE, + ENTITY_GENERIC_HURT, + ENTITY_GENERIC_SMALL_FALL, + ENTITY_GENERIC_SPLASH, + ENTITY_GENERIC_SWIM, + ENTITY_GHAST_AMBIENT, + ENTITY_GHAST_DEATH, + ENTITY_GHAST_HURT, + ENTITY_GHAST_SCREAM, + ENTITY_GHAST_SHOOT, + ENTITY_GHAST_WARN, + ENTITY_GUARDIAN_AMBIENT, + ENTITY_GUARDIAN_AMBIENT_LAND, + ENTITY_GUARDIAN_ATTACK, + ENTITY_GUARDIAN_DEATH, + ENTITY_GUARDIAN_DEATH_LAND, + ENTITY_GUARDIAN_FLOP, + ENTITY_GUARDIAN_HURT, + ENTITY_GUARDIAN_HURT_LAND, + ENTITY_HORSE_AMBIENT, + ENTITY_HORSE_ANGRY, + ENTITY_HORSE_ARMOR, + ENTITY_HORSE_BREATHE, + ENTITY_HORSE_DEATH, + ENTITY_HORSE_EAT, + ENTITY_HORSE_GALLOP, + ENTITY_HORSE_HURT, + ENTITY_HORSE_JUMP, + ENTITY_HORSE_LAND, + ENTITY_HORSE_SADDLE, + ENTITY_HORSE_STEP, + ENTITY_HORSE_STEP_WOOD, + ENTITY_HOSTILE_BIG_FALL, + ENTITY_HOSTILE_DEATH, + ENTITY_HOSTILE_HURT, + ENTITY_HOSTILE_SMALL_FALL, + ENTITY_HOSTILE_SPLASH, + ENTITY_HOSTILE_SWIM, + ENTITY_HUSK_AMBIENT, + ENTITY_HUSK_DEATH, + ENTITY_HUSK_HURT, + ENTITY_HUSK_STEP, + ENTITY_ITEM_BREAK, + ENTITY_ITEM_PICKUP, + ENTITY_LLAMA_AMBIENT, + ENTITY_LLAMA_ANGRY, + ENTITY_LLAMA_CHEST, + ENTITY_LLAMA_DEATH, + ENTITY_LLAMA_EAT, + ENTITY_LLAMA_HURT, + ENTITY_LLAMA_SPIT, + ENTITY_LLAMA_STEP, + ENTITY_LLAMA_SWAG, + ENTITY_MINECART_INSIDE, + ENTITY_MINECART_RIDING, + ENTITY_MOOSHROOM_SHEAR, + ENTITY_MULE_AMBIENT, + ENTITY_MULE_CHEST, + ENTITY_MULE_DEATH, + ENTITY_MULE_HURT, + ENTITY_PAINTING_BREAK, + ENTITY_PAINTING_PLACE, + ENTITY_PARROT_AMBIENT, + ENTITY_PARROT_DEATH, + ENTITY_PARROT_EAT, + ENTITY_PARROT_FLY, + ENTITY_PARROT_HURT, + ENTITY_PARROT_IMITATE_BLAZE, + ENTITY_PARROT_IMITATE_CREEPER, + ENTITY_PARROT_IMITATE_ELDER_GUARDIAN, + ENTITY_PARROT_IMITATE_ENDERMITE, + ENTITY_PARROT_IMITATE_GHAST, + ENTITY_PARROT_IMITATE_HUSK, + ENTITY_PARROT_IMITATE_SHULKER, + ENTITY_PARROT_IMITATE_SILVERFISH, + ENTITY_PARROT_IMITATE_SKELETON, + ENTITY_PARROT_IMITATE_SLIME, + ENTITY_PARROT_IMITATE_SPIDER, + ENTITY_PARROT_IMITATE_STRAY, + ENTITY_PARROT_IMITATE_VEX, + ENTITY_PARROT_IMITATE_WITCH, + ENTITY_PARROT_IMITATE_WITHER, + ENTITY_PARROT_IMITATE_WITHER_SKELETON, + ENTITY_PARROT_IMITATE_ZOMBIE, + ENTITY_PARROT_IMITATE_ZOMBIE_VILLAGER, + ENTITY_PARROT_STEP, + ENTITY_PIG_AMBIENT, + ENTITY_PIG_DEATH, + ENTITY_PIG_HURT, + ENTITY_PIG_SADDLE, + ENTITY_PIG_STEP, + ENTITY_PLAYER_ATTACK_CRIT, + ENTITY_PLAYER_ATTACK_KNOCKBACK, + ENTITY_PLAYER_ATTACK_NODAMAGE, + ENTITY_PLAYER_ATTACK_STRONG, + ENTITY_PLAYER_ATTACK_SWEEP, + ENTITY_PLAYER_ATTACK_WEAK, + ENTITY_PLAYER_BIG_FALL, + ENTITY_PLAYER_BREATH, + ENTITY_PLAYER_BURP, + ENTITY_PLAYER_DEATH, + ENTITY_PLAYER_HURT, + ENTITY_PLAYER_HURT_DROWN, + ENTITY_PLAYER_HURT_ON_FIRE, + ENTITY_PLAYER_LEVELUP, + ENTITY_PLAYER_SMALL_FALL, + ENTITY_PLAYER_SPLASH, + ENTITY_PLAYER_SWIM, + ENTITY_POLAR_BEAR_AMBIENT, + ENTITY_POLAR_BEAR_DEATH, + ENTITY_POLAR_BEAR_HURT, + ENTITY_POLAR_BEAR_STEP, + ENTITY_POLAR_BEAR_WARNING, + ENTITY_RABBIT_AMBIENT, + ENTITY_RABBIT_ATTACK, + ENTITY_RABBIT_DEATH, + ENTITY_RABBIT_HURT, + ENTITY_RABBIT_JUMP, + ENTITY_SHEEP_AMBIENT, + ENTITY_SHEEP_DEATH, + ENTITY_SHEEP_HURT, + ENTITY_SHEEP_SHEAR, + ENTITY_SHEEP_STEP, + ENTITY_SHULKER_AMBIENT, + ENTITY_SHULKER_BULLET_HIT, + ENTITY_SHULKER_BULLET_HURT, + ENTITY_SHULKER_CLOSE, + ENTITY_SHULKER_DEATH, + ENTITY_SHULKER_HURT, + ENTITY_SHULKER_HURT_CLOSED, + ENTITY_SHULKER_OPEN, + ENTITY_SHULKER_SHOOT, + ENTITY_SHULKER_TELEPORT, + ENTITY_SILVERFISH_AMBIENT, + ENTITY_SILVERFISH_DEATH, + ENTITY_SILVERFISH_HURT, + ENTITY_SILVERFISH_STEP, + ENTITY_SKELETON_AMBIENT, + ENTITY_SKELETON_DEATH, + ENTITY_SKELETON_HORSE_AMBIENT, + ENTITY_SKELETON_HORSE_DEATH, + ENTITY_SKELETON_HORSE_HURT, + ENTITY_SKELETON_HURT, + ENTITY_SKELETON_SHOOT, + ENTITY_SKELETON_STEP, + ENTITY_SLIME_ATTACK, + ENTITY_SLIME_DEATH, + ENTITY_SLIME_HURT, + ENTITY_SLIME_JUMP, + ENTITY_SLIME_SQUISH, + ENTITY_SNOWBALL_THROW, + ENTITY_SPIDER_AMBIENT, + ENTITY_SPIDER_DEATH, + ENTITY_SPIDER_HURT, + ENTITY_SPIDER_STEP, + ENTITY_SPLASH_POTION_BREAK, + ENTITY_SPLASH_POTION_THROW, + ENTITY_SQUID_AMBIENT, + ENTITY_SQUID_DEATH, + ENTITY_SQUID_HURT, + ENTITY_STRAY_AMBIENT, + ENTITY_STRAY_DEATH, + ENTITY_STRAY_HURT, + ENTITY_STRAY_STEP, + ENTITY_TNT_PRIMED, + ENTITY_VEX_AMBIENT, + ENTITY_VEX_CHARGE, + ENTITY_VEX_DEATH, + ENTITY_VEX_HURT, + ENTITY_VILLAGER_AMBIENT, + ENTITY_VILLAGER_DEATH, + ENTITY_VILLAGER_HURT, + ENTITY_VILLAGER_NO, + ENTITY_VILLAGER_YES, + ENTITY_WITCH_AMBIENT, + ENTITY_WITCH_DEATH, + ENTITY_WITCH_DRINK, + ENTITY_WITCH_HURT, + ENTITY_WITCH_THROW, + ENTITY_WITHER_AMBIENT, + ENTITY_WITHER_BREAK_BLOCK, + ENTITY_WITHER_DEATH, + ENTITY_WITHER_HURT, + ENTITY_WITHER_SHOOT, + ENTITY_WITHER_SKELETON_AMBIENT, + ENTITY_WITHER_SKELETON_DEATH, + ENTITY_WITHER_SKELETON_HURT, + ENTITY_WITHER_SKELETON_STEP, + ENTITY_WITHER_SPAWN, + ENTITY_WOLF_AMBIENT, + ENTITY_WOLF_DEATH, + ENTITY_WOLF_GROWL, + ENTITY_WOLF_HOWL(MCVersion.MC1_4, MCVersion.MC1_21_4), + ENTITY_WOLF_HURT, + ENTITY_WOLF_PANT, + ENTITY_WOLF_SHAKE, + ENTITY_WOLF_STEP, + ENTITY_WOLF_WHINE, + ENTITY_ZOMBIE_AMBIENT, + ENTITY_ZOMBIE_ATTACK_IRON_DOOR, + ENTITY_ZOMBIE_DEATH, + ENTITY_ZOMBIE_HORSE_AMBIENT, + ENTITY_ZOMBIE_HORSE_DEATH, + ENTITY_ZOMBIE_HORSE_HURT, + ENTITY_ZOMBIE_HURT, + ENTITY_ZOMBIE_INFECT, + ENTITY_ZOMBIE_STEP, + ENTITY_ZOMBIE_VILLAGER_AMBIENT, + ENTITY_ZOMBIE_VILLAGER_CONVERTED, + ENTITY_ZOMBIE_VILLAGER_CURE, + ENTITY_ZOMBIE_VILLAGER_DEATH, + ENTITY_ZOMBIE_VILLAGER_HURT, + ENTITY_ZOMBIE_VILLAGER_STEP, + ITEM_ARMOR_EQUIP_CHAIN, + ITEM_ARMOR_EQUIP_DIAMOND, + ITEM_ARMOR_EQUIP_ELYTRA, + ITEM_ARMOR_EQUIP_GENERIC, + ITEM_ARMOR_EQUIP_GOLD, + ITEM_ARMOR_EQUIP_IRON, + ITEM_ARMOR_EQUIP_LEATHER, + ITEM_BOTTLE_EMPTY, + ITEM_BOTTLE_FILL, + ITEM_BOTTLE_FILL_DRAGONBREATH, + ITEM_BUCKET_EMPTY, + ITEM_BUCKET_EMPTY_LAVA, + ITEM_BUCKET_FILL, + ITEM_BUCKET_FILL_LAVA, + ITEM_CHORUS_FRUIT_TELEPORT, + ITEM_ELYTRA_FLYING, + ITEM_FIRECHARGE_USE, + ITEM_FLINTANDSTEEL_USE, + ITEM_HOE_TILL, + ITEM_SHIELD_BLOCK, + ITEM_SHIELD_BREAK, + ITEM_SHOVEL_FLATTEN, + ITEM_TOTEM_USE, + MUSIC_CREATIVE, + MUSIC_CREDITS, + MUSIC_DRAGON, + MUSIC_END, + MUSIC_GAME, + MUSIC_MENU, + UI_BUTTON_CLICK, + UI_TOAST_CHALLENGE_COMPLETE, + UI_TOAST_IN, + UI_TOAST_OUT, + WEATHER_RAIN, + WEATHER_RAIN_ABOVE, + + // 1.13 renames + BLOCK_ENDER_CHEST_CLOSE, + BLOCK_ENDER_CHEST_OPEN, + BLOCK_LILY_PAD_PLACE, + BLOCK_METAL_PRESSURE_PLATE_CLICK_OFF, + BLOCK_METAL_PRESSURE_PLATE_CLICK_ON, + BLOCK_NOTE_BLOCK_BASEDRUM, + BLOCK_NOTE_BLOCK_BASS, + BLOCK_NOTE_BLOCK_BELL, + BLOCK_NOTE_BLOCK_CHIME, + BLOCK_NOTE_BLOCK_FLUTE, + BLOCK_NOTE_BLOCK_GUITAR, + BLOCK_NOTE_BLOCK_HARP, + BLOCK_NOTE_BLOCK_HAT, + BLOCK_NOTE_BLOCK_PLING, + BLOCK_NOTE_BLOCK_SNARE, + BLOCK_NOTE_BLOCK_XYLOPHONE, + BLOCK_SLIME_BLOCK_BREAK, + BLOCK_SLIME_BLOCK_FALL, + BLOCK_SLIME_BLOCK_HIT, + BLOCK_SLIME_BLOCK_PLACE, + BLOCK_SLIME_BLOCK_STEP, + BLOCK_STONE_PRESSURE_PLATE_CLICK_OFF, + BLOCK_STONE_PRESSURE_PLATE_CLICK_ON, + BLOCK_WOODEN_BUTTON_CLICK_OFF, + BLOCK_WOODEN_BUTTON_CLICK_ON, + BLOCK_WOODEN_PRESSURE_PLATE_CLICK_OFF, + BLOCK_WOODEN_PRESSURE_PLATE_CLICK_ON, + BLOCK_WOOL_BREAK, + BLOCK_WOOL_FALL, + BLOCK_WOOL_HIT, + BLOCK_WOOL_PLACE, + BLOCK_WOOL_STEP, + ENTITY_ARMOR_STAND_BREAK, + ENTITY_ARMOR_STAND_FALL, + ENTITY_ARMOR_STAND_HIT, + ENTITY_ARMOR_STAND_PLACE, + ENTITY_DRAGON_FIREBALL_EXPLODE, + ENTITY_ENDERMAN_AMBIENT, + ENTITY_ENDERMAN_DEATH, + ENTITY_ENDERMAN_HURT, + ENTITY_ENDERMAN_SCREAM, + ENTITY_ENDERMAN_STARE, + ENTITY_ENDERMAN_TELEPORT, + ENTITY_ENDER_DRAGON_AMBIENT, + ENTITY_ENDER_DRAGON_DEATH, + ENTITY_ENDER_DRAGON_FLAP, + ENTITY_ENDER_DRAGON_GROWL, + ENTITY_ENDER_DRAGON_HURT, + ENTITY_ENDER_DRAGON_SHOOT, + ENTITY_ENDER_EYE_DEATH, + ENTITY_ENDER_EYE_LAUNCH, + ENTITY_ENDER_PEARL_THROW, + ENTITY_EVOKER_AMBIENT, + ENTITY_EVOKER_CAST_SPELL, + ENTITY_EVOKER_DEATH, + ENTITY_EVOKER_FANGS_ATTACK, + ENTITY_EVOKER_HURT, + ENTITY_EVOKER_PREPARE_ATTACK, + ENTITY_EVOKER_PREPARE_SUMMON, + ENTITY_EVOKER_PREPARE_WOLOLO, + ENTITY_FIREWORK_ROCKET_BLAST, + ENTITY_FIREWORK_ROCKET_BLAST_FAR, + ENTITY_FIREWORK_ROCKET_LARGE_BLAST, + ENTITY_FIREWORK_ROCKET_LARGE_BLAST_FAR, + ENTITY_FIREWORK_ROCKET_LAUNCH, + ENTITY_FIREWORK_ROCKET_SHOOT, + ENTITY_FIREWORK_ROCKET_TWINKLE, + ENTITY_FIREWORK_ROCKET_TWINKLE_FAR, + ENTITY_FISHING_BOBBER_RETRIEVE, + ENTITY_FISHING_BOBBER_SPLASH, + ENTITY_FISHING_BOBBER_THROW, + ENTITY_ILLUSIONER_AMBIENT, + ENTITY_ILLUSIONER_CAST_SPELL, + ENTITY_ILLUSIONER_DEATH, + ENTITY_ILLUSIONER_HURT, + ENTITY_ILLUSIONER_MIRROR_MOVE, + ENTITY_ILLUSIONER_PREPARE_BLINDNESS, + ENTITY_ILLUSIONER_PREPARE_MIRROR, + ENTITY_IRON_GOLEM_ATTACK, + ENTITY_IRON_GOLEM_DEATH, + ENTITY_IRON_GOLEM_HURT, + ENTITY_IRON_GOLEM_STEP, + ENTITY_ITEM_FRAME_ADD_ITEM, + ENTITY_ITEM_FRAME_BREAK, + ENTITY_ITEM_FRAME_PLACE, + ENTITY_ITEM_FRAME_REMOVE_ITEM, + ENTITY_ITEM_FRAME_ROTATE_ITEM, + ENTITY_LEASH_KNOT_BREAK(MCVersion.MC1_9, MCVersion.MC1_21_5), + ENTITY_LEASH_KNOT_PLACE(MCVersion.MC1_9, MCVersion.MC1_21_5), + ENTITY_LIGHTNING_BOLT_IMPACT, + ENTITY_LIGHTNING_BOLT_THUNDER, + ENTITY_LINGERING_POTION_THROW, + ENTITY_MAGMA_CUBE_DEATH, + ENTITY_MAGMA_CUBE_DEATH_SMALL, + ENTITY_MAGMA_CUBE_HURT, + ENTITY_MAGMA_CUBE_HURT_SMALL, + ENTITY_MAGMA_CUBE_JUMP, + ENTITY_MAGMA_CUBE_SQUISH, + ENTITY_MAGMA_CUBE_SQUISH_SMALL, + ENTITY_PARROT_IMITATE_ENDER_DRAGON, + ENTITY_PARROT_IMITATE_EVOKER, + ENTITY_PARROT_IMITATE_ILLUSIONER, + ENTITY_PARROT_IMITATE_MAGMA_CUBE, + ENTITY_PARROT_IMITATE_VINDICATOR, + ENTITY_POLAR_BEAR_AMBIENT_BABY, + ENTITY_SLIME_DEATH_SMALL, + ENTITY_SLIME_HURT_SMALL, + ENTITY_SLIME_JUMP_SMALL, + ENTITY_SLIME_SQUISH_SMALL, + ENTITY_SNOW_GOLEM_AMBIENT, + ENTITY_SNOW_GOLEM_DEATH, + ENTITY_SNOW_GOLEM_HURT, + ENTITY_SNOW_GOLEM_SHOOT, + ENTITY_VILLAGER_TRADE, + ENTITY_VINDICATOR_AMBIENT, + ENTITY_VINDICATOR_DEATH, + ENTITY_VINDICATOR_HURT, + ENTITY_ZOMBIE_ATTACK_WOODEN_DOOR, + ENTITY_ZOMBIE_BREAK_WOODEN_DOOR, + MUSIC_DISC_11, + MUSIC_DISC_13, + MUSIC_DISC_BLOCKS, + MUSIC_DISC_CAT, + MUSIC_DISC_CHIRP, + MUSIC_DISC_FAR, + MUSIC_DISC_MALL, + MUSIC_DISC_MELLOHI, + MUSIC_DISC_STAL, + MUSIC_DISC_STRAD, + MUSIC_DISC_WAIT, + MUSIC_DISC_WARD, + + // 1.13 additions + AMBIENT_UNDERWATER_ENTER, + AMBIENT_UNDERWATER_EXIT, + AMBIENT_UNDERWATER_LOOP, + AMBIENT_UNDERWATER_LOOP_ADDITIONS, + AMBIENT_UNDERWATER_LOOP_ADDITIONS_RARE, + AMBIENT_UNDERWATER_LOOP_ADDITIONS_ULTRA_RARE, + BLOCK_BEACON_ACTIVATE, + BLOCK_BEACON_AMBIENT, + BLOCK_BEACON_DEACTIVATE, + BLOCK_BEACON_POWER_SELECT, + BLOCK_BUBBLE_COLUMN_BUBBLE_POP, + BLOCK_BUBBLE_COLUMN_UPWARDS_AMBIENT, + BLOCK_BUBBLE_COLUMN_UPWARDS_INSIDE, + BLOCK_BUBBLE_COLUMN_WHIRLPOOL_AMBIENT, + BLOCK_BUBBLE_COLUMN_WHIRLPOOL_INSIDE, + BLOCK_CONDUIT_ACTIVATE, + BLOCK_CONDUIT_AMBIENT, + BLOCK_CONDUIT_AMBIENT_SHORT, + BLOCK_CONDUIT_ATTACK_TARGET, + BLOCK_CONDUIT_DEACTIVATE, + BLOCK_CORAL_BLOCK_BREAK, + BLOCK_CORAL_BLOCK_FALL, + BLOCK_CORAL_BLOCK_HIT, + BLOCK_CORAL_BLOCK_PLACE, + BLOCK_CORAL_BLOCK_STEP, + BLOCK_PUMPKIN_CARVE, + BLOCK_WET_GRASS_BREAK, + BLOCK_WET_GRASS_FALL, + BLOCK_WET_GRASS_HIT, + BLOCK_WET_GRASS_PLACE, + BLOCK_WET_GRASS_STEP, + ENTITY_COD_AMBIENT, + ENTITY_COD_DEATH, + ENTITY_COD_FLOP, + ENTITY_COD_HURT, + ENTITY_DOLPHIN_AMBIENT, + ENTITY_DOLPHIN_AMBIENT_WATER, + ENTITY_DOLPHIN_ATTACK, + ENTITY_DOLPHIN_DEATH, + ENTITY_DOLPHIN_EAT, + ENTITY_DOLPHIN_HURT, + ENTITY_DOLPHIN_JUMP, + ENTITY_DOLPHIN_PLAY, + ENTITY_DOLPHIN_SPLASH, + ENTITY_DOLPHIN_SWIM, + ENTITY_DROWNED_AMBIENT, + ENTITY_DROWNED_AMBIENT_WATER, + ENTITY_DROWNED_DEATH, + ENTITY_DROWNED_DEATH_WATER, + ENTITY_DROWNED_HURT, + ENTITY_DROWNED_HURT_WATER, + ENTITY_DROWNED_SHOOT, + ENTITY_DROWNED_STEP, + ENTITY_DROWNED_SWIM, + ENTITY_FISH_SWIM, + ENTITY_HUSK_CONVERTED_TO_ZOMBIE, + ENTITY_PARROT_IMITATE_DROWNED, + ENTITY_PARROT_IMITATE_PHANTOM, + ENTITY_PHANTOM_AMBIENT, + ENTITY_PHANTOM_BITE, + ENTITY_PHANTOM_DEATH, + ENTITY_PHANTOM_FLAP, + ENTITY_PHANTOM_HURT, + ENTITY_PHANTOM_SWOOP, + ENTITY_PLAYER_SPLASH_HIGH_SPEED, + ENTITY_PUFFER_FISH_AMBIENT(MCVersion.MC1_13, MCVersion.MC1_21_5), + ENTITY_PUFFER_FISH_BLOW_OUT, + ENTITY_PUFFER_FISH_BLOW_UP, + ENTITY_PUFFER_FISH_DEATH, + ENTITY_PUFFER_FISH_FLOP, + ENTITY_PUFFER_FISH_HURT, + ENTITY_PUFFER_FISH_STING, + ENTITY_SALMON_AMBIENT, + ENTITY_SALMON_DEATH, + ENTITY_SALMON_FLOP, + ENTITY_SALMON_HURT, + ENTITY_SKELETON_HORSE_AMBIENT_WATER, + ENTITY_SKELETON_HORSE_GALLOP_WATER, + ENTITY_SKELETON_HORSE_JUMP_WATER, + ENTITY_SKELETON_HORSE_STEP_WATER, + ENTITY_SKELETON_HORSE_SWIM, + ENTITY_SQUID_SQUIRT, + ENTITY_TROPICAL_FISH_AMBIENT, + ENTITY_TROPICAL_FISH_DEATH, + ENTITY_TROPICAL_FISH_FLOP, + ENTITY_TROPICAL_FISH_HURT, + ENTITY_TURTLE_AMBIENT_LAND, + ENTITY_TURTLE_DEATH, + ENTITY_TURTLE_DEATH_BABY, + ENTITY_TURTLE_EGG_BREAK, + ENTITY_TURTLE_EGG_CRACK, + ENTITY_TURTLE_EGG_HATCH, + ENTITY_TURTLE_HURT, + ENTITY_TURTLE_HURT_BABY, + ENTITY_TURTLE_LAY_EGG, + ENTITY_TURTLE_SHAMBLE, + ENTITY_TURTLE_SHAMBLE_BABY, + ENTITY_TURTLE_SWIM, + ENTITY_ZOMBIE_CONVERTED_TO_DROWNED, + ENTITY_ZOMBIE_DESTROY_EGG, + ITEM_ARMOR_EQUIP_TURTLE, + ITEM_AXE_STRIP, + ITEM_BUCKET_EMPTY_FISH, + ITEM_BUCKET_FILL_FISH, + ITEM_TRIDENT_HIT, + ITEM_TRIDENT_HIT_GROUND, + ITEM_TRIDENT_RETURN, + ITEM_TRIDENT_RIPTIDE_1, + ITEM_TRIDENT_RIPTIDE_2, + ITEM_TRIDENT_RIPTIDE_3, + ITEM_TRIDENT_THROW, + ITEM_TRIDENT_THUNDER, + MUSIC_UNDER_WATER, + + // 1.14 additions + BLOCK_BAMBOO_BREAK(MCVersion.MC1_14), + BLOCK_BAMBOO_FALL(MCVersion.MC1_14), + BLOCK_BAMBOO_HIT(MCVersion.MC1_14), + BLOCK_BAMBOO_PLACE(MCVersion.MC1_14), + BLOCK_BAMBOO_SAPLING_BREAK(MCVersion.MC1_14), + BLOCK_BAMBOO_SAPLING_HIT(MCVersion.MC1_14), + BLOCK_BAMBOO_SAPLING_PLACE(MCVersion.MC1_14), + BLOCK_BAMBOO_STEP(MCVersion.MC1_14), + BLOCK_BARREL_CLOSE(MCVersion.MC1_14), + BLOCK_BARREL_OPEN(MCVersion.MC1_14), + BLOCK_BELL_RESONATE(MCVersion.MC1_14), + BLOCK_BELL_USE(MCVersion.MC1_14), + BLOCK_BLASTFURNACE_FIRE_CRACKLE(MCVersion.MC1_14), + BLOCK_CAMPFIRE_CRACKLE(MCVersion.MC1_14), + BLOCK_COMPOSTER_EMPTY(MCVersion.MC1_14), + BLOCK_COMPOSTER_FILL(MCVersion.MC1_14), + BLOCK_COMPOSTER_FILL_SUCCESS(MCVersion.MC1_14), + BLOCK_COMPOSTER_READY(MCVersion.MC1_14), + BLOCK_CROP_BREAK(MCVersion.MC1_14), + BLOCK_GRINDSTONE_USE(MCVersion.MC1_14), + BLOCK_LANTERN_BREAK(MCVersion.MC1_14), + BLOCK_LANTERN_FALL(MCVersion.MC1_14), + BLOCK_LANTERN_HIT(MCVersion.MC1_14), + BLOCK_LANTERN_PLACE(MCVersion.MC1_14), + BLOCK_LANTERN_STEP(MCVersion.MC1_14), + BLOCK_NETHER_WART_BREAK(MCVersion.MC1_14), + BLOCK_NOTE_BLOCK_BANJO(MCVersion.MC1_14), + BLOCK_NOTE_BLOCK_BIT(MCVersion.MC1_14), + BLOCK_NOTE_BLOCK_COW_BELL(MCVersion.MC1_14), + BLOCK_NOTE_BLOCK_DIDGERIDOO(MCVersion.MC1_14), + BLOCK_NOTE_BLOCK_IRON_XYLOPHONE(MCVersion.MC1_14), + BLOCK_SCAFFOLDING_BREAK(MCVersion.MC1_14), + BLOCK_SCAFFOLDING_FALL(MCVersion.MC1_14), + BLOCK_SCAFFOLDING_HIT(MCVersion.MC1_14), + BLOCK_SCAFFOLDING_PLACE(MCVersion.MC1_14), + BLOCK_SCAFFOLDING_STEP(MCVersion.MC1_14), + BLOCK_SMOKER_SMOKE(MCVersion.MC1_14), + BLOCK_SWEET_BERRY_BUSH_BREAK(MCVersion.MC1_14), + BLOCK_SWEET_BERRY_BUSH_PLACE(MCVersion.MC1_14), + ENTITY_CAT_BEG_FOR_FOOD(MCVersion.MC1_14), + ENTITY_CAT_EAT(MCVersion.MC1_14), + ENTITY_CAT_STRAY_AMBIENT(MCVersion.MC1_14), + ENTITY_EVOKER_CELEBRATE(MCVersion.MC1_14), + ENTITY_FOX_AGGRO(MCVersion.MC1_14), + ENTITY_FOX_AMBIENT(MCVersion.MC1_14), + ENTITY_FOX_BITE(MCVersion.MC1_14), + ENTITY_FOX_DEATH(MCVersion.MC1_14), + ENTITY_FOX_EAT(MCVersion.MC1_14), + ENTITY_FOX_HURT(MCVersion.MC1_14), + ENTITY_FOX_SCREECH(MCVersion.MC1_14), + ENTITY_FOX_SLEEP(MCVersion.MC1_14), + ENTITY_FOX_SNIFF(MCVersion.MC1_14), + ENTITY_FOX_SPIT(MCVersion.MC1_14), + ENTITY_MOOSHROOM_CONVERT(MCVersion.MC1_14), + ENTITY_MOOSHROOM_EAT(MCVersion.MC1_14), + ENTITY_MOOSHROOM_MILK(MCVersion.MC1_14), + ENTITY_MOOSHROOM_SUSPICIOUS_MILK(MCVersion.MC1_14), + ENTITY_OCELOT_AMBIENT(MCVersion.MC1_14), + ENTITY_OCELOT_DEATH(MCVersion.MC1_14), + ENTITY_OCELOT_HURT(MCVersion.MC1_14), + ENTITY_PANDA_AGGRESSIVE_AMBIENT(MCVersion.MC1_14), + ENTITY_PANDA_AMBIENT(MCVersion.MC1_14), + ENTITY_PANDA_BITE(MCVersion.MC1_14), + ENTITY_PANDA_CANT_BREED(MCVersion.MC1_14), + ENTITY_PANDA_DEATH(MCVersion.MC1_14), + ENTITY_PANDA_EAT(MCVersion.MC1_14), + ENTITY_PANDA_HURT(MCVersion.MC1_14), + ENTITY_PANDA_PRE_SNEEZE(MCVersion.MC1_14), + ENTITY_PANDA_SNEEZE(MCVersion.MC1_14), + ENTITY_PANDA_STEP(MCVersion.MC1_14), + ENTITY_PANDA_WORRIED_AMBIENT(MCVersion.MC1_14), + ENTITY_PARROT_IMITATE_GUARDIAN(MCVersion.MC1_14), + ENTITY_PARROT_IMITATE_PILLAGER(MCVersion.MC1_14), + ENTITY_PARROT_IMITATE_RAVAGER(MCVersion.MC1_14), + ENTITY_PILLAGER_AMBIENT(MCVersion.MC1_14), + ENTITY_PILLAGER_CELEBRATE(MCVersion.MC1_14), + ENTITY_PILLAGER_DEATH(MCVersion.MC1_14), + ENTITY_PILLAGER_HURT(MCVersion.MC1_14), + ENTITY_PLAYER_HURT_SWEET_BERRY_BUSH(MCVersion.MC1_14), + ENTITY_RAVAGER_AMBIENT(MCVersion.MC1_14), + ENTITY_RAVAGER_ATTACK(MCVersion.MC1_14), + ENTITY_RAVAGER_CELEBRATE(MCVersion.MC1_14), + ENTITY_RAVAGER_DEATH(MCVersion.MC1_14), + ENTITY_RAVAGER_HURT(MCVersion.MC1_14), + ENTITY_RAVAGER_ROAR(MCVersion.MC1_14), + ENTITY_RAVAGER_STEP(MCVersion.MC1_14), + ENTITY_RAVAGER_STUNNED(MCVersion.MC1_14), + ENTITY_VILLAGER_CELEBRATE(MCVersion.MC1_14), + ENTITY_VILLAGER_WORK_ARMORER(MCVersion.MC1_14), + ENTITY_VILLAGER_WORK_BUTCHER(MCVersion.MC1_14), + ENTITY_VILLAGER_WORK_CARTOGRAPHER(MCVersion.MC1_14), + ENTITY_VILLAGER_WORK_CLERIC(MCVersion.MC1_14), + ENTITY_VILLAGER_WORK_FARMER(MCVersion.MC1_14), + ENTITY_VILLAGER_WORK_FISHERMAN(MCVersion.MC1_14), + ENTITY_VILLAGER_WORK_FLETCHER(MCVersion.MC1_14), + ENTITY_VILLAGER_WORK_LEATHERWORKER(MCVersion.MC1_14), + ENTITY_VILLAGER_WORK_LIBRARIAN(MCVersion.MC1_14), + ENTITY_VILLAGER_WORK_MASON(MCVersion.MC1_14), + ENTITY_VILLAGER_WORK_SHEPHERD(MCVersion.MC1_14), + ENTITY_VILLAGER_WORK_TOOLSMITH(MCVersion.MC1_14), + ENTITY_VILLAGER_WORK_WEAPONSMITH(MCVersion.MC1_14), + ENTITY_VINDICATOR_CELEBRATE(MCVersion.MC1_14), + ENTITY_WANDERING_TRADER_AMBIENT(MCVersion.MC1_14), + ENTITY_WANDERING_TRADER_DEATH(MCVersion.MC1_14), + ENTITY_WANDERING_TRADER_DISAPPEARED(MCVersion.MC1_14), + ENTITY_WANDERING_TRADER_DRINK_MILK(MCVersion.MC1_14), + ENTITY_WANDERING_TRADER_DRINK_POTION(MCVersion.MC1_14), + ENTITY_WANDERING_TRADER_HURT(MCVersion.MC1_14), + ENTITY_WANDERING_TRADER_NO(MCVersion.MC1_14), + ENTITY_WANDERING_TRADER_REAPPEARED(MCVersion.MC1_14), + ENTITY_WANDERING_TRADER_TRADE(MCVersion.MC1_14), + ENTITY_WANDERING_TRADER_YES(MCVersion.MC1_14), + ENTITY_WITCH_CELEBRATE(MCVersion.MC1_14), + EVENT_RAID_HORN(MCVersion.MC1_14), + ITEM_BOOK_PAGE_TURN(MCVersion.MC1_14), + ITEM_BOOK_PUT(MCVersion.MC1_14), + ITEM_CROP_PLANT(MCVersion.MC1_14), + ITEM_CROSSBOW_HIT(MCVersion.MC1_14), + ITEM_CROSSBOW_LOADING_END(MCVersion.MC1_14), + ITEM_CROSSBOW_LOADING_MIDDLE(MCVersion.MC1_14), + ITEM_CROSSBOW_LOADING_START(MCVersion.MC1_14), + ITEM_CROSSBOW_QUICK_CHARGE_1(MCVersion.MC1_14), + ITEM_CROSSBOW_QUICK_CHARGE_2(MCVersion.MC1_14), + ITEM_CROSSBOW_QUICK_CHARGE_3(MCVersion.MC1_14), + ITEM_CROSSBOW_SHOOT(MCVersion.MC1_14), + ITEM_NETHER_WART_PLANT(MCVersion.MC1_14), + ITEM_SWEET_BERRIES_PICK_FROM_BUSH(MCVersion.MC1_14, MCVersion.MC1_16_X), + UI_CARTOGRAPHY_TABLE_TAKE_RESULT(MCVersion.MC1_14), + UI_LOOM_SELECT_PATTERN(MCVersion.MC1_14), + UI_LOOM_TAKE_RESULT(MCVersion.MC1_14), + UI_STONECUTTER_SELECT_RECIPE(MCVersion.MC1_14), + UI_STONECUTTER_TAKE_RESULT(MCVersion.MC1_14), + + // 1.15 additions + BLOCK_BEEHIVE_DRIP(MCVersion.MC1_15), + BLOCK_BEEHIVE_ENTER(MCVersion.MC1_15), + BLOCK_BEEHIVE_EXIT(MCVersion.MC1_15), + BLOCK_BEEHIVE_SHEAR(MCVersion.MC1_15), + BLOCK_BEEHIVE_WORK(MCVersion.MC1_15), + BLOCK_HONEY_BLOCK_BREAK(MCVersion.MC1_15), + BLOCK_HONEY_BLOCK_FALL(MCVersion.MC1_15), + BLOCK_HONEY_BLOCK_HIT(MCVersion.MC1_15), + BLOCK_HONEY_BLOCK_PLACE(MCVersion.MC1_15), + BLOCK_HONEY_BLOCK_SLIDE(MCVersion.MC1_15), + BLOCK_HONEY_BLOCK_STEP(MCVersion.MC1_15), + ENTITY_BEE_DEATH(MCVersion.MC1_15), + ENTITY_BEE_HURT(MCVersion.MC1_15), + ENTITY_BEE_LOOP(MCVersion.MC1_15), + ENTITY_BEE_LOOP_AGGRESSIVE(MCVersion.MC1_15), + ENTITY_BEE_POLLINATE(MCVersion.MC1_15), + ENTITY_BEE_STING(MCVersion.MC1_15), + ENTITY_IRON_GOLEM_DAMAGE(MCVersion.MC1_15), + ENTITY_IRON_GOLEM_REPAIR(MCVersion.MC1_15), + ITEM_HONEY_BOTTLE_DRINK(MCVersion.MC1_15), + + // 1.16 additions + AMBIENT_BASALT_DELTAS_ADDITIONS(MCVersion.MC1_16), + AMBIENT_BASALT_DELTAS_LOOP(MCVersion.MC1_16), + AMBIENT_BASALT_DELTAS_MOOD(MCVersion.MC1_16), + AMBIENT_CRIMSON_FOREST_ADDITIONS(MCVersion.MC1_16), + AMBIENT_CRIMSON_FOREST_LOOP(MCVersion.MC1_16), + AMBIENT_CRIMSON_FOREST_MOOD(MCVersion.MC1_16), + AMBIENT_NETHER_WASTES_ADDITIONS(MCVersion.MC1_16), + AMBIENT_NETHER_WASTES_LOOP(MCVersion.MC1_16), + AMBIENT_NETHER_WASTES_MOOD(MCVersion.MC1_16), + AMBIENT_SOUL_SAND_VALLEY_ADDITIONS(MCVersion.MC1_16), + AMBIENT_SOUL_SAND_VALLEY_LOOP(MCVersion.MC1_16), + AMBIENT_SOUL_SAND_VALLEY_MOOD(MCVersion.MC1_16), + AMBIENT_WARPED_FOREST_ADDITIONS(MCVersion.MC1_16), + AMBIENT_WARPED_FOREST_LOOP(MCVersion.MC1_16), + AMBIENT_WARPED_FOREST_MOOD(MCVersion.MC1_16), + BLOCK_ANCIENT_DEBRIS_BREAK(MCVersion.MC1_16), + BLOCK_ANCIENT_DEBRIS_FALL(MCVersion.MC1_16), + BLOCK_ANCIENT_DEBRIS_HIT(MCVersion.MC1_16), + BLOCK_ANCIENT_DEBRIS_PLACE(MCVersion.MC1_16), + BLOCK_ANCIENT_DEBRIS_STEP(MCVersion.MC1_16), + BLOCK_BASALT_BREAK(MCVersion.MC1_16), + BLOCK_BASALT_FALL(MCVersion.MC1_16), + BLOCK_BASALT_HIT(MCVersion.MC1_16), + BLOCK_BASALT_PLACE(MCVersion.MC1_16), + BLOCK_BASALT_STEP(MCVersion.MC1_16), + BLOCK_BONE_BLOCK_BREAK(MCVersion.MC1_16), + BLOCK_BONE_BLOCK_FALL(MCVersion.MC1_16), + BLOCK_BONE_BLOCK_HIT(MCVersion.MC1_16), + BLOCK_BONE_BLOCK_PLACE(MCVersion.MC1_16), + BLOCK_BONE_BLOCK_STEP(MCVersion.MC1_16), + BLOCK_CHAIN_BREAK(MCVersion.MC1_16), + BLOCK_CHAIN_FALL(MCVersion.MC1_16), + BLOCK_CHAIN_HIT(MCVersion.MC1_16), + BLOCK_CHAIN_PLACE(MCVersion.MC1_16), + BLOCK_CHAIN_STEP(MCVersion.MC1_16), + BLOCK_FUNGUS_BREAK(MCVersion.MC1_16), + BLOCK_FUNGUS_FALL(MCVersion.MC1_16), + BLOCK_FUNGUS_HIT(MCVersion.MC1_16), + BLOCK_FUNGUS_PLACE(MCVersion.MC1_16), + BLOCK_FUNGUS_STEP(MCVersion.MC1_16), + BLOCK_GILDED_BLACKSTONE_BREAK(MCVersion.MC1_16), + BLOCK_GILDED_BLACKSTONE_FALL(MCVersion.MC1_16), + BLOCK_GILDED_BLACKSTONE_HIT(MCVersion.MC1_16), + BLOCK_GILDED_BLACKSTONE_PLACE(MCVersion.MC1_16), + BLOCK_GILDED_BLACKSTONE_STEP(MCVersion.MC1_16), + BLOCK_LODESTONE_BREAK(MCVersion.MC1_16), + BLOCK_LODESTONE_FALL(MCVersion.MC1_16), + BLOCK_LODESTONE_HIT(MCVersion.MC1_16), + BLOCK_LODESTONE_PLACE(MCVersion.MC1_16), + BLOCK_LODESTONE_STEP(MCVersion.MC1_16), + BLOCK_NETHERITE_BLOCK_BREAK(MCVersion.MC1_16), + BLOCK_NETHERITE_BLOCK_FALL(MCVersion.MC1_16), + BLOCK_NETHERITE_BLOCK_HIT(MCVersion.MC1_16), + BLOCK_NETHERITE_BLOCK_PLACE(MCVersion.MC1_16), + BLOCK_NETHERITE_BLOCK_STEP(MCVersion.MC1_16), + BLOCK_NETHERRACK_BREAK(MCVersion.MC1_16), + BLOCK_NETHERRACK_FALL(MCVersion.MC1_16), + BLOCK_NETHERRACK_HIT(MCVersion.MC1_16), + BLOCK_NETHERRACK_PLACE(MCVersion.MC1_16), + BLOCK_NETHERRACK_STEP(MCVersion.MC1_16), + BLOCK_NETHER_BRICKS_BREAK(MCVersion.MC1_16), + BLOCK_NETHER_BRICKS_FALL(MCVersion.MC1_16), + BLOCK_NETHER_BRICKS_HIT(MCVersion.MC1_16), + BLOCK_NETHER_BRICKS_PLACE(MCVersion.MC1_16), + BLOCK_NETHER_BRICKS_STEP(MCVersion.MC1_16), + BLOCK_NETHER_GOLD_ORE_BREAK(MCVersion.MC1_16), + BLOCK_NETHER_GOLD_ORE_FALL(MCVersion.MC1_16), + BLOCK_NETHER_GOLD_ORE_HIT(MCVersion.MC1_16), + BLOCK_NETHER_GOLD_ORE_PLACE(MCVersion.MC1_16), + BLOCK_NETHER_GOLD_ORE_STEP(MCVersion.MC1_16), + BLOCK_NETHER_ORE_BREAK(MCVersion.MC1_16), + BLOCK_NETHER_ORE_FALL(MCVersion.MC1_16), + BLOCK_NETHER_ORE_HIT(MCVersion.MC1_16), + BLOCK_NETHER_ORE_PLACE(MCVersion.MC1_16), + BLOCK_NETHER_ORE_STEP(MCVersion.MC1_16), + BLOCK_NETHER_SPROUTS_BREAK(MCVersion.MC1_16), + BLOCK_NETHER_SPROUTS_FALL(MCVersion.MC1_16), + BLOCK_NETHER_SPROUTS_HIT(MCVersion.MC1_16), + BLOCK_NETHER_SPROUTS_PLACE(MCVersion.MC1_16), + BLOCK_NETHER_SPROUTS_STEP(MCVersion.MC1_16), + BLOCK_NYLIUM_BREAK(MCVersion.MC1_16), + BLOCK_NYLIUM_FALL(MCVersion.MC1_16), + BLOCK_NYLIUM_HIT(MCVersion.MC1_16), + BLOCK_NYLIUM_PLACE(MCVersion.MC1_16), + BLOCK_NYLIUM_STEP(MCVersion.MC1_16), + BLOCK_RESPAWN_ANCHOR_AMBIENT(MCVersion.MC1_16), + BLOCK_RESPAWN_ANCHOR_CHARGE(MCVersion.MC1_16), + BLOCK_RESPAWN_ANCHOR_DEPLETE(MCVersion.MC1_16), + BLOCK_RESPAWN_ANCHOR_SET_SPAWN(MCVersion.MC1_16), + BLOCK_ROOTS_BREAK(MCVersion.MC1_16), + BLOCK_ROOTS_FALL(MCVersion.MC1_16), + BLOCK_ROOTS_HIT(MCVersion.MC1_16), + BLOCK_ROOTS_PLACE(MCVersion.MC1_16), + BLOCK_ROOTS_STEP(MCVersion.MC1_16), + BLOCK_SHROOMLIGHT_BREAK(MCVersion.MC1_16), + BLOCK_SHROOMLIGHT_FALL(MCVersion.MC1_16), + BLOCK_SHROOMLIGHT_HIT(MCVersion.MC1_16), + BLOCK_SHROOMLIGHT_PLACE(MCVersion.MC1_16), + BLOCK_SHROOMLIGHT_STEP(MCVersion.MC1_16), + BLOCK_SMITHING_TABLE_USE(MCVersion.MC1_16), + BLOCK_SOUL_SAND_BREAK(MCVersion.MC1_16), + BLOCK_SOUL_SAND_FALL(MCVersion.MC1_16), + BLOCK_SOUL_SAND_HIT(MCVersion.MC1_16), + BLOCK_SOUL_SAND_PLACE(MCVersion.MC1_16), + BLOCK_SOUL_SAND_STEP(MCVersion.MC1_16), + BLOCK_SOUL_SOIL_BREAK(MCVersion.MC1_16), + BLOCK_SOUL_SOIL_FALL(MCVersion.MC1_16), + BLOCK_SOUL_SOIL_HIT(MCVersion.MC1_16), + BLOCK_SOUL_SOIL_PLACE(MCVersion.MC1_16), + BLOCK_SOUL_SOIL_STEP(MCVersion.MC1_16), + BLOCK_STEM_BREAK(MCVersion.MC1_16), + BLOCK_STEM_FALL(MCVersion.MC1_16), + BLOCK_STEM_HIT(MCVersion.MC1_16), + BLOCK_STEM_PLACE(MCVersion.MC1_16), + BLOCK_STEM_STEP(MCVersion.MC1_16), + BLOCK_VINE_STEP(MCVersion.MC1_16), + BLOCK_WART_BLOCK_BREAK(MCVersion.MC1_16), + BLOCK_WART_BLOCK_FALL(MCVersion.MC1_16), + BLOCK_WART_BLOCK_HIT(MCVersion.MC1_16), + BLOCK_WART_BLOCK_PLACE(MCVersion.MC1_16), + BLOCK_WART_BLOCK_STEP(MCVersion.MC1_16), + BLOCK_WEEPING_VINES_BREAK(MCVersion.MC1_16), + BLOCK_WEEPING_VINES_FALL(MCVersion.MC1_16), + BLOCK_WEEPING_VINES_HIT(MCVersion.MC1_16), + BLOCK_WEEPING_VINES_PLACE(MCVersion.MC1_16), + BLOCK_WEEPING_VINES_STEP(MCVersion.MC1_16), + ENTITY_DONKEY_EAT(MCVersion.MC1_16), + ENTITY_FOX_TELEPORT(MCVersion.MC1_16), + ENTITY_HOGLIN_AMBIENT(MCVersion.MC1_16), + ENTITY_HOGLIN_ANGRY(MCVersion.MC1_16), + ENTITY_HOGLIN_ATTACK(MCVersion.MC1_16), + ENTITY_HOGLIN_CONVERTED_TO_ZOMBIFIED(MCVersion.MC1_16), + ENTITY_HOGLIN_DEATH(MCVersion.MC1_16), + ENTITY_HOGLIN_HURT(MCVersion.MC1_16), + ENTITY_HOGLIN_RETREAT(MCVersion.MC1_16), + ENTITY_HOGLIN_STEP(MCVersion.MC1_16), + ENTITY_MULE_ANGRY(MCVersion.MC1_16), + ENTITY_MULE_EAT(MCVersion.MC1_16), + ENTITY_PARROT_IMITATE_HOGLIN(MCVersion.MC1_16), + ENTITY_PARROT_IMITATE_PIGLIN(MCVersion.MC1_16), + ENTITY_PARROT_IMITATE_ZOGLIN(MCVersion.MC1_16), + ENTITY_PIGLIN_ADMIRING_ITEM(MCVersion.MC1_16), + ENTITY_PIGLIN_AMBIENT(MCVersion.MC1_16), + ENTITY_PIGLIN_ANGRY(MCVersion.MC1_16), + ENTITY_PIGLIN_CELEBRATE(MCVersion.MC1_16), + ENTITY_PIGLIN_CONVERTED_TO_ZOMBIFIED(MCVersion.MC1_16), + ENTITY_PIGLIN_DEATH(MCVersion.MC1_16), + ENTITY_PIGLIN_HURT(MCVersion.MC1_16), + ENTITY_PIGLIN_JEALOUS(MCVersion.MC1_16), + ENTITY_PIGLIN_RETREAT(MCVersion.MC1_16), + ENTITY_PIGLIN_STEP(MCVersion.MC1_16), + ENTITY_SNOW_GOLEM_SHEAR(MCVersion.MC1_16), + ENTITY_STRIDER_AMBIENT(MCVersion.MC1_16), + ENTITY_STRIDER_DEATH(MCVersion.MC1_16), + ENTITY_STRIDER_EAT(MCVersion.MC1_16), + ENTITY_STRIDER_HAPPY(MCVersion.MC1_16), + ENTITY_STRIDER_HURT(MCVersion.MC1_16), + ENTITY_STRIDER_RETREAT(MCVersion.MC1_16), + ENTITY_STRIDER_SADDLE(MCVersion.MC1_16), + ENTITY_STRIDER_STEP(MCVersion.MC1_16), + ENTITY_STRIDER_STEP_LAVA(MCVersion.MC1_16), + ENTITY_ZOGLIN_AMBIENT(MCVersion.MC1_16), + ENTITY_ZOGLIN_ANGRY(MCVersion.MC1_16), + ENTITY_ZOGLIN_ATTACK(MCVersion.MC1_16), + ENTITY_ZOGLIN_DEATH(MCVersion.MC1_16), + ENTITY_ZOGLIN_HURT(MCVersion.MC1_16), + ENTITY_ZOGLIN_STEP(MCVersion.MC1_16), + ENTITY_ZOMBIFIED_PIGLIN_AMBIENT(MCVersion.MC1_16), + ENTITY_ZOMBIFIED_PIGLIN_ANGRY(MCVersion.MC1_16), + ENTITY_ZOMBIFIED_PIGLIN_DEATH(MCVersion.MC1_16), + ENTITY_ZOMBIFIED_PIGLIN_HURT(MCVersion.MC1_16), + ITEM_ARMOR_EQUIP_NETHERITE(MCVersion.MC1_16), + ITEM_LODESTONE_COMPASS_LOCK(MCVersion.MC1_16), + MUSIC_DISC_PIGSTEP(MCVersion.MC1_16), + MUSIC_NETHER_BASALT_DELTAS(MCVersion.MC1_16), + MUSIC_NETHER_CRIMSON_FOREST(MCVersion.MC1_16), + MUSIC_NETHER_NETHER_WASTES(MCVersion.MC1_16), + MUSIC_NETHER_SOUL_SAND_VALLEY(MCVersion.MC1_16), + MUSIC_NETHER_WARPED_FOREST(MCVersion.MC1_16), + PARTICLE_SOUL_ESCAPE(MCVersion.MC1_16), + + // 1.16.2 additions + ENTITY_PARROT_IMITATE_PIGLIN_BRUTE(MCVersion.MC1_16_X), + ENTITY_PIGLIN_BRUTE_AMBIENT(MCVersion.MC1_16_X), + ENTITY_PIGLIN_BRUTE_ANGRY(MCVersion.MC1_16_X), + ENTITY_PIGLIN_BRUTE_CONVERTED_TO_ZOMBIFIED(MCVersion.MC1_16_X), + ENTITY_PIGLIN_BRUTE_DEATH(MCVersion.MC1_16_X), + ENTITY_PIGLIN_BRUTE_HURT(MCVersion.MC1_16_X), + ENTITY_PIGLIN_BRUTE_STEP(MCVersion.MC1_16_X), + + // 1.17 additions + BLOCK_AMETHYST_BLOCK_BREAK(MCVersion.MC1_17), + BLOCK_AMETHYST_BLOCK_CHIME(MCVersion.MC1_17), + BLOCK_AMETHYST_BLOCK_FALL(MCVersion.MC1_17), + BLOCK_AMETHYST_BLOCK_HIT(MCVersion.MC1_17), + BLOCK_AMETHYST_BLOCK_PLACE(MCVersion.MC1_17), + BLOCK_AMETHYST_BLOCK_STEP(MCVersion.MC1_17), + BLOCK_AMETHYST_CLUSTER_BREAK(MCVersion.MC1_17), + BLOCK_AMETHYST_CLUSTER_FALL(MCVersion.MC1_17), + BLOCK_AMETHYST_CLUSTER_HIT(MCVersion.MC1_17), + BLOCK_AMETHYST_CLUSTER_PLACE(MCVersion.MC1_17), + BLOCK_AMETHYST_CLUSTER_STEP(MCVersion.MC1_17), + BLOCK_AZALEA_BREAK(MCVersion.MC1_17), + BLOCK_AZALEA_FALL(MCVersion.MC1_17), + BLOCK_AZALEA_HIT(MCVersion.MC1_17), + BLOCK_AZALEA_LEAVES_BREAK(MCVersion.MC1_17), + BLOCK_AZALEA_LEAVES_FALL(MCVersion.MC1_17), + BLOCK_AZALEA_LEAVES_HIT(MCVersion.MC1_17), + BLOCK_AZALEA_LEAVES_PLACE(MCVersion.MC1_17), + BLOCK_AZALEA_LEAVES_STEP(MCVersion.MC1_17), + BLOCK_AZALEA_PLACE(MCVersion.MC1_17), + BLOCK_AZALEA_STEP(MCVersion.MC1_17), + BLOCK_BIG_DRIPLEAF_BREAK(MCVersion.MC1_17), + BLOCK_BIG_DRIPLEAF_FALL(MCVersion.MC1_17), + BLOCK_BIG_DRIPLEAF_HIT(MCVersion.MC1_17), + BLOCK_BIG_DRIPLEAF_PLACE(MCVersion.MC1_17), + BLOCK_BIG_DRIPLEAF_STEP(MCVersion.MC1_17), + BLOCK_BIG_DRIPLEAF_TILT_DOWN(MCVersion.MC1_17), + BLOCK_BIG_DRIPLEAF_TILT_UP(MCVersion.MC1_17), + BLOCK_CAKE_ADD_CANDLE(MCVersion.MC1_17), + BLOCK_CALCITE_BREAK(MCVersion.MC1_17), + BLOCK_CALCITE_FALL(MCVersion.MC1_17), + BLOCK_CALCITE_HIT(MCVersion.MC1_17), + BLOCK_CALCITE_PLACE(MCVersion.MC1_17), + BLOCK_CALCITE_STEP(MCVersion.MC1_17), + BLOCK_CANDLE_AMBIENT(MCVersion.MC1_17), + BLOCK_CANDLE_BREAK(MCVersion.MC1_17), + BLOCK_CANDLE_EXTINGUISH(MCVersion.MC1_17), + BLOCK_CANDLE_FALL(MCVersion.MC1_17), + BLOCK_CANDLE_HIT(MCVersion.MC1_17), + BLOCK_CANDLE_PLACE(MCVersion.MC1_17), + BLOCK_CANDLE_STEP(MCVersion.MC1_17), + BLOCK_CAVE_VINES_BREAK(MCVersion.MC1_17), + BLOCK_CAVE_VINES_FALL(MCVersion.MC1_17), + BLOCK_CAVE_VINES_HIT(MCVersion.MC1_17), + BLOCK_CAVE_VINES_PICK_BERRIES(MCVersion.MC1_17), + BLOCK_CAVE_VINES_PLACE(MCVersion.MC1_17), + BLOCK_CAVE_VINES_STEP(MCVersion.MC1_17), + BLOCK_COPPER_BREAK(MCVersion.MC1_17), + BLOCK_COPPER_FALL(MCVersion.MC1_17), + BLOCK_COPPER_HIT(MCVersion.MC1_17), + BLOCK_COPPER_PLACE(MCVersion.MC1_17), + BLOCK_COPPER_STEP(MCVersion.MC1_17), + BLOCK_DEEPSLATE_BREAK(MCVersion.MC1_17), + BLOCK_DEEPSLATE_BRICKS_BREAK(MCVersion.MC1_17), + BLOCK_DEEPSLATE_BRICKS_FALL(MCVersion.MC1_17), + BLOCK_DEEPSLATE_BRICKS_HIT(MCVersion.MC1_17), + BLOCK_DEEPSLATE_BRICKS_PLACE(MCVersion.MC1_17), + BLOCK_DEEPSLATE_BRICKS_STEP(MCVersion.MC1_17), + BLOCK_DEEPSLATE_FALL(MCVersion.MC1_17), + BLOCK_DEEPSLATE_HIT(MCVersion.MC1_17), + BLOCK_DEEPSLATE_PLACE(MCVersion.MC1_17), + BLOCK_DEEPSLATE_STEP(MCVersion.MC1_17), + BLOCK_DEEPSLATE_TILES_BREAK(MCVersion.MC1_17), + BLOCK_DEEPSLATE_TILES_FALL(MCVersion.MC1_17), + BLOCK_DEEPSLATE_TILES_HIT(MCVersion.MC1_17), + BLOCK_DEEPSLATE_TILES_PLACE(MCVersion.MC1_17), + BLOCK_DEEPSLATE_TILES_STEP(MCVersion.MC1_17), + BLOCK_DRIPSTONE_BLOCK_BREAK(MCVersion.MC1_17), + BLOCK_DRIPSTONE_BLOCK_FALL(MCVersion.MC1_17), + BLOCK_DRIPSTONE_BLOCK_HIT(MCVersion.MC1_17), + BLOCK_DRIPSTONE_BLOCK_PLACE(MCVersion.MC1_17), + BLOCK_DRIPSTONE_BLOCK_STEP(MCVersion.MC1_17), + BLOCK_FLOWERING_AZALEA_BREAK(MCVersion.MC1_17), + BLOCK_FLOWERING_AZALEA_FALL(MCVersion.MC1_17), + BLOCK_FLOWERING_AZALEA_HIT(MCVersion.MC1_17), + BLOCK_FLOWERING_AZALEA_PLACE(MCVersion.MC1_17), + BLOCK_FLOWERING_AZALEA_STEP(MCVersion.MC1_17), + BLOCK_HANGING_ROOTS_BREAK(MCVersion.MC1_17), + BLOCK_HANGING_ROOTS_FALL(MCVersion.MC1_17), + BLOCK_HANGING_ROOTS_HIT(MCVersion.MC1_17), + BLOCK_HANGING_ROOTS_PLACE(MCVersion.MC1_17), + BLOCK_HANGING_ROOTS_STEP(MCVersion.MC1_17), + BLOCK_LARGE_AMETHYST_BUD_BREAK(MCVersion.MC1_17), + BLOCK_LARGE_AMETHYST_BUD_PLACE(MCVersion.MC1_17), + BLOCK_MEDIUM_AMETHYST_BUD_BREAK(MCVersion.MC1_17), + BLOCK_MEDIUM_AMETHYST_BUD_PLACE(MCVersion.MC1_17), + BLOCK_MOSS_BREAK(MCVersion.MC1_17), + BLOCK_MOSS_CARPET_BREAK(MCVersion.MC1_17), + BLOCK_MOSS_CARPET_FALL(MCVersion.MC1_17), + BLOCK_MOSS_CARPET_HIT(MCVersion.MC1_17), + BLOCK_MOSS_CARPET_PLACE(MCVersion.MC1_17), + BLOCK_MOSS_CARPET_STEP(MCVersion.MC1_17), + BLOCK_MOSS_FALL(MCVersion.MC1_17), + BLOCK_MOSS_HIT(MCVersion.MC1_17), + BLOCK_MOSS_PLACE(MCVersion.MC1_17), + BLOCK_MOSS_STEP(MCVersion.MC1_17), + BLOCK_POINTED_DRIPSTONE_BREAK(MCVersion.MC1_17), + BLOCK_POINTED_DRIPSTONE_DRIP_LAVA(MCVersion.MC1_17), + BLOCK_POINTED_DRIPSTONE_DRIP_LAVA_INTO_CAULDRON(MCVersion.MC1_17), + BLOCK_POINTED_DRIPSTONE_DRIP_WATER(MCVersion.MC1_17), + BLOCK_POINTED_DRIPSTONE_DRIP_WATER_INTO_CAULDRON(MCVersion.MC1_17), + BLOCK_POINTED_DRIPSTONE_FALL(MCVersion.MC1_17), + BLOCK_POINTED_DRIPSTONE_HIT(MCVersion.MC1_17), + BLOCK_POINTED_DRIPSTONE_LAND(MCVersion.MC1_17), + BLOCK_POINTED_DRIPSTONE_PLACE(MCVersion.MC1_17), + BLOCK_POINTED_DRIPSTONE_STEP(MCVersion.MC1_17), + BLOCK_POLISHED_DEEPSLATE_BREAK(MCVersion.MC1_17), + BLOCK_POLISHED_DEEPSLATE_FALL(MCVersion.MC1_17), + BLOCK_POLISHED_DEEPSLATE_HIT(MCVersion.MC1_17), + BLOCK_POLISHED_DEEPSLATE_PLACE(MCVersion.MC1_17), + BLOCK_POLISHED_DEEPSLATE_STEP(MCVersion.MC1_17), + BLOCK_POWDER_SNOW_BREAK(MCVersion.MC1_17), + BLOCK_POWDER_SNOW_FALL(MCVersion.MC1_17), + BLOCK_POWDER_SNOW_HIT(MCVersion.MC1_17), + BLOCK_POWDER_SNOW_PLACE(MCVersion.MC1_17), + BLOCK_POWDER_SNOW_STEP(MCVersion.MC1_17), + BLOCK_ROOTED_DIRT_BREAK(MCVersion.MC1_17), + BLOCK_ROOTED_DIRT_FALL(MCVersion.MC1_17), + BLOCK_ROOTED_DIRT_HIT(MCVersion.MC1_17), + BLOCK_ROOTED_DIRT_PLACE(MCVersion.MC1_17), + BLOCK_ROOTED_DIRT_STEP(MCVersion.MC1_17), + BLOCK_SCULK_SENSOR_BREAK(MCVersion.MC1_17), + BLOCK_SCULK_SENSOR_CLICKING(MCVersion.MC1_17), + BLOCK_SCULK_SENSOR_CLICKING_STOP(MCVersion.MC1_17), + BLOCK_SCULK_SENSOR_FALL(MCVersion.MC1_17), + BLOCK_SCULK_SENSOR_HIT(MCVersion.MC1_17), + BLOCK_SCULK_SENSOR_PLACE(MCVersion.MC1_17), + BLOCK_SCULK_SENSOR_STEP(MCVersion.MC1_17), + BLOCK_SMALL_AMETHYST_BUD_BREAK(MCVersion.MC1_17), + BLOCK_SMALL_AMETHYST_BUD_PLACE(MCVersion.MC1_17), + BLOCK_SMALL_DRIPLEAF_BREAK(MCVersion.MC1_17), + BLOCK_SMALL_DRIPLEAF_FALL(MCVersion.MC1_17), + BLOCK_SMALL_DRIPLEAF_HIT(MCVersion.MC1_17), + BLOCK_SMALL_DRIPLEAF_PLACE(MCVersion.MC1_17), + BLOCK_SMALL_DRIPLEAF_STEP(MCVersion.MC1_17), + BLOCK_SPORE_BLOSSOM_BREAK(MCVersion.MC1_17), + BLOCK_SPORE_BLOSSOM_FALL(MCVersion.MC1_17), + BLOCK_SPORE_BLOSSOM_HIT(MCVersion.MC1_17), + BLOCK_SPORE_BLOSSOM_PLACE(MCVersion.MC1_17), + BLOCK_SPORE_BLOSSOM_STEP(MCVersion.MC1_17), + BLOCK_SWEET_BERRY_BUSH_PICK_BERRIES(MCVersion.MC1_17), + BLOCK_TUFF_BREAK(MCVersion.MC1_17), + BLOCK_TUFF_FALL(MCVersion.MC1_17), + BLOCK_TUFF_HIT(MCVersion.MC1_17), + BLOCK_TUFF_PLACE(MCVersion.MC1_17), + BLOCK_TUFF_STEP(MCVersion.MC1_17), + BLOCK_VINE_BREAK(MCVersion.MC1_17), + BLOCK_VINE_FALL(MCVersion.MC1_17), + BLOCK_VINE_HIT(MCVersion.MC1_17), + BLOCK_VINE_PLACE(MCVersion.MC1_17), + ENTITY_AXOLOTL_ATTACK(MCVersion.MC1_17), + ENTITY_AXOLOTL_DEATH(MCVersion.MC1_17), + ENTITY_AXOLOTL_HURT(MCVersion.MC1_17), + ENTITY_AXOLOTL_IDLE_AIR(MCVersion.MC1_17), + ENTITY_AXOLOTL_IDLE_WATER(MCVersion.MC1_17), + ENTITY_AXOLOTL_SPLASH(MCVersion.MC1_17), + ENTITY_AXOLOTL_SWIM(MCVersion.MC1_17), + ENTITY_GLOW_ITEM_FRAME_ADD_ITEM(MCVersion.MC1_17), + ENTITY_GLOW_ITEM_FRAME_BREAK(MCVersion.MC1_17), + ENTITY_GLOW_ITEM_FRAME_PLACE(MCVersion.MC1_17), + ENTITY_GLOW_ITEM_FRAME_REMOVE_ITEM(MCVersion.MC1_17), + ENTITY_GLOW_ITEM_FRAME_ROTATE_ITEM(MCVersion.MC1_17), + ENTITY_GLOW_SQUID_AMBIENT(MCVersion.MC1_17), + ENTITY_GLOW_SQUID_DEATH(MCVersion.MC1_17), + ENTITY_GLOW_SQUID_HURT(MCVersion.MC1_17), + ENTITY_GLOW_SQUID_SQUIRT(MCVersion.MC1_17), + ENTITY_GOAT_AMBIENT(MCVersion.MC1_17), + ENTITY_GOAT_DEATH(MCVersion.MC1_17), + ENTITY_GOAT_EAT(MCVersion.MC1_17), + ENTITY_GOAT_HURT(MCVersion.MC1_17), + ENTITY_GOAT_LONG_JUMP(MCVersion.MC1_17), + ENTITY_GOAT_MILK(MCVersion.MC1_17), + ENTITY_GOAT_PREPARE_RAM(MCVersion.MC1_17), + ENTITY_GOAT_RAM_IMPACT(MCVersion.MC1_17), + ENTITY_GOAT_SCREAMING_AMBIENT(MCVersion.MC1_17), + ENTITY_GOAT_SCREAMING_DEATH(MCVersion.MC1_17), + ENTITY_GOAT_SCREAMING_EAT(MCVersion.MC1_17), + ENTITY_GOAT_SCREAMING_HURT(MCVersion.MC1_17), + ENTITY_GOAT_SCREAMING_LONG_JUMP(MCVersion.MC1_17), + ENTITY_GOAT_SCREAMING_MILK(MCVersion.MC1_17), + ENTITY_GOAT_SCREAMING_PREPARE_RAM(MCVersion.MC1_17), + ENTITY_GOAT_SCREAMING_RAM_IMPACT(MCVersion.MC1_17), + ENTITY_GOAT_STEP(MCVersion.MC1_17), + ENTITY_MINECART_INSIDE_UNDERWATER(MCVersion.MC1_17), + ENTITY_PLAYER_HURT_FREEZE(MCVersion.MC1_17), + ENTITY_SKELETON_CONVERTED_TO_STRAY(MCVersion.MC1_17), + ITEM_AXE_SCRAPE(MCVersion.MC1_17), + ITEM_AXE_WAX_OFF(MCVersion.MC1_17), + ITEM_BONE_MEAL_USE(MCVersion.MC1_17), + ITEM_BUCKET_EMPTY_AXOLOTL(MCVersion.MC1_17), + ITEM_BUCKET_EMPTY_POWDER_SNOW(MCVersion.MC1_17), + ITEM_BUCKET_FILL_AXOLOTL(MCVersion.MC1_17), + ITEM_BUCKET_FILL_POWDER_SNOW(MCVersion.MC1_17), + ITEM_DYE_USE(MCVersion.MC1_17), + ITEM_GLOW_INK_SAC_USE(MCVersion.MC1_17), + ITEM_HONEYCOMB_WAX_ON(MCVersion.MC1_17), + ITEM_INK_SAC_USE(MCVersion.MC1_17), + ITEM_SPYGLASS_STOP_USING(MCVersion.MC1_17), + ITEM_SPYGLASS_USE(MCVersion.MC1_17), + + // 1.18 additions + BLOCK_GROWING_PLANT_CROP(MCVersion.MC1_18), + ITEM_BUNDLE_DROP_CONTENTS(MCVersion.MC1_18), + ITEM_BUNDLE_INSERT(MCVersion.MC1_18), + ITEM_BUNDLE_REMOVE_ONE(MCVersion.MC1_18), + MUSIC_DISC_OTHERSIDE(MCVersion.MC1_18), + MUSIC_OVERWORLD_DRIPSTONE_CAVES(MCVersion.MC1_18), + MUSIC_OVERWORLD_FROZEN_PEAKS(MCVersion.MC1_18), + MUSIC_OVERWORLD_GROVE(MCVersion.MC1_18), + MUSIC_OVERWORLD_JAGGED_PEAKS(MCVersion.MC1_18), + MUSIC_OVERWORLD_LUSH_CAVES(MCVersion.MC1_18), + MUSIC_OVERWORLD_MEADOW(MCVersion.MC1_18), + MUSIC_OVERWORLD_SNOWY_SLOPES(MCVersion.MC1_18), + MUSIC_OVERWORLD_STONY_PEAKS(MCVersion.MC1_18), + + // 1.19 additions + BLOCK_FROGLIGHT_BREAK(MCVersion.MC1_19), + BLOCK_FROGLIGHT_FALL(MCVersion.MC1_19), + BLOCK_FROGLIGHT_HIT(MCVersion.MC1_19), + BLOCK_FROGLIGHT_PLACE(MCVersion.MC1_19), + BLOCK_FROGLIGHT_STEP(MCVersion.MC1_19), + BLOCK_FROGSPAWN_BREAK(MCVersion.MC1_19), + BLOCK_FROGSPAWN_FALL(MCVersion.MC1_19), + BLOCK_FROGSPAWN_HATCH(MCVersion.MC1_19), + BLOCK_FROGSPAWN_HIT(MCVersion.MC1_19), + BLOCK_FROGSPAWN_PLACE(MCVersion.MC1_19), + BLOCK_FROGSPAWN_STEP(MCVersion.MC1_19), + BLOCK_MANGROVE_ROOTS_BREAK(MCVersion.MC1_19), + BLOCK_MANGROVE_ROOTS_FALL(MCVersion.MC1_19), + BLOCK_MANGROVE_ROOTS_HIT(MCVersion.MC1_19), + BLOCK_MANGROVE_ROOTS_PLACE(MCVersion.MC1_19), + BLOCK_MANGROVE_ROOTS_STEP(MCVersion.MC1_19), + BLOCK_MUDDY_MANGROVE_ROOTS_BREAK(MCVersion.MC1_19), + BLOCK_MUDDY_MANGROVE_ROOTS_FALL(MCVersion.MC1_19), + BLOCK_MUDDY_MANGROVE_ROOTS_HIT(MCVersion.MC1_19), + BLOCK_MUDDY_MANGROVE_ROOTS_PLACE(MCVersion.MC1_19), + BLOCK_MUDDY_MANGROVE_ROOTS_STEP(MCVersion.MC1_19), + BLOCK_MUD_BREAK(MCVersion.MC1_19), + BLOCK_MUD_BRICKS_BREAK(MCVersion.MC1_19), + BLOCK_MUD_BRICKS_FALL(MCVersion.MC1_19), + BLOCK_MUD_BRICKS_HIT(MCVersion.MC1_19), + BLOCK_MUD_BRICKS_PLACE(MCVersion.MC1_19), + BLOCK_MUD_BRICKS_STEP(MCVersion.MC1_19), + BLOCK_MUD_FALL(MCVersion.MC1_19), + BLOCK_MUD_HIT(MCVersion.MC1_19), + BLOCK_MUD_PLACE(MCVersion.MC1_19), + BLOCK_MUD_STEP(MCVersion.MC1_19), + BLOCK_PACKED_MUD_BREAK(MCVersion.MC1_19), + BLOCK_PACKED_MUD_FALL(MCVersion.MC1_19), + BLOCK_PACKED_MUD_HIT(MCVersion.MC1_19), + BLOCK_PACKED_MUD_PLACE(MCVersion.MC1_19), + BLOCK_PACKED_MUD_STEP(MCVersion.MC1_19), + BLOCK_SCULK_BREAK(MCVersion.MC1_19), + BLOCK_SCULK_CATALYST_BLOOM(MCVersion.MC1_19), + BLOCK_SCULK_CATALYST_BREAK(MCVersion.MC1_19), + BLOCK_SCULK_CATALYST_FALL(MCVersion.MC1_19), + BLOCK_SCULK_CATALYST_HIT(MCVersion.MC1_19), + BLOCK_SCULK_CATALYST_PLACE(MCVersion.MC1_19), + BLOCK_SCULK_CATALYST_STEP(MCVersion.MC1_19), + BLOCK_SCULK_CHARGE(MCVersion.MC1_19), + BLOCK_SCULK_FALL(MCVersion.MC1_19), + BLOCK_SCULK_HIT(MCVersion.MC1_19), + BLOCK_SCULK_PLACE(MCVersion.MC1_19), + BLOCK_SCULK_SHRIEKER_BREAK(MCVersion.MC1_19), + BLOCK_SCULK_SHRIEKER_FALL(MCVersion.MC1_19), + BLOCK_SCULK_SHRIEKER_HIT(MCVersion.MC1_19), + BLOCK_SCULK_SHRIEKER_PLACE(MCVersion.MC1_19), + BLOCK_SCULK_SHRIEKER_SHRIEK(MCVersion.MC1_19), + BLOCK_SCULK_SHRIEKER_STEP(MCVersion.MC1_19), + BLOCK_SCULK_SPREAD(MCVersion.MC1_19), + BLOCK_SCULK_STEP(MCVersion.MC1_19), + BLOCK_SCULK_VEIN_BREAK(MCVersion.MC1_19), + BLOCK_SCULK_VEIN_FALL(MCVersion.MC1_19), + BLOCK_SCULK_VEIN_HIT(MCVersion.MC1_19), + BLOCK_SCULK_VEIN_PLACE(MCVersion.MC1_19), + BLOCK_SCULK_VEIN_STEP(MCVersion.MC1_19), + ENTITY_ALLAY_AMBIENT_WITHOUT_ITEM(MCVersion.MC1_19), + ENTITY_ALLAY_AMBIENT_WITH_ITEM(MCVersion.MC1_19), + ENTITY_ALLAY_DEATH(MCVersion.MC1_19), + ENTITY_ALLAY_HURT(MCVersion.MC1_19), + ENTITY_ALLAY_ITEM_GIVEN(MCVersion.MC1_19), + ENTITY_ALLAY_ITEM_TAKEN(MCVersion.MC1_19), + ENTITY_ALLAY_ITEM_THROWN(MCVersion.MC1_19), + ENTITY_FROG_AMBIENT(MCVersion.MC1_19), + ENTITY_FROG_DEATH(MCVersion.MC1_19), + ENTITY_FROG_EAT(MCVersion.MC1_19), + ENTITY_FROG_HURT(MCVersion.MC1_19), + ENTITY_FROG_LAY_SPAWN(MCVersion.MC1_19), + ENTITY_FROG_LONG_JUMP(MCVersion.MC1_19), + ENTITY_FROG_STEP(MCVersion.MC1_19), + ENTITY_FROG_TONGUE(MCVersion.MC1_19), + ENTITY_GOAT_HORN_BREAK(MCVersion.MC1_19), + ENTITY_PARROT_IMITATE_WARDEN(MCVersion.MC1_19), + ENTITY_TADPOLE_DEATH(MCVersion.MC1_19), + ENTITY_TADPOLE_FLOP(MCVersion.MC1_19), + ENTITY_TADPOLE_GROW_UP(MCVersion.MC1_19), + ENTITY_TADPOLE_HURT(MCVersion.MC1_19), + ENTITY_WARDEN_AGITATED(MCVersion.MC1_19), + ENTITY_WARDEN_AMBIENT(MCVersion.MC1_19), + ENTITY_WARDEN_ANGRY(MCVersion.MC1_19), + ENTITY_WARDEN_ATTACK_IMPACT(MCVersion.MC1_19), + ENTITY_WARDEN_DEATH(MCVersion.MC1_19), + ENTITY_WARDEN_DIG(MCVersion.MC1_19), + ENTITY_WARDEN_EMERGE(MCVersion.MC1_19), + ENTITY_WARDEN_HEARTBEAT(MCVersion.MC1_19), + ENTITY_WARDEN_HURT(MCVersion.MC1_19), + ENTITY_WARDEN_LISTENING(MCVersion.MC1_19), + ENTITY_WARDEN_LISTENING_ANGRY(MCVersion.MC1_19), + ENTITY_WARDEN_NEARBY_CLOSE(MCVersion.MC1_19), + ENTITY_WARDEN_NEARBY_CLOSER(MCVersion.MC1_19), + ENTITY_WARDEN_NEARBY_CLOSEST(MCVersion.MC1_19), + ENTITY_WARDEN_ROAR(MCVersion.MC1_19), + ENTITY_WARDEN_SNIFF(MCVersion.MC1_19), + ENTITY_WARDEN_SONIC_BOOM(MCVersion.MC1_19), + ENTITY_WARDEN_SONIC_CHARGE(MCVersion.MC1_19), + ENTITY_WARDEN_STEP(MCVersion.MC1_19), + ENTITY_WARDEN_TENDRIL_CLICKS(MCVersion.MC1_19), + ITEM_BUCKET_EMPTY_TADPOLE(MCVersion.MC1_19), + ITEM_BUCKET_FILL_TADPOLE(MCVersion.MC1_19), + ITEM_GOAT_HORN_SOUND_0(MCVersion.MC1_19), + ITEM_GOAT_HORN_SOUND_1(MCVersion.MC1_19), + ITEM_GOAT_HORN_SOUND_2(MCVersion.MC1_19), + ITEM_GOAT_HORN_SOUND_3(MCVersion.MC1_19), + ITEM_GOAT_HORN_SOUND_4(MCVersion.MC1_19), + ITEM_GOAT_HORN_SOUND_5(MCVersion.MC1_19), + ITEM_GOAT_HORN_SOUND_6(MCVersion.MC1_19), + ITEM_GOAT_HORN_SOUND_7(MCVersion.MC1_19), + MUSIC_DISC_5(MCVersion.MC1_19), + MUSIC_OVERWORLD_DEEP_DARK(MCVersion.MC1_19), + MUSIC_OVERWORLD_JUNGLE_AND_FOREST(MCVersion.MC1_19, MCVersion.MC1_19_4), + MUSIC_OVERWORLD_OLD_GROWTH_TAIGA(MCVersion.MC1_19), + MUSIC_OVERWORLD_SWAMP(MCVersion.MC1_19), + + // 1.19.3 additions + BLOCK_BAMBOO_WOOD_BREAK(MCVersion.MC1_19_3), + BLOCK_BAMBOO_WOOD_BUTTON_CLICK_OFF(MCVersion.MC1_19_3), + BLOCK_BAMBOO_WOOD_BUTTON_CLICK_ON(MCVersion.MC1_19_3), + BLOCK_BAMBOO_WOOD_DOOR_CLOSE(MCVersion.MC1_19_3), + BLOCK_BAMBOO_WOOD_DOOR_OPEN(MCVersion.MC1_19_3), + BLOCK_BAMBOO_WOOD_FALL(MCVersion.MC1_19_3), + BLOCK_BAMBOO_WOOD_FENCE_GATE_CLOSE(MCVersion.MC1_19_3), + BLOCK_BAMBOO_WOOD_FENCE_GATE_OPEN(MCVersion.MC1_19_3), + BLOCK_BAMBOO_WOOD_HANGING_SIGN_BREAK(MCVersion.MC1_19_3), + BLOCK_BAMBOO_WOOD_HANGING_SIGN_FALL(MCVersion.MC1_19_3), + BLOCK_BAMBOO_WOOD_HANGING_SIGN_HIT(MCVersion.MC1_19_3), + BLOCK_BAMBOO_WOOD_HANGING_SIGN_PLACE(MCVersion.MC1_19_3), + BLOCK_BAMBOO_WOOD_HANGING_SIGN_STEP(MCVersion.MC1_19_3), + BLOCK_BAMBOO_WOOD_HIT(MCVersion.MC1_19_3), + BLOCK_BAMBOO_WOOD_PLACE(MCVersion.MC1_19_3), + BLOCK_BAMBOO_WOOD_PRESSURE_PLATE_CLICK_OFF(MCVersion.MC1_19_3), + BLOCK_BAMBOO_WOOD_PRESSURE_PLATE_CLICK_ON(MCVersion.MC1_19_3), + BLOCK_BAMBOO_WOOD_STEP(MCVersion.MC1_19_3), + BLOCK_BAMBOO_WOOD_TRAPDOOR_CLOSE(MCVersion.MC1_19_3), + BLOCK_BAMBOO_WOOD_TRAPDOOR_OPEN(MCVersion.MC1_19_3), + BLOCK_CHISELED_BOOKSHELF_BREAK(MCVersion.MC1_19_3), + BLOCK_CHISELED_BOOKSHELF_FALL(MCVersion.MC1_19_3), + BLOCK_CHISELED_BOOKSHELF_HIT(MCVersion.MC1_19_3), + BLOCK_CHISELED_BOOKSHELF_INSERT(MCVersion.MC1_19_3), + BLOCK_CHISELED_BOOKSHELF_INSERT_ENCHANTED(MCVersion.MC1_19_3), + BLOCK_CHISELED_BOOKSHELF_PICKUP(MCVersion.MC1_19_3), + BLOCK_CHISELED_BOOKSHELF_PICKUP_ENCHANTED(MCVersion.MC1_19_3), + BLOCK_CHISELED_BOOKSHELF_PLACE(MCVersion.MC1_19_3), + BLOCK_CHISELED_BOOKSHELF_STEP(MCVersion.MC1_19_3), + BLOCK_HANGING_SIGN_BREAK(MCVersion.MC1_19_3), + BLOCK_HANGING_SIGN_FALL(MCVersion.MC1_19_3), + BLOCK_HANGING_SIGN_HIT(MCVersion.MC1_19_3), + BLOCK_HANGING_SIGN_PLACE(MCVersion.MC1_19_3), + BLOCK_HANGING_SIGN_STEP(MCVersion.MC1_19_3), + BLOCK_NETHER_WOOD_BREAK(MCVersion.MC1_19_3), + BLOCK_NETHER_WOOD_BUTTON_CLICK_OFF(MCVersion.MC1_19_3), + BLOCK_NETHER_WOOD_BUTTON_CLICK_ON(MCVersion.MC1_19_3), + BLOCK_NETHER_WOOD_DOOR_CLOSE(MCVersion.MC1_19_3), + BLOCK_NETHER_WOOD_DOOR_OPEN(MCVersion.MC1_19_3), + BLOCK_NETHER_WOOD_FALL(MCVersion.MC1_19_3), + BLOCK_NETHER_WOOD_FENCE_GATE_CLOSE(MCVersion.MC1_19_3), + BLOCK_NETHER_WOOD_FENCE_GATE_OPEN(MCVersion.MC1_19_3), + BLOCK_NETHER_WOOD_HANGING_SIGN_BREAK(MCVersion.MC1_19_3), + BLOCK_NETHER_WOOD_HANGING_SIGN_FALL(MCVersion.MC1_19_3), + BLOCK_NETHER_WOOD_HANGING_SIGN_HIT(MCVersion.MC1_19_3), + BLOCK_NETHER_WOOD_HANGING_SIGN_PLACE(MCVersion.MC1_19_3), + BLOCK_NETHER_WOOD_HANGING_SIGN_STEP(MCVersion.MC1_19_3), + BLOCK_NETHER_WOOD_HIT(MCVersion.MC1_19_3), + BLOCK_NETHER_WOOD_PLACE(MCVersion.MC1_19_3), + BLOCK_NETHER_WOOD_PRESSURE_PLATE_CLICK_OFF(MCVersion.MC1_19_3), + BLOCK_NETHER_WOOD_PRESSURE_PLATE_CLICK_ON(MCVersion.MC1_19_3), + BLOCK_NETHER_WOOD_STEP(MCVersion.MC1_19_3), + BLOCK_NETHER_WOOD_TRAPDOOR_CLOSE(MCVersion.MC1_19_3), + BLOCK_NETHER_WOOD_TRAPDOOR_OPEN(MCVersion.MC1_19_3), + BLOCK_NOTE_BLOCK_IMITATE_CREEPER(MCVersion.MC1_19_3), + BLOCK_NOTE_BLOCK_IMITATE_ENDER_DRAGON(MCVersion.MC1_19_3), + BLOCK_NOTE_BLOCK_IMITATE_PIGLIN(MCVersion.MC1_19_3), + BLOCK_NOTE_BLOCK_IMITATE_SKELETON(MCVersion.MC1_19_3), + BLOCK_NOTE_BLOCK_IMITATE_WITHER_SKELETON(MCVersion.MC1_19_3), + BLOCK_NOTE_BLOCK_IMITATE_ZOMBIE(MCVersion.MC1_19_3), + ENTITY_CAMEL_AMBIENT(MCVersion.MC1_19_3), + ENTITY_CAMEL_DASH(MCVersion.MC1_19_3), + ENTITY_CAMEL_DASH_READY(MCVersion.MC1_19_3), + ENTITY_CAMEL_DEATH(MCVersion.MC1_19_3), + ENTITY_CAMEL_EAT(MCVersion.MC1_19_3), + ENTITY_CAMEL_HURT(MCVersion.MC1_19_3), + ENTITY_CAMEL_SADDLE(MCVersion.MC1_19_3), + ENTITY_CAMEL_SIT(MCVersion.MC1_19_3), + ENTITY_CAMEL_STAND(MCVersion.MC1_19_3), + ENTITY_CAMEL_STEP(MCVersion.MC1_19_3), + ENTITY_CAMEL_STEP_SAND(MCVersion.MC1_19_3), + + // 1.19.4 additions + BLOCK_CHERRY_LEAVES_BREAK(MCVersion.MC1_19_4), + BLOCK_CHERRY_LEAVES_FALL(MCVersion.MC1_19_4), + BLOCK_CHERRY_LEAVES_HIT(MCVersion.MC1_19_4), + BLOCK_CHERRY_LEAVES_PLACE(MCVersion.MC1_19_4), + BLOCK_CHERRY_LEAVES_STEP(MCVersion.MC1_19_4), + BLOCK_CHERRY_SAPLING_BREAK(MCVersion.MC1_19_4), + BLOCK_CHERRY_SAPLING_FALL(MCVersion.MC1_19_4), + BLOCK_CHERRY_SAPLING_HIT(MCVersion.MC1_19_4), + BLOCK_CHERRY_SAPLING_PLACE(MCVersion.MC1_19_4), + BLOCK_CHERRY_SAPLING_STEP(MCVersion.MC1_19_4), + BLOCK_CHERRY_WOOD_BREAK(MCVersion.MC1_19_4), + BLOCK_CHERRY_WOOD_BUTTON_CLICK_OFF(MCVersion.MC1_19_4), + BLOCK_CHERRY_WOOD_BUTTON_CLICK_ON(MCVersion.MC1_19_4), + BLOCK_CHERRY_WOOD_DOOR_CLOSE(MCVersion.MC1_19_4), + BLOCK_CHERRY_WOOD_DOOR_OPEN(MCVersion.MC1_19_4), + BLOCK_CHERRY_WOOD_FALL(MCVersion.MC1_19_4), + BLOCK_CHERRY_WOOD_FENCE_GATE_CLOSE(MCVersion.MC1_19_4), + BLOCK_CHERRY_WOOD_FENCE_GATE_OPEN(MCVersion.MC1_19_4), + BLOCK_CHERRY_WOOD_HANGING_SIGN_BREAK(MCVersion.MC1_19_4), + BLOCK_CHERRY_WOOD_HANGING_SIGN_FALL(MCVersion.MC1_19_4), + BLOCK_CHERRY_WOOD_HANGING_SIGN_HIT(MCVersion.MC1_19_4), + BLOCK_CHERRY_WOOD_HANGING_SIGN_PLACE(MCVersion.MC1_19_4), + BLOCK_CHERRY_WOOD_HANGING_SIGN_STEP(MCVersion.MC1_19_4), + BLOCK_CHERRY_WOOD_HIT(MCVersion.MC1_19_4), + BLOCK_CHERRY_WOOD_PLACE(MCVersion.MC1_19_4), + BLOCK_CHERRY_WOOD_PRESSURE_PLATE_CLICK_OFF(MCVersion.MC1_19_4), + BLOCK_CHERRY_WOOD_PRESSURE_PLATE_CLICK_ON(MCVersion.MC1_19_4), + BLOCK_CHERRY_WOOD_STEP(MCVersion.MC1_19_4), + BLOCK_CHERRY_WOOD_TRAPDOOR_CLOSE(MCVersion.MC1_19_4), + BLOCK_CHERRY_WOOD_TRAPDOOR_OPEN(MCVersion.MC1_19_4), + BLOCK_DECORATED_POT_BREAK(MCVersion.MC1_19_4), + BLOCK_DECORATED_POT_FALL(MCVersion.MC1_19_4), + BLOCK_DECORATED_POT_HIT(MCVersion.MC1_19_4), + BLOCK_DECORATED_POT_PLACE(MCVersion.MC1_19_4), + BLOCK_DECORATED_POT_SHATTER(MCVersion.MC1_19_4), + BLOCK_DECORATED_POT_STEP(MCVersion.MC1_19_4), + BLOCK_PINK_PETALS_BREAK(MCVersion.MC1_19_4), + BLOCK_PINK_PETALS_FALL(MCVersion.MC1_19_4), + BLOCK_PINK_PETALS_HIT(MCVersion.MC1_19_4), + BLOCK_PINK_PETALS_PLACE(MCVersion.MC1_19_4), + BLOCK_PINK_PETALS_STEP(MCVersion.MC1_19_4), + BLOCK_SUSPICIOUS_SAND_BREAK(MCVersion.MC1_19_4), + BLOCK_SUSPICIOUS_SAND_FALL(MCVersion.MC1_19_4), + BLOCK_SUSPICIOUS_SAND_HIT(MCVersion.MC1_19_4), + BLOCK_SUSPICIOUS_SAND_PLACE(MCVersion.MC1_19_4), + BLOCK_SUSPICIOUS_SAND_STEP(MCVersion.MC1_19_4), + ENTITY_SNIFFER_DEATH(MCVersion.MC1_19_4), + ENTITY_SNIFFER_DIGGING(MCVersion.MC1_19_4), + ENTITY_SNIFFER_DIGGING_STOP(MCVersion.MC1_19_4), + ENTITY_SNIFFER_DROP_SEED(MCVersion.MC1_19_4), + ENTITY_SNIFFER_EAT(MCVersion.MC1_19_4), + ENTITY_SNIFFER_HAPPY(MCVersion.MC1_19_4), + ENTITY_SNIFFER_HURT(MCVersion.MC1_19_4), + ENTITY_SNIFFER_IDLE(MCVersion.MC1_19_4), + ENTITY_SNIFFER_SCENTING(MCVersion.MC1_19_4), + ENTITY_SNIFFER_SEARCHING(MCVersion.MC1_19_4), + ENTITY_SNIFFER_SNIFFING(MCVersion.MC1_19_4), + ENTITY_SNIFFER_STEP(MCVersion.MC1_19_4), + INTENTIONALLY_EMPTY(MCVersion.MC1_19_4), + ITEM_BRUSH_BRUSHING(MCVersion.MC1_19_4, MCVersion.MC1_19_4), + ITEM_BRUSH_BRUSH_SAND_COMPLETED(MCVersion.MC1_19_4, MCVersion.MC1_19_4), + MUSIC_OVERWORLD_CHERRY_GROVE(MCVersion.MC1_19_4), + + // 1.20 additions + BLOCK_AMETHYST_BLOCK_RESONATE(MCVersion.MC1_20), + BLOCK_SIGN_WAXED_INTERACT_FAIL(MCVersion.MC1_20), + BLOCK_SNIFFER_EGG_CRACK(MCVersion.MC1_20), + BLOCK_SNIFFER_EGG_HATCH(MCVersion.MC1_20), + BLOCK_SNIFFER_EGG_PLOP(MCVersion.MC1_20), + BLOCK_SUSPICIOUS_GRAVEL_BREAK(MCVersion.MC1_20), + BLOCK_SUSPICIOUS_GRAVEL_FALL(MCVersion.MC1_20), + BLOCK_SUSPICIOUS_GRAVEL_HIT(MCVersion.MC1_20), + BLOCK_SUSPICIOUS_GRAVEL_PLACE(MCVersion.MC1_20), + BLOCK_SUSPICIOUS_GRAVEL_STEP(MCVersion.MC1_20), + ITEM_BRUSH_BRUSHING_GENERIC(MCVersion.MC1_20), + ITEM_BRUSH_BRUSHING_GRAVEL(MCVersion.MC1_20), + ITEM_BRUSH_BRUSHING_GRAVEL_COMPLETE(MCVersion.MC1_20), + ITEM_BRUSH_BRUSHING_SAND(MCVersion.MC1_20), + ITEM_BRUSH_BRUSHING_SAND_COMPLETE(MCVersion.MC1_20), + MUSIC_DISC_RELIC(MCVersion.MC1_20), + MUSIC_OVERWORLD_BADLANDS(MCVersion.MC1_20), + MUSIC_OVERWORLD_BAMBOO_JUNGLE(MCVersion.MC1_20), + MUSIC_OVERWORLD_DESERT(MCVersion.MC1_20), + MUSIC_OVERWORLD_FLOWER_FOREST(MCVersion.MC1_20), + MUSIC_OVERWORLD_FOREST(MCVersion.MC1_20), + MUSIC_OVERWORLD_JUNGLE(MCVersion.MC1_20), + MUSIC_OVERWORLD_SPARSE_JUNGLE(MCVersion.MC1_20), + + // 1.20.2 additions + BLOCK_SPONGE_ABSORB(MCVersion.MC1_20_2), + BLOCK_SPONGE_BREAK(MCVersion.MC1_20_2), + BLOCK_SPONGE_FALL(MCVersion.MC1_20_2), + BLOCK_SPONGE_HIT(MCVersion.MC1_20_2), + BLOCK_SPONGE_PLACE(MCVersion.MC1_20_2), + BLOCK_SPONGE_STEP(MCVersion.MC1_20_2), + BLOCK_WET_SPONGE_BREAK(MCVersion.MC1_20_2), + BLOCK_WET_SPONGE_FALL(MCVersion.MC1_20_2), + BLOCK_WET_SPONGE_HIT(MCVersion.MC1_20_2), + BLOCK_WET_SPONGE_PLACE(MCVersion.MC1_20_2), + BLOCK_WET_SPONGE_STEP(MCVersion.MC1_20_2), + + // 1.20.3 additions + BLOCK_COPPER_BULB_BREAK(MCVersion.MC1_20_4), + BLOCK_COPPER_BULB_FALL(MCVersion.MC1_20_4), + BLOCK_COPPER_BULB_HIT(MCVersion.MC1_20_4), + BLOCK_COPPER_BULB_PLACE(MCVersion.MC1_20_4), + BLOCK_COPPER_BULB_STEP(MCVersion.MC1_20_4), + BLOCK_COPPER_BULB_TURN_OFF(MCVersion.MC1_20_4), + BLOCK_COPPER_BULB_TURN_ON(MCVersion.MC1_20_4), + BLOCK_COPPER_DOOR_CLOSE(MCVersion.MC1_20_4), + BLOCK_COPPER_DOOR_OPEN(MCVersion.MC1_20_4), + BLOCK_COPPER_GRATE_BREAK(MCVersion.MC1_20_4), + BLOCK_COPPER_GRATE_FALL(MCVersion.MC1_20_4), + BLOCK_COPPER_GRATE_HIT(MCVersion.MC1_20_4), + BLOCK_COPPER_GRATE_PLACE(MCVersion.MC1_20_4), + BLOCK_COPPER_GRATE_STEP(MCVersion.MC1_20_4), + BLOCK_COPPER_TRAPDOOR_CLOSE(MCVersion.MC1_20_4), + BLOCK_COPPER_TRAPDOOR_OPEN(MCVersion.MC1_20_4), + BLOCK_CRAFTER_CRAFT(MCVersion.MC1_20_4), + BLOCK_CRAFTER_FAIL(MCVersion.MC1_20_4), + BLOCK_DECORATED_POT_INSERT(MCVersion.MC1_20_4), + BLOCK_DECORATED_POT_INSERT_FAIL(MCVersion.MC1_20_4), + BLOCK_HANGING_SIGN_WAXED_INTERACT_FAIL(MCVersion.MC1_20_4), + BLOCK_POLISHED_TUFF_BREAK(MCVersion.MC1_20_4), + BLOCK_POLISHED_TUFF_FALL(MCVersion.MC1_20_4), + BLOCK_POLISHED_TUFF_HIT(MCVersion.MC1_20_4), + BLOCK_POLISHED_TUFF_PLACE(MCVersion.MC1_20_4), + BLOCK_POLISHED_TUFF_STEP(MCVersion.MC1_20_4), + BLOCK_TRIAL_SPAWNER_AMBIENT(MCVersion.MC1_20_4), + BLOCK_TRIAL_SPAWNER_BREAK(MCVersion.MC1_20_4), + BLOCK_TRIAL_SPAWNER_CLOSE_SHUTTER(MCVersion.MC1_20_4), + BLOCK_TRIAL_SPAWNER_DETECT_PLAYER(MCVersion.MC1_20_4), + BLOCK_TRIAL_SPAWNER_EJECT_ITEM(MCVersion.MC1_20_4), + BLOCK_TRIAL_SPAWNER_FALL(MCVersion.MC1_20_4), + BLOCK_TRIAL_SPAWNER_HIT(MCVersion.MC1_20_4), + BLOCK_TRIAL_SPAWNER_OPEN_SHUTTER(MCVersion.MC1_20_4), + BLOCK_TRIAL_SPAWNER_PLACE(MCVersion.MC1_20_4), + BLOCK_TRIAL_SPAWNER_SPAWN_MOB(MCVersion.MC1_20_4), + BLOCK_TRIAL_SPAWNER_STEP(MCVersion.MC1_20_4), + BLOCK_TUFF_BRICKS_BREAK(MCVersion.MC1_20_4), + BLOCK_TUFF_BRICKS_FALL(MCVersion.MC1_20_4), + BLOCK_TUFF_BRICKS_HIT(MCVersion.MC1_20_4), + BLOCK_TUFF_BRICKS_PLACE(MCVersion.MC1_20_4), + BLOCK_TUFF_BRICKS_STEP(MCVersion.MC1_20_4), + ENTITY_BREEZE_DEATH(MCVersion.MC1_20_4), + ENTITY_BREEZE_HURT(MCVersion.MC1_20_4), + ENTITY_BREEZE_IDLE_AIR(MCVersion.MC1_20_4), + ENTITY_BREEZE_IDLE_GROUND(MCVersion.MC1_20_4), + ENTITY_BREEZE_INHALE(MCVersion.MC1_20_4), + ENTITY_BREEZE_JUMP(MCVersion.MC1_20_4), + ENTITY_BREEZE_LAND(MCVersion.MC1_20_4), + ENTITY_BREEZE_SHOOT(MCVersion.MC1_20_4), + ENTITY_BREEZE_SLIDE(MCVersion.MC1_20_4), + ENTITY_GENERIC_WIND_BURST(MCVersion.MC1_20_4, MCVersion.MC1_20_4), + ENTITY_PARROT_IMITATE_BREEZE(MCVersion.MC1_20_4), + ENTITY_PLAYER_TELEPORT(MCVersion.MC1_20_4), + + // 1.20.5 additions + BLOCK_COBWEB_BREAK(MCVersion.MC1_20_6), + BLOCK_COBWEB_FALL(MCVersion.MC1_20_6), + BLOCK_COBWEB_HIT(MCVersion.MC1_20_6), + BLOCK_COBWEB_PLACE(MCVersion.MC1_20_6), + BLOCK_COBWEB_STEP(MCVersion.MC1_20_6), + BLOCK_HEAVY_CORE_BREAK(MCVersion.MC1_20_6), + BLOCK_HEAVY_CORE_FALL(MCVersion.MC1_20_6), + BLOCK_HEAVY_CORE_HIT(MCVersion.MC1_20_6), + BLOCK_HEAVY_CORE_PLACE(MCVersion.MC1_20_6), + BLOCK_HEAVY_CORE_STEP(MCVersion.MC1_20_6), + BLOCK_TRIAL_SPAWNER_ABOUT_TO_SPAWN_ITEM(MCVersion.MC1_20_6), + BLOCK_TRIAL_SPAWNER_AMBIENT_CHARGED(MCVersion.MC1_20_6, MCVersion.MC1_20_6), + BLOCK_TRIAL_SPAWNER_CHARGE_ACTIVATE(MCVersion.MC1_20_6, MCVersion.MC1_20_6), + BLOCK_TRIAL_SPAWNER_SPAWN_ITEM(MCVersion.MC1_20_6), + BLOCK_TRIAL_SPAWNER_SPAWN_ITEM_BEGIN(MCVersion.MC1_20_6), + BLOCK_VAULT_ACTIVATE(MCVersion.MC1_20_6), + BLOCK_VAULT_AMBIENT(MCVersion.MC1_20_6), + BLOCK_VAULT_BREAK(MCVersion.MC1_20_6), + BLOCK_VAULT_CLOSE_SHUTTER(MCVersion.MC1_20_6), + BLOCK_VAULT_DEACTIVATE(MCVersion.MC1_20_6), + BLOCK_VAULT_EJECT_ITEM(MCVersion.MC1_20_6), + BLOCK_VAULT_FALL(MCVersion.MC1_20_6), + BLOCK_VAULT_HIT(MCVersion.MC1_20_6), + BLOCK_VAULT_INSERT_ITEM(MCVersion.MC1_20_6), + BLOCK_VAULT_INSERT_ITEM_FAIL(MCVersion.MC1_20_6), + BLOCK_VAULT_OPEN_SHUTTER(MCVersion.MC1_20_6), + BLOCK_VAULT_PLACE(MCVersion.MC1_20_6), + BLOCK_VAULT_STEP(MCVersion.MC1_20_6), + BLOCK_WET_SPONGE_DRIES(MCVersion.MC1_20_6), + ENTITY_ARMADILLO_AMBIENT(MCVersion.MC1_20_6), + ENTITY_ARMADILLO_BRUSH(MCVersion.MC1_20_6), + ENTITY_ARMADILLO_DEATH(MCVersion.MC1_20_6), + ENTITY_ARMADILLO_EAT(MCVersion.MC1_20_6), + ENTITY_ARMADILLO_HURT(MCVersion.MC1_20_6), + ENTITY_ARMADILLO_HURT_REDUCED(MCVersion.MC1_20_6), + ENTITY_ARMADILLO_LAND(MCVersion.MC1_20_6), + ENTITY_ARMADILLO_PEEK(MCVersion.MC1_20_6), + ENTITY_ARMADILLO_ROLL(MCVersion.MC1_20_6), + ENTITY_ARMADILLO_SCUTE_DROP(MCVersion.MC1_20_6), + ENTITY_ARMADILLO_STEP(MCVersion.MC1_20_6), + ENTITY_ARMADILLO_UNROLL_FINISH(MCVersion.MC1_20_6), + ENTITY_ARMADILLO_UNROLL_START(MCVersion.MC1_20_6), + ENTITY_BOGGED_AMBIENT(MCVersion.MC1_20_6), + ENTITY_BOGGED_DEATH(MCVersion.MC1_20_6), + ENTITY_BOGGED_HURT(MCVersion.MC1_20_6), + ENTITY_BOGGED_SHEAR(MCVersion.MC1_20_6), + ENTITY_BOGGED_STEP(MCVersion.MC1_20_6), + ENTITY_BREEZE_CHARGE(MCVersion.MC1_20_6), + ENTITY_BREEZE_DEFLECT(MCVersion.MC1_20_6), + ENTITY_BREEZE_WHIRL(MCVersion.MC1_20_6), + ENTITY_BREEZE_WIND_BURST(MCVersion.MC1_20_6), // changed from ENTITY_GENERIC_WIND_BURST + ENTITY_DONKEY_JUMP(MCVersion.MC1_20_6), + ENTITY_MULE_JUMP(MCVersion.MC1_20_6), + ENTITY_PARROT_IMITATE_BOGGED(MCVersion.MC1_20_6), + ENTITY_WIND_CHARGE_THROW(MCVersion.MC1_20_6), + ENTITY_WIND_CHARGE_WIND_BURST(MCVersion.MC1_20_6), + EVENT_MOB_EFFECT_BAD_OMEN(MCVersion.MC1_20_6), + EVENT_MOB_EFFECT_RAID_OMEN(MCVersion.MC1_20_6), + EVENT_MOB_EFFECT_TRIAL_OMEN(MCVersion.MC1_20_6), + ITEM_ARMOR_EQUIP_WOLF(MCVersion.MC1_20_6), + ITEM_ARMOR_UNEQUIP_WOLF(MCVersion.MC1_20_6), + ITEM_MACE_SMASH_AIR(MCVersion.MC1_20_6), + ITEM_MACE_SMASH_GROUND(MCVersion.MC1_20_6), + ITEM_MACE_SMASH_GROUND_HEAVY(MCVersion.MC1_20_6), + ITEM_WOLF_ARMOR_BREAK(MCVersion.MC1_20_6), + ITEM_WOLF_ARMOR_CRACK(MCVersion.MC1_20_6), + ITEM_WOLF_ARMOR_DAMAGE(MCVersion.MC1_20_6), + ITEM_WOLF_ARMOR_REPAIR(MCVersion.MC1_20_6), + + // 1.21 additions + BLOCK_TRIAL_SPAWNER_AMBIENT_OMINOUS(MCVersion.MC1_21), // changed from BLOCK_TRIAL_SPAWNER_AMBIENT_CHARGED + BLOCK_TRIAL_SPAWNER_OMINOUS_ACTIVATE(MCVersion.MC1_21), // changed from BLOCK_TRIAL_SPAWNER_CHARGE_ACTIVATE + BLOCK_VAULT_REJECT_REWARDED_PLAYER(MCVersion.MC1_21), + MUSIC_DISC_CREATOR(MCVersion.MC1_21), + MUSIC_DISC_CREATOR_MUSIC_BOX(MCVersion.MC1_21), + MUSIC_DISC_PRECIPICE(MCVersion.MC1_21), + + // 1.21.2 additions + BLOCK_CREAKING_HEART_BREAK(MCVersion.MC1_21_3), + BLOCK_CREAKING_HEART_FALL(MCVersion.MC1_21_3), + BLOCK_CREAKING_HEART_HIT(MCVersion.MC1_21_3), + BLOCK_CREAKING_HEART_HURT(MCVersion.MC1_21_3), + BLOCK_CREAKING_HEART_IDLE(MCVersion.MC1_21_3), + BLOCK_CREAKING_HEART_PLACE(MCVersion.MC1_21_3), + BLOCK_CREAKING_HEART_SPAWN(MCVersion.MC1_21_3), + BLOCK_CREAKING_HEART_STEP(MCVersion.MC1_21_3), + BLOCK_PALE_HANGING_MOSS_IDLE(MCVersion.MC1_21_3), + BLOCK_SPAWNER_BREAK(MCVersion.MC1_21_3), + BLOCK_SPAWNER_FALL(MCVersion.MC1_21_3), + BLOCK_SPAWNER_HIT(MCVersion.MC1_21_3), + BLOCK_SPAWNER_PLACE(MCVersion.MC1_21_3), + BLOCK_SPAWNER_STEP(MCVersion.MC1_21_3), + ENTITY_CREAKING_ACTIVATE(MCVersion.MC1_21_3), + ENTITY_CREAKING_AMBIENT(MCVersion.MC1_21_3), + ENTITY_CREAKING_ATTACK(MCVersion.MC1_21_3), + ENTITY_CREAKING_DEACTIVATE(MCVersion.MC1_21_3), + ENTITY_CREAKING_DEATH(MCVersion.MC1_21_3), + ENTITY_CREAKING_FREEZE(MCVersion.MC1_21_3), + ENTITY_CREAKING_SPAWN(MCVersion.MC1_21_3), + ENTITY_CREAKING_STEP(MCVersion.MC1_21_3), + ENTITY_CREAKING_SWAY(MCVersion.MC1_21_3), + ENTITY_CREAKING_UNFREEZE(MCVersion.MC1_21_3), + ENTITY_PARROT_IMITATE_CREAKING(MCVersion.MC1_21_3), + ITEM_BUNDLE_INSERT_FAIL(MCVersion.MC1_21_3), + UI_HUD_BUBBLE_POP(MCVersion.MC1_21_3), + + // 1.21.4 additions + BLOCK_EYEBLOSSOM_CLOSE(MCVersion.MC1_21_4), + BLOCK_EYEBLOSSOM_CLOSE_LONG(MCVersion.MC1_21_4), + BLOCK_EYEBLOSSOM_IDLE(MCVersion.MC1_21_4), + BLOCK_EYEBLOSSOM_OPEN(MCVersion.MC1_21_4), + BLOCK_EYEBLOSSOM_OPEN_LONG(MCVersion.MC1_21_4), + BLOCK_RESIN_BREAK(MCVersion.MC1_21_4), + BLOCK_RESIN_BRICKS_BREAK(MCVersion.MC1_21_4), + BLOCK_RESIN_BRICKS_FALL(MCVersion.MC1_21_4), + BLOCK_RESIN_BRICKS_HIT(MCVersion.MC1_21_4), + BLOCK_RESIN_BRICKS_PLACE(MCVersion.MC1_21_4), + BLOCK_RESIN_BRICKS_STEP(MCVersion.MC1_21_4), + BLOCK_RESIN_FALL(MCVersion.MC1_21_4), + BLOCK_RESIN_PLACE(MCVersion.MC1_21_4), + BLOCK_RESIN_STEP(MCVersion.MC1_21_4), + ENTITY_CREAKING_TWITCH(MCVersion.MC1_21_4), + + // 1.21.5 additions + BLOCK_CACTUS_FLOWER_BREAK(MCVersion.MC1_21_5), + BLOCK_CACTUS_FLOWER_PLACE(MCVersion.MC1_21_5), + BLOCK_DEADBUSH_IDLE(MCVersion.MC1_21_5), + BLOCK_FIREFLY_BUSH_IDLE(MCVersion.MC1_21_5), + BLOCK_IRON_BREAK(MCVersion.MC1_21_5), + BLOCK_IRON_FALL(MCVersion.MC1_21_5), + BLOCK_IRON_HIT(MCVersion.MC1_21_5), + BLOCK_IRON_PLACE(MCVersion.MC1_21_5), + BLOCK_IRON_STEP(MCVersion.MC1_21_5), + BLOCK_LEAF_LITTER_BREAK(MCVersion.MC1_21_5), + BLOCK_LEAF_LITTER_STEP(MCVersion.MC1_21_5), + BLOCK_LEAF_LITTER_PLACE(MCVersion.MC1_21_5), + BLOCK_LEAF_LITTER_HIT(MCVersion.MC1_21_5), + BLOCK_LEAF_LITTER_FALL(MCVersion.MC1_21_5), + BLOCK_SAND_IDLE(MCVersion.MC1_21_5), + BLOCK_SAND_WIND(MCVersion.MC1_21_5, MCVersion.MC1_21_5), + ENTITY_WOLF_ANGRY_AMBIENT(MCVersion.MC1_21_5), + ENTITY_WOLF_ANGRY_DEATH(MCVersion.MC1_21_5), + ENTITY_WOLF_ANGRY_GROWL(MCVersion.MC1_21_5), + ENTITY_WOLF_ANGRY_HURT(MCVersion.MC1_21_5), + ENTITY_WOLF_ANGRY_PANT(MCVersion.MC1_21_5), + ENTITY_WOLF_ANGRY_WHINE(MCVersion.MC1_21_5), + ENTITY_WOLF_BIG_AMBIENT(MCVersion.MC1_21_5), + ENTITY_WOLF_BIG_DEATH(MCVersion.MC1_21_5), + ENTITY_WOLF_BIG_GROWL(MCVersion.MC1_21_5), + ENTITY_WOLF_BIG_HURT(MCVersion.MC1_21_5), + ENTITY_WOLF_BIG_PANT(MCVersion.MC1_21_5), + ENTITY_WOLF_BIG_WHINE(MCVersion.MC1_21_5), + ENTITY_WOLF_CUTE_AMBIENT(MCVersion.MC1_21_5), + ENTITY_WOLF_CUTE_DEATH(MCVersion.MC1_21_5), + ENTITY_WOLF_CUTE_GROWL(MCVersion.MC1_21_5), + ENTITY_WOLF_CUTE_HURT(MCVersion.MC1_21_5), + ENTITY_WOLF_CUTE_PANT(MCVersion.MC1_21_5), + ENTITY_WOLF_CUTE_WHINE(MCVersion.MC1_21_5), + ENTITY_WOLF_GRUMPY_AMBIENT(MCVersion.MC1_21_5), + ENTITY_WOLF_GRUMPY_DEATH(MCVersion.MC1_21_5), + ENTITY_WOLF_GRUMPY_GROWL(MCVersion.MC1_21_5), + ENTITY_WOLF_GRUMPY_HURT(MCVersion.MC1_21_5), + ENTITY_WOLF_GRUMPY_PANT(MCVersion.MC1_21_5), + ENTITY_WOLF_GRUMPY_WHINE(MCVersion.MC1_21_5), + ENTITY_WOLF_PUGLIN_AMBIENT(MCVersion.MC1_21_5), + ENTITY_WOLF_PUGLIN_DEATH(MCVersion.MC1_21_5), + ENTITY_WOLF_PUGLIN_GROWL(MCVersion.MC1_21_5), + ENTITY_WOLF_PUGLIN_HURT(MCVersion.MC1_21_5), + ENTITY_WOLF_PUGLIN_PANT(MCVersion.MC1_21_5), + ENTITY_WOLF_PUGLIN_WHINE(MCVersion.MC1_21_5), + ENTITY_WOLF_SAD_AMBIENT(MCVersion.MC1_21_5), + ENTITY_WOLF_SAD_DEATH(MCVersion.MC1_21_5), + ENTITY_WOLF_SAD_GROWL(MCVersion.MC1_21_5), + ENTITY_WOLF_SAD_HURT(MCVersion.MC1_21_5), + ENTITY_WOLF_SAD_PANT(MCVersion.MC1_21_5), + ENTITY_WOLF_SAD_WHINE(MCVersion.MC1_21_5), + + // 1.21.6 additions + BLOCK_DRIED_GHAST_AMBIENT(MCVersion.MC1_21_6), + BLOCK_DRIED_GHAST_AMBIENT_WATER(MCVersion.MC1_21_6), + BLOCK_DRIED_GHAST_BREAK(MCVersion.MC1_21_6), + BLOCK_DRIED_GHAST_FALL(MCVersion.MC1_21_6), + BLOCK_DRIED_GHAST_PLACE(MCVersion.MC1_21_6), + BLOCK_DRIED_GHAST_PLACE_IN_WATER(MCVersion.MC1_21_6), + BLOCK_DRIED_GHAST_STEP(MCVersion.MC1_21_6), + BLOCK_DRIED_GHAST_TRANSITION(MCVersion.MC1_21_6), + BLOCK_DRY_GRASS_AMBIENT(MCVersion.MC1_21_6), + ENTITY_GHASTLING_AMBIENT(MCVersion.MC1_21_6), + ENTITY_GHASTLING_DEATH(MCVersion.MC1_21_6), + ENTITY_GHASTLING_HURT(MCVersion.MC1_21_6), + ENTITY_GHASTLING_SPAWN(MCVersion.MC1_21_6), + ENTITY_HAPPY_GHAST_AMBIENT(MCVersion.MC1_21_6), + ENTITY_HAPPY_GHAST_DEATH(MCVersion.MC1_21_6), + ENTITY_HAPPY_GHAST_EQUIP(MCVersion.MC1_21_6), + ENTITY_HAPPY_GHAST_HARNESS_GOGGLES_DOWN(MCVersion.MC1_21_6), + ENTITY_HAPPY_GHAST_HARNESS_GOGGLES_UP(MCVersion.MC1_21_6), + ENTITY_HAPPY_GHAST_HURT(MCVersion.MC1_21_6), + ENTITY_HAPPY_GHAST_RIDING(MCVersion.MC1_21_6), + ENTITY_HAPPY_GHAST_UNEQUIP(MCVersion.MC1_21_6), + ITEM_HORSE_ARMOR_UNEQUIP(MCVersion.MC1_21_6), + ITEM_LEAD_BREAK(MCVersion.MC1_21_6), + ITEM_LEAD_TIED(MCVersion.MC1_21_6), + ITEM_LEAD_UNTIED(MCVersion.MC1_21_6), + ITEM_LLAMA_CARPET_UNEQUIP(MCVersion.MC1_21_6), + ITEM_SADDLE_UNEQUIP(MCVersion.MC1_21_6), + ITEM_SHEARS_SNIP(MCVersion.MC1_21_6), + MUSIC_DISC_TEARS(MCVersion.MC1_21_6), + + // 1.21.7 additions + MUSIC_DISC_LAVA_CHICKEN(MCVersion.MC1_21_7), + + // 1.21.9 additions + BLOCK_COPPER_CHEST_CLOSE(MCVersion.MC1_21_9), + BLOCK_COPPER_CHEST_OPEN(MCVersion.MC1_21_9), + BLOCK_COPPER_CHEST_OXIDIZED_CLOSE(MCVersion.MC1_21_9), + BLOCK_COPPER_CHEST_OXIDIZED_OPEN(MCVersion.MC1_21_9), + BLOCK_COPPER_CHEST_WEATHERED_CLOSE(MCVersion.MC1_21_9), + BLOCK_COPPER_CHEST_WEATHERED_OPEN(MCVersion.MC1_21_9), + BLOCK_COPPER_GOLEM_STATUE_BREAK(MCVersion.MC1_21_9), + BLOCK_COPPER_GOLEM_STATUE_FALL(MCVersion.MC1_21_9), + BLOCK_COPPER_GOLEM_STATUE_HIT(MCVersion.MC1_21_9), + BLOCK_COPPER_GOLEM_STATUE_PLACE(MCVersion.MC1_21_9), + BLOCK_COPPER_GOLEM_STATUE_STEP(MCVersion.MC1_21_9), + BLOCK_SHELF_ACTIVATE(MCVersion.MC1_21_9), + BLOCK_SHELF_BREAK(MCVersion.MC1_21_9), + BLOCK_SHELF_DEACTIVATE(MCVersion.MC1_21_9), + BLOCK_SHELF_FALL(MCVersion.MC1_21_9), + BLOCK_SHELF_HIT(MCVersion.MC1_21_9), + BLOCK_SHELF_MULTI_SWAP(MCVersion.MC1_21_9), + BLOCK_SHELF_PLACE(MCVersion.MC1_21_9), + BLOCK_SHELF_PLACE_ITEM(MCVersion.MC1_21_9), + BLOCK_SHELF_SINGLE_SWAP(MCVersion.MC1_21_9), + BLOCK_SHELF_STEP(MCVersion.MC1_21_9), + BLOCK_SHELF_TAKE_ITEM(MCVersion.MC1_21_9), + ENTITY_COPPER_GOLEM_DEATH(MCVersion.MC1_21_9), + ENTITY_COPPER_GOLEM_HURT(MCVersion.MC1_21_9), + ENTITY_COPPER_GOLEM_ITEM_DROP(MCVersion.MC1_21_9), + ENTITY_COPPER_GOLEM_ITEM_NO_DROP(MCVersion.MC1_21_9), + ENTITY_COPPER_GOLEM_NO_ITEM_GET(MCVersion.MC1_21_9), + ENTITY_COPPER_GOLEM_NO_ITEM_NO_GET(MCVersion.MC1_21_9), + ENTITY_COPPER_GOLEM_SHEAR(MCVersion.MC1_21_9), + ENTITY_COPPER_GOLEM_SPAWN(MCVersion.MC1_21_9), + ENTITY_COPPER_GOLEM_SPIN(MCVersion.MC1_21_9), + ENTITY_COPPER_GOLEM_STEP(MCVersion.MC1_21_9), + ENTITY_COPPER_GOLEM_BECOME_STATUE(MCVersion.MC1_21_9), + ENTITY_COPPER_GOLEM_OXIDIZED_DEATH(MCVersion.MC1_21_9), + ENTITY_COPPER_GOLEM_OXIDIZED_HURT(MCVersion.MC1_21_9), + ENTITY_COPPER_GOLEM_OXIDIZED_SPIN(MCVersion.MC1_21_9), + ENTITY_COPPER_GOLEM_OXIDIZED_STEP(MCVersion.MC1_21_9), + ENTITY_COPPER_GOLEM_WEATHERED_DEATH(MCVersion.MC1_21_9), + ENTITY_COPPER_GOLEM_WEATHERED_HURT(MCVersion.MC1_21_9), + ENTITY_COPPER_GOLEM_WEATHERED_SPIN(MCVersion.MC1_21_9), + ENTITY_COPPER_GOLEM_WEATHERED_STEP(MCVersion.MC1_21_9), + ITEM_ARMOR_EQUIP_COPPER(MCVersion.MC1_21_9), + WEATHER_END_FLASH(MCVersion.MC1_21_9), + + // 1.21.11 additions + ENTITY_BABY_NAUTILUS_AMBIENT(MCVersion.MC1_21_11), + ENTITY_BABY_NAUTILUS_AMBIENT_LAND(MCVersion.MC1_21_11), + ENTITY_BABY_NAUTILUS_DEATH(MCVersion.MC1_21_11), + ENTITY_BABY_NAUTILUS_DEATH_LAND(MCVersion.MC1_21_11), + ENTITY_BABY_NAUTILUS_EAT(MCVersion.MC1_21_11), + ENTITY_BABY_NAUTILUS_HURT(MCVersion.MC1_21_11), + ENTITY_BABY_NAUTILUS_HURT_LAND(MCVersion.MC1_21_11), + ENTITY_BABY_NAUTILUS_SWIM(MCVersion.MC1_21_11), + ENTITY_CAMEL_HUSK_AMBIENT(MCVersion.MC1_21_11), + ENTITY_CAMEL_HUSK_DASH(MCVersion.MC1_21_11), + ENTITY_CAMEL_HUSK_DASH_READY(MCVersion.MC1_21_11), + ENTITY_CAMEL_HUSK_DEATH(MCVersion.MC1_21_11), + ENTITY_CAMEL_HUSK_EAT(MCVersion.MC1_21_11), + ENTITY_CAMEL_HUSK_HURT(MCVersion.MC1_21_11), + ENTITY_CAMEL_HUSK_SADDLE(MCVersion.MC1_21_11), + ENTITY_CAMEL_HUSK_SIT(MCVersion.MC1_21_11), + ENTITY_CAMEL_HUSK_STAND(MCVersion.MC1_21_11), + ENTITY_CAMEL_HUSK_STEP(MCVersion.MC1_21_11), + ENTITY_CAMEL_HUSK_STEP_SAND(MCVersion.MC1_21_11), + ENTITY_NAUTILUS_AMBIENT(MCVersion.MC1_21_11), + ENTITY_NAUTILUS_AMBIENT_LAND(MCVersion.MC1_21_11), + ENTITY_NAUTILUS_DASH(MCVersion.MC1_21_11), + ENTITY_NAUTILUS_DASH_LAND(MCVersion.MC1_21_11), + ENTITY_NAUTILUS_DASH_READY(MCVersion.MC1_21_11), + ENTITY_NAUTILUS_DASH_READY_LAND(MCVersion.MC1_21_11), + ENTITY_NAUTILUS_DEATH(MCVersion.MC1_21_11), + ENTITY_NAUTILUS_DEATH_LAND(MCVersion.MC1_21_11), + ENTITY_NAUTILUS_EAT(MCVersion.MC1_21_11), + ENTITY_NAUTILUS_HURT(MCVersion.MC1_21_11), + ENTITY_NAUTILUS_HURT_LAND(MCVersion.MC1_21_11), + ENTITY_NAUTILUS_RIDING(MCVersion.MC1_21_11), + ENTITY_NAUTILUS_SWIM(MCVersion.MC1_21_11), + ENTITY_PARCHED_AMBIENT(MCVersion.MC1_21_11), + ENTITY_PARCHED_DEATH(MCVersion.MC1_21_11), + ENTITY_PARCHED_HURT(MCVersion.MC1_21_11), + ENTITY_PARCHED_STEP(MCVersion.MC1_21_11), + ENTITY_PARROT_IMITATE_CAMEL_HUSK(MCVersion.MC1_21_11), + ENTITY_PARROT_IMITATE_PARCHED(MCVersion.MC1_21_11), + ENTITY_PARROT_IMITATE_ZOMBIE_HORSE(MCVersion.MC1_21_11), + ENTITY_PARROT_IMITATE_ZOMBIE_NAUTILUS(MCVersion.MC1_21_11), + ENTITY_ZOMBIE_HORSE_ANGRY(MCVersion.MC1_21_11), + ENTITY_ZOMBIE_HORSE_EAT(MCVersion.MC1_21_11), + ENTITY_ZOMBIE_NAUTILUS_AMBIENT(MCVersion.MC1_21_11), + ENTITY_ZOMBIE_NAUTILUS_AMBIENT_LAND(MCVersion.MC1_21_11), + ENTITY_ZOMBIE_NAUTILUS_DASH(MCVersion.MC1_21_11), + ENTITY_ZOMBIE_NAUTILUS_DASH_LAND(MCVersion.MC1_21_11), + ENTITY_ZOMBIE_NAUTILUS_DASH_READY(MCVersion.MC1_21_11), + ENTITY_ZOMBIE_NAUTILUS_DASH_READY_LAND(MCVersion.MC1_21_11), + ENTITY_ZOMBIE_NAUTILUS_DEATH(MCVersion.MC1_21_11), + ENTITY_ZOMBIE_NAUTILUS_DEATH_LAND(MCVersion.MC1_21_11), + ENTITY_ZOMBIE_NAUTILUS_EAT(MCVersion.MC1_21_11), + ENTITY_ZOMBIE_NAUTILUS_HURT(MCVersion.MC1_21_11), + ENTITY_ZOMBIE_NAUTILUS_HURT_LAND(MCVersion.MC1_21_11), + ENTITY_ZOMBIE_NAUTILUS_SWIM(MCVersion.MC1_21_11), + ITEM_ARMOR_EQUIP_NAUTILUS(MCVersion.MC1_21_11), + ITEM_ARMOR_UNEQUIP_NAUTILUS(MCVersion.MC1_21_11), + ITEM_NAUTILUS_SADDLE_EQUIP(MCVersion.MC1_21_11), + ITEM_NAUTILUS_SADDLE_UNDERWATER_EQUIP(MCVersion.MC1_21_11), + ITEM_SPEAR_ATTACK(MCVersion.MC1_21_11), + ITEM_SPEAR_HIT(MCVersion.MC1_21_11), + ITEM_SPEAR_LUNGE_1(MCVersion.MC1_21_11), + ITEM_SPEAR_LUNGE_2(MCVersion.MC1_21_11), + ITEM_SPEAR_LUNGE_3(MCVersion.MC1_21_11), + ITEM_SPEAR_USE(MCVersion.MC1_21_11), + ITEM_SPEAR_WOOD_ATTACK(MCVersion.MC1_21_11), + ITEM_SPEAR_WOOD_HIT(MCVersion.MC1_21_11), + ITEM_SPEAR_WOOD_USE(MCVersion.MC1_21_11), + + UNKNOWN(MCVersion.NEVER); + + private final MCVersion since; + private final MCVersion until; + + MCVanillaSound() { + this(MCVersion.MC1_9); + } + + MCVanillaSound(MCVersion since) { + this.since = since; + this.until = MCVersion.FUTURE; + } + + MCVanillaSound(MCVersion since, MCVersion until) { + this.since = since; + this.until = until; + } + + public boolean existsIn(MCVersion version) { + return version.gte(since) && version.lte(until); + } + } } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCSoundCategory.java b/src/main/java/com/laytonsmith/abstraction/enums/MCSoundCategory.java new file mode 100644 index 0000000000..53d0934dc5 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCSoundCategory.java @@ -0,0 +1,18 @@ +package com.laytonsmith.abstraction.enums; + +import com.laytonsmith.annotations.MEnum; + +@MEnum("com.commandhelper.SoundCategory") +public enum MCSoundCategory { + MASTER, + MUSIC, + RECORDS, + WEATHER, + BLOCKS, + HOSTILE, + NEUTRAL, + PLAYERS, + AMBIENT, + VOICE, + UI +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCSpawnReason.java b/src/main/java/com/laytonsmith/abstraction/enums/MCSpawnReason.java index 0ae4cdbd78..5b90893f3e 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCSpawnReason.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCSpawnReason.java @@ -2,18 +2,19 @@ import com.laytonsmith.annotations.MEnum; -/** - * - * @author jb_aero - */ -@MEnum("SpawnReason") +@MEnum("com.commandhelper.SpawnReason") public enum MCSpawnReason { - BED, + BEEHIVE, BREEDING, + BUCKET, + BUILD_COPPERGOLEM, BUILD_IRONGOLEM, BUILD_SNOWMAN, BUILD_WITHER, - CHUNK_GEN, + /** + * Spawned by vanilla /summon command + */ + COMMAND, /** * Spawned by plugins */ @@ -29,7 +30,11 @@ public enum MCSpawnReason { JOCKEY, LIGHTNING, NATURAL, + PATROL, + PIGLIN_ZOMBIFIED, + RAID, REINFORCEMENTS, + SHOULDER_ENTITY, SLIME_SPLIT, SPAWNER, SPAWNER_EGG, @@ -42,4 +47,33 @@ public enum MCSpawnReason { OCELOT_BABY, SILVERFISH_BLOCK, MOUNT, + TRAP, + ENDER_PEARL, + DROWNED, + SHEARED, + EXPLOSION, + FROZEN, + SPELL, + METAMORPHOSIS, + DUPLICATION, + ENCHANTMENT, + TRIAL_SPAWNER, + POTION_EFFECT, + REHYDRATION("Paper"), // Happy Ghast + REANIMATE; // Copper Golem + + final String mcImplementation; + + MCSpawnReason() { + this.mcImplementation = null; + } + + MCSpawnReason(String mcImplementation) { + this.mcImplementation = mcImplementation; + } + + @Deprecated + public String getImplementation() { + return this.mcImplementation; + } } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCTagType.java b/src/main/java/com/laytonsmith/abstraction/enums/MCTagType.java new file mode 100644 index 0000000000..6d27e95ff6 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCTagType.java @@ -0,0 +1,222 @@ +package com.laytonsmith.abstraction.enums; + +import com.laytonsmith.abstraction.MCNamespacedKey; +import com.laytonsmith.abstraction.MCTagContainer; +import com.laytonsmith.abstraction.StaticLayer; +import com.laytonsmith.core.ArgumentValidation; +import com.laytonsmith.core.constructs.CArray; +import com.laytonsmith.core.constructs.CDouble; +import com.laytonsmith.core.constructs.CInt; +import com.laytonsmith.core.constructs.CString; +import com.laytonsmith.core.constructs.Target; +import com.laytonsmith.core.exceptions.CRE.CRECastException; +import com.laytonsmith.core.exceptions.CRE.CREFormatException; +import com.laytonsmith.core.natives.interfaces.Mixed; + +import java.util.function.Function; + +/** + * Minecraft NBT types, with functions to convert to and from MethodScript constructs. + */ +public enum MCTagType { + BYTE( + (Mixed v) -> ArgumentValidation.getInt8(v, v.getTarget()), + (Byte v) -> new CInt(((Number) v).longValue(), Target.UNKNOWN)), + BYTE_ARRAY( + (Mixed v) -> { + CArray array = ArgumentValidation.getArray(v, v.getTarget()); + if(array.isAssociative()) { + throw new CRECastException("Expected byte array to not be associative.", v.getTarget()); + } + byte[] bytes = new byte[(int) array.size()]; + int i = 0; + for(Mixed m : array) { + bytes[i++] = ArgumentValidation.getInt8(m, m.getTarget()); + } + return bytes; + }, + (byte[] array) -> { + CArray r = new CArray(Target.UNKNOWN); + for(int i : array) { + r.push(new CInt(i, Target.UNKNOWN), Target.UNKNOWN); + } + return r; + }), + DOUBLE( + (Mixed v) -> ArgumentValidation.getDouble(v, v.getTarget()), + (Double v) -> new CDouble(v, Target.UNKNOWN)), + FLOAT( + (Mixed v) -> ArgumentValidation.getDouble32(v, v.getTarget()), + (Float v) -> new CDouble(v.doubleValue(), Target.UNKNOWN)), + INTEGER( + (Mixed v) -> ArgumentValidation.getInt32(v, v.getTarget()), + (Integer v) -> new CInt(((Number) v).longValue(), Target.UNKNOWN)), + INTEGER_ARRAY( + (Mixed v) -> { + CArray array = ArgumentValidation.getArray(v, v.getTarget()); + if(array.isAssociative()) { + throw new CRECastException("Expected integer array to not be associative.", v.getTarget()); + } + int[] ints = new int[(int) array.size()]; + int i = 0; + for(Mixed m : array) { + ints[i++] = ArgumentValidation.getInt32(m, m.getTarget()); + } + return ints; + }, + (int[] array) -> { + CArray r = new CArray(Target.UNKNOWN); + for(int i : array) { + r.push(new CInt(i, Target.UNKNOWN), Target.UNKNOWN); + } + return r; + }), + LONG( + (Mixed v) -> ArgumentValidation.getInt(v, v.getTarget()), + (Long v) -> new CInt(((Number) v).longValue(), Target.UNKNOWN)), + LONG_ARRAY( + (Mixed v) -> { + CArray array = ArgumentValidation.getArray(v, v.getTarget()); + if(array.isAssociative()) { + throw new CRECastException("Expected long array to not be associative.", v.getTarget()); + } + long[] longs = new long[(int) array.size()]; + int i = 0; + for(Mixed m : array) { + longs[i++] = ArgumentValidation.getInt(m, m.getTarget()); + } + return longs; + }, + (long[] array) -> { + CArray ret = new CArray(Target.UNKNOWN); + for(long i : array) { + ret.push(new CInt(i, Target.UNKNOWN), Target.UNKNOWN); + } + return ret; + }), + SHORT( + (Mixed v) -> ArgumentValidation.getInt16(v, v.getTarget()), + (Short v) -> new CInt(((Number) v).longValue(), Target.UNKNOWN)), + STRING( + (Mixed v) -> v.val(), + (String v) -> new CString(v, Target.UNKNOWN)), + TAG_CONTAINER( + (Mixed v) -> { + throw new UnsupportedOperationException(); + }, + (MCTagContainer v) -> { + throw new UnsupportedOperationException(); + }), + TAG_CONTAINER_ARRAY( + (Mixed v) -> { + throw new UnsupportedOperationException(); + }, + (MCTagContainer[] v) -> { + throw new UnsupportedOperationException(); + }); + + private final Function conversion; + private final Function construction; + + MCTagType(Function conversion, Function construction) { + this.conversion = conversion; + this.construction = construction; + } + + /** + * Returns a Java object from a MethodScript construct. + * Throws a ConfigRuntimeException if the value is not valid for this tag type. + * @param container the tag container context + * @param value MethodScript construct + * @return a Java object + */ + public Object convert(MCTagContainer container, Mixed value) { + if(this == TAG_CONTAINER) { + if(!value.isInstanceOf(CArray.TYPE)) { + throw new CREFormatException("Expected tag container to be an array.", value.getTarget()); + } + CArray containerArray = (CArray) value; + if(!containerArray.isAssociative()) { + throw new CREFormatException("Expected tag container array to be associative.", value.getTarget()); + } + for(String key : containerArray.stringKeySet()) { + Mixed possibleArray = containerArray.get(key, value.getTarget()); + if(!possibleArray.isInstanceOf(CArray.TYPE)) { + throw new CREFormatException("Expected tag entry to be an array.", possibleArray.getTarget()); + } + CArray entryArray = (CArray) possibleArray; + if(!entryArray.isAssociative()) { + throw new CREFormatException("Expected tag array to be associative.", entryArray.getTarget()); + } + Mixed entryType = entryArray.get("type", entryArray.getTarget()); + Mixed entryValue = entryArray.get("value", entryArray.getTarget()); + MCTagType tagType; + try { + tagType = MCTagType.valueOf(entryType.val()); + } catch (IllegalArgumentException ex) { + throw new CREFormatException("Tag type is not valid: " + entryType.val(), entryType.getTarget()); + } + Object tagValue; + if(tagType == MCTagType.TAG_CONTAINER) { + tagValue = tagType.convert(container.newContainer(), entryValue); + } else if(tagType == TAG_CONTAINER_ARRAY) { + tagValue = tagType.convert(container, entryValue); + } else { + tagValue = tagType.convert(container, entryValue); + } + try { + container.set(StaticLayer.GetConvertor().GetNamespacedKey(key), tagType, tagValue); + } catch (ClassCastException ex) { + throw new CREFormatException("Tag value does not match expected type.", entryValue.getTarget()); + } catch (IllegalArgumentException ex) { + throw new CREFormatException(ex.getMessage(), entryValue.getTarget()); + } + } + return container; + } else if(this == TAG_CONTAINER_ARRAY) { + if(!value.isInstanceOf(CArray.TYPE)) { + throw new CREFormatException("Expected tag container to be an array.", value.getTarget()); + } + CArray array = (CArray) value; + if(array.isAssociative()) { + throw new CREFormatException("Expected tag container array to not be associative.", array.getTarget()); + } + MCTagContainer[] containers = new MCTagContainer[(int) array.size()]; + int i = 0; + for(Mixed possibleContainer : array) { + containers[i++] = (MCTagContainer) TAG_CONTAINER.convert(container.newContainer(), possibleContainer); + } + return containers; + } + return conversion.apply(value); + } + + /** + * Returns a MethodScript construct from a Java object. + * Throws a ClassCastException if the value does not match this tag type. + * @param value a valid Java object + * @return a MethodScript construct + */ + public Mixed construct(Object value) throws ClassCastException { + if(this == TAG_CONTAINER) { + MCTagContainer container = (MCTagContainer) value; + CArray containerArray = CArray.GetAssociativeArray(Target.UNKNOWN); + for(MCNamespacedKey key : container.getKeys()) { + CArray entry = CArray.GetAssociativeArray(Target.UNKNOWN); + MCTagType type = container.getType(key); + entry.set("type", type.name(), Target.UNKNOWN); + entry.set("value", type.construct(container.get(key, type)), Target.UNKNOWN); + containerArray.set(key.toString(), entry, Target.UNKNOWN); + } + return containerArray; + } else if(this == TAG_CONTAINER_ARRAY) { + MCTagContainer[] containers = (MCTagContainer[]) value; + CArray array = new CArray(Target.UNKNOWN, containers.length); + for(MCTagContainer container : containers) { + array.push(TAG_CONTAINER.construct(container), Target.UNKNOWN); + } + return array; + } + return (Mixed) construction.apply(value); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCTargetReason.java b/src/main/java/com/laytonsmith/abstraction/enums/MCTargetReason.java index 1ddf9f1493..54d0a95be0 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCTargetReason.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCTargetReason.java @@ -1,14 +1,22 @@ - - package com.laytonsmith.abstraction.enums; -/** - * - * - */ public enum MCTargetReason { - TARGET_DIED, CLOSEST_PLAYER, TARGET_ATTACKED_ENTITY, PIG_ZOMBIE_TARGET, - FORGOT_TARGET, TARGET_ATTACKED_OWNER, OWNER_ATTACKED_TARGET, RANDOM_TARGET, - DEFEND_VILLAGE, TARGET_ATTACKED_NEARBY_ENTITY, REINFORCEMENT_TARGET, COLLISION, - CUSTOM; + TARGET_DIED, + CLOSEST_PLAYER, + TARGET_ATTACKED_ENTITY, + FORGOT_TARGET, + TARGET_ATTACKED_OWNER, + OWNER_ATTACKED_TARGET, + RANDOM_TARGET, + DEFEND_VILLAGE, + TARGET_ATTACKED_NEARBY_ENTITY, + REINFORCEMENT_TARGET, + COLLISION, + CLOSEST_ENTITY, + FOLLOW_LEADER, + TEMPT, + CUSTOM, + TARGET_OTHER_LEVEL, // Paper + TARGET_INVALID, // Paper + UNKNOWN } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCTeleportCause.java b/src/main/java/com/laytonsmith/abstraction/enums/MCTeleportCause.java index 61c14543d1..4f5f35a60f 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCTeleportCause.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCTeleportCause.java @@ -2,12 +2,17 @@ import com.laytonsmith.annotations.MEnum; -@MEnum("TeleportCause") +@MEnum("com.commandhelper.TeleportCause") public enum MCTeleportCause { COMMAND, END_PORTAL, ENDER_PEARL, NETHER_PORTAL, PLUGIN, + SPECTATE, + END_GATEWAY, + CHORUS_FRUIT, + DISMOUNT, + EXIT_BED, UNKNOWN } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCTone.java b/src/main/java/com/laytonsmith/abstraction/enums/MCTone.java index b2e8aa2d01..dc9ceaa9f6 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCTone.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCTone.java @@ -1,13 +1,8 @@ - package com.laytonsmith.abstraction.enums; import com.laytonsmith.annotations.MEnum; -/** - * - * - */ -@MEnum("Tone") +@MEnum("com.commandhelper.Tone") public enum MCTone { G, A, @@ -15,5 +10,5 @@ public enum MCTone { C, D, E, - F; + F } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCTreeSpecies.java b/src/main/java/com/laytonsmith/abstraction/enums/MCTreeSpecies.java new file mode 100644 index 0000000000..b267aabef8 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCTreeSpecies.java @@ -0,0 +1,17 @@ +package com.laytonsmith.abstraction.enums; + +import com.laytonsmith.annotations.MEnum; + +@MEnum("com.commandhelper.WoodType") +public enum MCTreeSpecies { + ACACIA, + BAMBOO, + BIRCH, + CHERRY, + DARK_OAK, + JUNGLE, + MANGROVE, + OAK, + PALE_OAK, + SPRUCE, +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCTreeType.java b/src/main/java/com/laytonsmith/abstraction/enums/MCTreeType.java index 37681e105d..6192188327 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCTreeType.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCTreeType.java @@ -2,11 +2,7 @@ import com.laytonsmith.annotations.MEnum; -/** - * - * @author Hekta - */ -@MEnum("TreeType") +@MEnum("com.commandhelper.TreeType") public enum MCTreeType { TREE, BIG_TREE, @@ -23,5 +19,15 @@ public enum MCTreeType { DARK_OAK, COCOA_TREE, MEGA_REDWOOD, - TALL_BIRCH; -} \ No newline at end of file + MEGA_PINE, + TALL_BIRCH, + CHORUS_PLANT, + CRIMSON_FUNGUS, + WARPED_FUNGUS, + AZALEA, + MANGROVE, + TALL_MANGROVE, + CHERRY, + PALE_OAK, + PALE_OAK_CREAKING, +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCTrimMaterial.java b/src/main/java/com/laytonsmith/abstraction/enums/MCTrimMaterial.java new file mode 100644 index 0000000000..bde88b5a70 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCTrimMaterial.java @@ -0,0 +1,77 @@ +package com.laytonsmith.abstraction.enums; + +import com.laytonsmith.PureUtilities.ClassLoading.DynamicEnum; +import com.laytonsmith.annotations.MDynamicEnum; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +@MDynamicEnum("com.commandhelper.TrimMaterial") +public abstract class MCTrimMaterial extends DynamicEnum { + + protected static final Map MAP = new HashMap<>(); + + public MCTrimMaterial(MCVanillaTrimMaterial mcVanillaTrimMaterial, Concrete concrete) { + super(mcVanillaTrimMaterial, concrete); + } + + public static MCTrimMaterial valueOf(String test) throws IllegalArgumentException { + MCTrimMaterial ret = MAP.get(test); + if(ret == null) { + throw new IllegalArgumentException("Unknown trim material type: " + test); + } + return ret; + } + + public static Set types() { + if(MAP.isEmpty()) { // docs mode + Set dummy = new HashSet<>(); + for(final MCVanillaTrimMaterial s : MCVanillaTrimMaterial.values()) { + if(s != MCVanillaTrimMaterial.UNKNOWN) { + dummy.add(s.name()); + } + } + return dummy; + } + return new TreeSet<>(MAP.keySet()); + } + + public static List values() { + if(MAP.isEmpty()) { // docs mode + ArrayList dummy = new ArrayList<>(); + for(final MCVanillaTrimMaterial s : MCVanillaTrimMaterial.values()) { + if(s == MCVanillaTrimMaterial.UNKNOWN) { + continue; + } + dummy.add(new MCTrimMaterial<>(s, null) { + @Override + public String name() { + return s.name(); + } + }); + } + return dummy; + } + return new ArrayList<>(MAP.values()); + } + + public enum MCVanillaTrimMaterial { + AMETHYST, + COPPER, + DIAMOND, + EMERALD, + GOLD, + IRON, + LAPIS, + NETHERITE, + QUARTZ, + REDSTONE, + RESIN, + UNKNOWN + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCTrimPattern.java b/src/main/java/com/laytonsmith/abstraction/enums/MCTrimPattern.java new file mode 100644 index 0000000000..2f5b03f36b --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCTrimPattern.java @@ -0,0 +1,98 @@ +package com.laytonsmith.abstraction.enums; + +import com.laytonsmith.PureUtilities.ClassLoading.DynamicEnum; +import com.laytonsmith.annotations.MDynamicEnum; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +@MDynamicEnum("com.commandhelper.TrimPattern") +public abstract class MCTrimPattern extends DynamicEnum { + + protected static final Map MAP = new HashMap<>(); + + public MCTrimPattern(MCVanillaTrimPattern mcVanillaTrimPattern, Concrete concrete) { + super(mcVanillaTrimPattern, concrete); + } + + public static MCTrimPattern valueOf(String test) throws IllegalArgumentException { + MCTrimPattern ret = MAP.get(test); + if(ret == null) { + throw new IllegalArgumentException("Unknown trim pattern type: " + test); + } + return ret; + } + + public static Set types() { + if(MAP.isEmpty()) { // docs mode + Set dummy = new HashSet<>(); + for(final MCVanillaTrimPattern s : MCVanillaTrimPattern.values()) { + if(s != MCVanillaTrimPattern.UNKNOWN) { + dummy.add(s.name()); + } + } + return dummy; + } + return new TreeSet<>(MAP.keySet()); + } + + public static List values() { + if(MAP.isEmpty()) { // docs mode + ArrayList dummy = new ArrayList<>(); + for(final MCVanillaTrimPattern s : MCVanillaTrimPattern.values()) { + if(s == MCVanillaTrimPattern.UNKNOWN) { + continue; + } + dummy.add(new MCTrimPattern<>(s, null) { + @Override + public String name() { + return s.name(); + } + }); + } + return dummy; + } + return new ArrayList<>(MAP.values()); + } + + public enum MCVanillaTrimPattern { + BOLT(MCVersion.MC1_20_6), + COAST, + DUNE, + EYE, + FLOW(MCVersion.MC1_20_6), + HOST, + RAISER, + RIB, + SENTRY, + SHAPER, + SILENCE, + SNOUT, + SPIRE, + TIDE, + VEX, + WARD, + WAYFINDER, + WILD, + UNKNOWN; + + private final MCVersion since; + + MCVanillaTrimPattern() { + this(MCVersion.MC1_19_4); + } + + MCVanillaTrimPattern(MCVersion since) { + this.since = since; + } + + public boolean existsIn(MCVersion version) { + return version.gte(this.since); + } + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCUnleashReason.java b/src/main/java/com/laytonsmith/abstraction/enums/MCUnleashReason.java new file mode 100644 index 0000000000..3caa60801f --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCUnleashReason.java @@ -0,0 +1,10 @@ +package com.laytonsmith.abstraction.enums; + +public enum MCUnleashReason { + HOLDER_GONE, + PLAYER_UNLEASH, + DISTANCE, + SHEAR, + FIREWORK, + UNKNOWN +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCVersion.java b/src/main/java/com/laytonsmith/abstraction/enums/MCVersion.java new file mode 100644 index 0000000000..8812e82edc --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCVersion.java @@ -0,0 +1,180 @@ +package com.laytonsmith.abstraction.enums; + +import com.laytonsmith.PureUtilities.Common.StringUtils; +import com.laytonsmith.PureUtilities.Version; + +/** + * Created by jb_aero on 3/17/2015. + *

+ * This may require unforeseen changes later to support fossil versions, but odds are support will not be requested. + */ +public enum MCVersion implements Version { + /** + * Baseline, highly unlikely anyone is running something before this + */ + MC1_0, + MC1_1, + MC1_2, + MC1_2_5, + MC1_3, + MC1_3_2, + MC1_4, + MC1_4_2, + MC1_4_5, + // below this point, Bukkit begins package versioning + MC1_4_7, + MC1_5, + MC1_5_2, + MC1_6, + MC1_6_2, + MC1_6_4, + MC1_7, + MC1_7_2, + MC1_7_X, + MC1_8, + MC1_8_3, + MC1_8_6, + MC1_8_7, + MC1_8_X, + MC1_9, + MC1_9_X, + MC1_9_4, + MC1_10, + MC1_10_X, + MC1_11, + MC1_11_X, + MC1_12, + MC1_12_X, + MC1_13, + MC1_13_X, + MC1_14, + MC1_14_X, + MC1_15, + MC1_15_X, + MC1_16, + MC1_16_1, + MC1_16_2, + MC1_16_3, + MC1_16_4, + MC1_16_X, + MC1_17, + MC1_17_X, + MC1_18, + MC1_18_1, + MC1_18_X, + MC1_19, + MC1_19_1, + MC1_19_2, + MC1_19_3, + MC1_19_4, + MC1_19_X, + MC1_20, + MC1_20_1, + MC1_20_2, + MC1_20_4, + MC1_20_6, + MC1_20_X, + MC1_21, + MC1_21_1, + MC1_21_3, + MC1_21_4, + MC1_21_5, + MC1_21_6, + MC1_21_7, + MC1_21_8, + MC1_21_9, + MC1_21_10, + MC1_21_11, + MC1_21_X, + MCX_X, + CURRENT, + FUTURE, + NEVER; + + public static final MCVersion EARLIEST_SUPPORTED = MC1_16_X; + public static final MCVersion LATEST_SUPPORTED = MC1_21_11; + + public static MCVersion match(String[] source) { + String[] parts = new String[Math.min(3, source.length)]; + for(int i = 0; i < parts.length; i++) { + parts[i] = source[i]; + } + String attempt = "MC" + StringUtils.Join(parts, "_"); + try { + return valueOf(attempt); + } catch (IllegalArgumentException iae) { + if(parts.length == 3) { + parts[2] = "0".equals(parts[2]) ? null : "X"; + attempt = "MC" + StringUtils.Join(parts[2] == null ? new String[]{parts[0], parts[1]} : parts, "_"); + try { + return valueOf(attempt); + } catch (IllegalArgumentException iae2) { + parts[1] = "X"; + attempt = "MC" + StringUtils.Join(new String[]{parts[0], parts[1]}, "_"); + try { + return valueOf(attempt); + } catch (IllegalArgumentException iae3) { + return MCX_X; + } + } + } + if(parts.length == 2) { + parts[1] = "X"; + attempt = "MC" + StringUtils.Join(parts, "_"); + try { + return valueOf(attempt); + } catch (IllegalArgumentException iae2) { + return MCX_X; + } + } + return MCX_X; + } + } + + @Override + public int getMajor() { + String form = name().split("_")[0].substring(2); + if("X".equals(form)) { + return -1; + } + return Integer.parseInt(form); + } + + @Override + public int getMinor() { + String[] parts = name().split("_"); + if(parts.length < 2 || "X".equals(parts[1])) { + return -1; + } + return Integer.parseInt(parts[1]); + } + + @Override + public int getSupplemental() { + String[] parts = name().split("_"); + if(parts.length < 3 || "X".equals(parts[2])) { + return -1; + } + return Integer.parseInt(parts[2]); + } + + @Override + public boolean lt(Version other) { + return other instanceof MCVersion && this.ordinal() < ((MCVersion) other).ordinal(); + } + + @Override + public boolean lte(Version other) { + return other instanceof MCVersion && !(this.ordinal() > ((MCVersion) other).ordinal()); + } + + @Override + public boolean gt(Version other) { + return other instanceof MCVersion && this.ordinal() > ((MCVersion) other).ordinal(); + } + + @Override + public boolean gte(Version other) { + return other instanceof MCVersion && !(this.ordinal() < ((MCVersion) other).ordinal()); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCWolfType.java b/src/main/java/com/laytonsmith/abstraction/enums/MCWolfType.java deleted file mode 100644 index 0ac066e275..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCWolfType.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.laytonsmith.abstraction.enums; - -import com.laytonsmith.annotations.MEnum; - -/** - * - * @author jb_aero - */ -@MEnum("WolfType") -public enum MCWolfType { - ANGRY, - TAMED -} \ No newline at end of file diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCWorldEnvironment.java b/src/main/java/com/laytonsmith/abstraction/enums/MCWorldEnvironment.java index 9463194f63..231603a6e0 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCWorldEnvironment.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCWorldEnvironment.java @@ -1,15 +1,11 @@ - package com.laytonsmith.abstraction.enums; import com.laytonsmith.annotations.MEnum; -/** - * - * - */ -@MEnum("WorldEnvironment") +@MEnum("com.commandhelper.WorldEnvironment") public enum MCWorldEnvironment { NORMAL, NETHER, - THE_END; + THE_END, + CUSTOM } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCWorldType.java b/src/main/java/com/laytonsmith/abstraction/enums/MCWorldType.java index ec86ad6fd3..3016e69235 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCWorldType.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCWorldType.java @@ -1,17 +1,11 @@ - package com.laytonsmith.abstraction.enums; import com.laytonsmith.annotations.MEnum; -/** - * - * - */ -@MEnum("WorldType") +@MEnum("com.commandhelper.WorldType") public enum MCWorldType { NORMAL, - FLAT, - VERSION_1_1, - LARGE_BIOMES, - AMPLIFIED; + FLAT, + LARGE_BIOMES, + AMPLIFIED } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/MCZombieType.java b/src/main/java/com/laytonsmith/abstraction/enums/MCZombieType.java index 3a08cf4ba2..1397537fbb 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/MCZombieType.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/MCZombieType.java @@ -2,12 +2,7 @@ import com.laytonsmith.annotations.MEnum; -/** - * - * @author jb_aero - */ -@MEnum("ZombieType") +@MEnum("com.commandhelper.ZombieType") public enum MCZombieType { BABY, - VILLAGER -} \ No newline at end of file +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCAction.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCAction.java index f3fb791e44..79cd70bf14 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCAction.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCAction.java @@ -1,4 +1,3 @@ - package com.laytonsmith.abstraction.enums.bukkit; import com.laytonsmith.abstraction.Implementation; @@ -7,21 +6,17 @@ import com.laytonsmith.annotations.abstractionenum; import org.bukkit.event.block.Action; -/** - * - * - */ @abstractionenum( - implementation= Implementation.Type.BUKKIT, - forAbstractEnum=MCAction.class, - forConcreteEnum=Action.class + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCAction.class, + forConcreteEnum = Action.class ) -public class BukkitMCAction extends EnumConvertor{ - +public class BukkitMCAction extends EnumConvertor { + private static BukkitMCAction instance; - - public static BukkitMCAction getConvertor(){ - if(instance == null){ + + public static BukkitMCAction getConvertor() { + if(instance == null) { instance = new BukkitMCAction(); } return instance; diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCArt.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCArt.java index 82c93ba4c4..7b4454433f 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCArt.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCArt.java @@ -1,25 +1,78 @@ package com.laytonsmith.abstraction.enums.bukkit; -import com.laytonsmith.abstraction.Implementation; -import com.laytonsmith.abstraction.enums.EnumConvertor; +import com.laytonsmith.PureUtilities.Common.ReflectionUtils; import com.laytonsmith.abstraction.enums.MCArt; -import com.laytonsmith.annotations.abstractionenum; +import com.laytonsmith.core.MSLog; +import com.laytonsmith.core.Static; +import com.laytonsmith.core.constructs.Target; import org.bukkit.Art; +import org.bukkit.Keyed; +import org.bukkit.NamespacedKey; -@abstractionenum( - implementation = Implementation.Type.BUKKIT, -forAbstractEnum = MCArt.class, -forConcreteEnum = Art.class) -public class BukkitMCArt extends EnumConvertor { +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; - private static com.laytonsmith.abstraction.enums.bukkit.BukkitMCArt instance; +public class BukkitMCArt extends MCArt { - public static com.laytonsmith.abstraction.enums.bukkit.BukkitMCArt getConvertor() { - if (instance == null) { - instance = new com.laytonsmith.abstraction.enums.bukkit.BukkitMCArt(); + private static final Map BUKKIT_MAP = new HashMap<>(); + + public BukkitMCArt(MCVanillaArt vanillaArtType, Art art) { + super(vanillaArtType, art); + } + + @Override + public String name() { + if(getAbstracted() == MCVanillaArt.UNKNOWN) { + // changed from enum to interface in 1.21.3 + NamespacedKey key = ReflectionUtils.invokeMethod(Keyed.class, getConcrete(), "getKey"); + return key.getKey().toUpperCase(Locale.ROOT); + } + return getAbstracted().name(); + } + + public static MCArt valueOfConcrete(Art test) { + MCArt type = BUKKIT_MAP.get(test); + if(type == null) { + // changed from enum to interface in 1.21.3 + NamespacedKey key = ReflectionUtils.invokeMethod(Keyed.class, test, "getKey"); + MSLog.GetLogger().e(MSLog.Tags.GENERAL, "Bukkit Art missing in BUKKIT_MAP: " + + key.getKey().toUpperCase(Locale.ROOT), Target.UNKNOWN); + return new BukkitMCArt(MCVanillaArt.UNKNOWN, test); + } + return type; + } + + public static void build() { + for(MCVanillaArt v : MCVanillaArt.values()) { + if(v.existsIn(Static.getServer().getMinecraftVersion())) { + Art type; + try { + type = (Art) Art.class.getDeclaredField(v.name()).get(null); + } catch (IllegalAccessException | NoSuchFieldException e) { + MSLog.GetLogger().w(MSLog.Tags.GENERAL, "Could not find a Bukkit Art for " + v.name(), + Target.UNKNOWN); + continue; + } + BukkitMCArt wrapper = new BukkitMCArt(v, type); + MAP.put(v.name(), wrapper); + BUKKIT_MAP.put(type, wrapper); + } + } + for(Field f : Art.class.getFields()) { + try { + Art a = (Art) f.get(null); + if(!BUKKIT_MAP.containsKey(a)) { + MSLog.GetLogger().w(MSLog.Tags.GENERAL, "Could not find an MCArt for " + f.getName(), + Target.UNKNOWN); + MCArt wrapper = new BukkitMCArt(MCVanillaArt.UNKNOWN, a); + MAP.put(f.getName(), wrapper); + BUKKIT_MAP.put(a, wrapper); + } + } catch (IllegalAccessException | ClassCastException e) { + throw new RuntimeException(e); + } } - return instance; } - - } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCAttribute.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCAttribute.java new file mode 100644 index 0000000000..612c09bba1 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCAttribute.java @@ -0,0 +1,86 @@ +package com.laytonsmith.abstraction.enums.bukkit; + +import com.laytonsmith.PureUtilities.Common.ReflectionUtils; +import com.laytonsmith.abstraction.enums.MCAttribute; +import com.laytonsmith.abstraction.enums.MCVersion; +import com.laytonsmith.core.MSLog; +import com.laytonsmith.core.Static; +import com.laytonsmith.core.constructs.Target; +import org.bukkit.Keyed; +import org.bukkit.NamespacedKey; +import org.bukkit.attribute.Attribute; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +public class BukkitMCAttribute extends MCAttribute { + + private static final Map BUKKIT_MAP = new HashMap<>(); + + public BukkitMCAttribute(MCVanillaAttribute vanillaAttributeType, Attribute attribute) { + super(vanillaAttributeType, attribute); + } + + @Override + public String name() { + if(getAbstracted() == MCVanillaAttribute.UNKNOWN) { + // changed from enum to interface in 1.21.3 + NamespacedKey key = ReflectionUtils.invokeMethod(Keyed.class, getConcrete(), "getKey"); + return key.getKey().toUpperCase(Locale.ROOT); + } + return getAbstracted().name(); + } + + public static MCAttribute valueOfConcrete(Attribute test) { + MCAttribute type = BUKKIT_MAP.get(test); + if(type == null) { + // changed from enum to interface in 1.21.3 + NamespacedKey key = ReflectionUtils.invokeMethod(Keyed.class, test, "getKey"); + MSLog.GetLogger().e(MSLog.Tags.GENERAL, "Bukkit Attribute missing in BUKKIT_MAP: " + + key.getKey().toUpperCase(Locale.ROOT), Target.UNKNOWN); + return new BukkitMCAttribute(MCVanillaAttribute.UNKNOWN, test); + } + return type; + } + + public static void build() { + for(MCVanillaAttribute v : MCVanillaAttribute.values()) { + if(v.existsIn(Static.getServer().getMinecraftVersion())) { + String name = v.name(); + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_21_3)) { + // remove prefixes in MC 1.21.3 and later + name = name.replace("GENERIC_", ""); + name = name.replace("PLAYER_", ""); + name = name.replace("ZOMBIE_", ""); + } + Attribute type; + try { + type = (Attribute) Attribute.class.getDeclaredField(name).get(null); + } catch (IllegalAccessException | NoSuchFieldException e) { + MSLog.GetLogger().w(MSLog.Tags.GENERAL, "Could not find a Bukkit Attribute for " + v.name(), + Target.UNKNOWN); + continue; + } + BukkitMCAttribute wrapper = new BukkitMCAttribute(v, type); + MAP.put(v.name(), wrapper); + BUKKIT_MAP.put(type, wrapper); + } + } + for(Field f : Attribute.class.getFields()) { + try { + Attribute a = (Attribute) f.get(null); + if(!BUKKIT_MAP.containsKey(a)) { + MSLog.GetLogger().w(MSLog.Tags.GENERAL, "Could not find an MCAttribute for " + f.getName(), + Target.UNKNOWN); + MCAttribute wrapper = new BukkitMCAttribute(MCVanillaAttribute.UNKNOWN, a); + MAP.put(f.getName(), wrapper); + BUKKIT_MAP.put(a, wrapper); + } + } catch (IllegalAccessException | ClassCastException e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCBiomeType.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCBiomeType.java index 81b5c599cd..208ade1fbe 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCBiomeType.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCBiomeType.java @@ -1,30 +1,72 @@ - package com.laytonsmith.abstraction.enums.bukkit; -import com.laytonsmith.abstraction.Implementation; -import com.laytonsmith.abstraction.enums.EnumConvertor; +import com.laytonsmith.PureUtilities.Common.ReflectionUtils; import com.laytonsmith.abstraction.enums.MCBiomeType; -import com.laytonsmith.annotations.abstractionenum; +import com.laytonsmith.core.MSLog; +import com.laytonsmith.core.Static; +import com.laytonsmith.core.constructs.Target; +import org.bukkit.Keyed; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; import org.bukkit.block.Biome; -/** - * - * - */ -@abstractionenum( - implementation = Implementation.Type.BUKKIT, -forAbstractEnum = MCBiomeType.class, -forConcreteEnum = Biome.class) -public class BukkitMCBiomeType extends EnumConvertor { - - private static com.laytonsmith.abstraction.enums.bukkit.BukkitMCBiomeType instance; - - public static com.laytonsmith.abstraction.enums.bukkit.BukkitMCBiomeType getConvertor() { - if (instance == null) { - instance = new com.laytonsmith.abstraction.enums.bukkit.BukkitMCBiomeType(); +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +public class BukkitMCBiomeType extends MCBiomeType { + + private static final Map BUKKIT_MAP = new HashMap<>(); + + public BukkitMCBiomeType(MCVanillaBiomeType vanillaBiomeType, Biome biome) { + super(vanillaBiomeType, biome); + } + + @Override + public String name() { + if(getAbstracted() == MCVanillaBiomeType.UNKNOWN) { + // changed from enum to interface in 1.21.3 + NamespacedKey key = ReflectionUtils.invokeMethod(Keyed.class, getConcrete(), "getKey"); + return key.getKey().toUpperCase(Locale.ROOT); + } + return getAbstracted().name(); + } + + public static MCBiomeType valueOfConcrete(Biome test) { + MCBiomeType type = BUKKIT_MAP.get(test); + if(type == null) { + NamespacedKey key = ReflectionUtils.invokeMethod(Keyed.class, test, "getKey"); + MSLog.GetLogger().e(MSLog.Tags.GENERAL, "Bukkit BiomeType missing in BUKKIT_MAP: " + + key.getKey().toUpperCase(Locale.ROOT), Target.UNKNOWN); + return new BukkitMCBiomeType(MCVanillaBiomeType.UNKNOWN, test); + } + return type; + } + + // This way we don't take up extra memory on non-bukkit implementations + public static void build() { + for(MCVanillaBiomeType v : MCVanillaBiomeType.values()) { + if(v.existsIn(Static.getServer().getMinecraftVersion())) { + Biome type = Registry.BIOME.get(NamespacedKey.minecraft(v.name().toLowerCase(Locale.ROOT))); + if(type == null) { + MSLog.GetLogger().w(MSLog.Tags.GENERAL, "Could not find a Bukkit BiomeType for " + v.name(), + Target.UNKNOWN); + continue; + } + BukkitMCBiomeType wrapper = new BukkitMCBiomeType(v, type); + MAP.put(v.name(), wrapper); + BUKKIT_MAP.put(type, wrapper); + } + } + for(Biome b : Registry.BIOME) { + if(!BUKKIT_MAP.containsKey(b)) { + NamespacedKey key = ReflectionUtils.invokeMethod(Keyed.class, b, "getKey"); + MSLog.GetLogger().w(MSLog.Tags.GENERAL, "Could not find MCBiomeType for " + + key.getKey().toUpperCase(Locale.ROOT), Target.UNKNOWN); + MCBiomeType wrapper = new BukkitMCBiomeType(MCVanillaBiomeType.UNKNOWN, b); + MAP.put(key.getKey().toUpperCase(Locale.ROOT), wrapper); + BUKKIT_MAP.put(b, wrapper); + } } - return instance; } - - } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCBlockFace.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCBlockFace.java index c3d8a26dd0..8502e0ae66 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCBlockFace.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCBlockFace.java @@ -1,4 +1,3 @@ - package com.laytonsmith.abstraction.enums.bukkit; import com.laytonsmith.abstraction.Implementation; @@ -7,23 +6,19 @@ import com.laytonsmith.annotations.abstractionenum; import org.bukkit.block.BlockFace; -/** - * - */ @abstractionenum( implementation = Implementation.Type.BUKKIT, -forAbstractEnum = MCBlockFace.class, -forConcreteEnum = BlockFace.class) + forAbstractEnum = MCBlockFace.class, + forConcreteEnum = BlockFace.class +) public class BukkitMCBlockFace extends EnumConvertor { private static com.laytonsmith.abstraction.enums.bukkit.BukkitMCBlockFace instance; public static com.laytonsmith.abstraction.enums.bukkit.BukkitMCBlockFace getConvertor() { - if (instance == null) { + if(instance == null) { instance = new com.laytonsmith.abstraction.enums.bukkit.BukkitMCBlockFace(); } return instance; } - - } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCChatColor.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCChatColor.java index 6b321662ab..25b0dfe868 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCChatColor.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCChatColor.java @@ -1,4 +1,3 @@ - package com.laytonsmith.abstraction.enums.bukkit; import com.laytonsmith.abstraction.Implementation; @@ -7,19 +6,17 @@ import com.laytonsmith.annotations.abstractionenum; import org.bukkit.ChatColor; -/** - * - * - */ @abstractionenum( implementation = Implementation.Type.BUKKIT, -forAbstractEnum = MCChatColor.class, -forConcreteEnum = ChatColor.class) + forAbstractEnum = MCChatColor.class, + forConcreteEnum = ChatColor.class +) public class BukkitMCChatColor extends EnumConvertor { + private static com.laytonsmith.abstraction.enums.bukkit.BukkitMCChatColor instance; public static com.laytonsmith.abstraction.enums.bukkit.BukkitMCChatColor getConvertor() { - if (instance == null) { + if(instance == null) { instance = new com.laytonsmith.abstraction.enums.bukkit.BukkitMCChatColor(); } return instance; @@ -27,7 +24,7 @@ public static com.laytonsmith.abstraction.enums.bukkit.BukkitMCChatColor getConv @Override protected MCChatColor getAbstractedEnumCustom(ChatColor concrete) { - switch(concrete){ + switch(concrete) { case MAGIC: return MCChatColor.RANDOM; case RESET: @@ -38,7 +35,7 @@ protected MCChatColor getAbstractedEnumCustom(ChatColor concrete) { @Override protected ChatColor getConcreteEnumCustom(MCChatColor abstracted) { - switch(abstracted){ + switch(abstracted) { case RANDOM: return ChatColor.MAGIC; case PLAIN_WHITE: @@ -46,8 +43,4 @@ protected ChatColor getConcreteEnumCustom(MCChatColor abstracted) { } return super.getConcreteEnumCustom(abstracted); } - - - - } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCClickType.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCClickType.java index 5dbd2d6b8e..9395ac9dce 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCClickType.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCClickType.java @@ -6,21 +6,17 @@ import com.laytonsmith.annotations.abstractionenum; import org.bukkit.event.inventory.ClickType; -/** - * - * @author jb_aero - */ @abstractionenum( - implementation= Implementation.Type.BUKKIT, - forAbstractEnum=MCClickType.class, - forConcreteEnum=ClickType.class + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCClickType.class, + forConcreteEnum = ClickType.class ) -public class BukkitMCClickType extends EnumConvertor{ +public class BukkitMCClickType extends EnumConvertor { private static BukkitMCClickType instance; - + public static BukkitMCClickType getConvertor() { - if (instance == null) { + if(instance == null) { instance = new BukkitMCClickType(); } return instance; diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCDamageCause.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCDamageCause.java index d1ad9f9228..78a0b1afe1 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCDamageCause.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCDamageCause.java @@ -1,4 +1,3 @@ - package com.laytonsmith.abstraction.enums.bukkit; import com.laytonsmith.abstraction.Implementation; @@ -7,19 +6,17 @@ import com.laytonsmith.annotations.abstractionenum; import org.bukkit.event.entity.EntityDamageEvent.DamageCause; -/** - * - * - */ @abstractionenum( implementation = Implementation.Type.BUKKIT, -forAbstractEnum = MCDamageCause.class, -forConcreteEnum = DamageCause.class) + forAbstractEnum = MCDamageCause.class, + forConcreteEnum = DamageCause.class +) public class BukkitMCDamageCause extends EnumConvertor { + private static com.laytonsmith.abstraction.enums.bukkit.BukkitMCDamageCause instance; public static com.laytonsmith.abstraction.enums.bukkit.BukkitMCDamageCause getConvertor() { - if (instance == null) { + if(instance == null) { instance = new com.laytonsmith.abstraction.enums.bukkit.BukkitMCDamageCause(); } return instance; diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCDifficulty.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCDifficulty.java index 0da01dbe59..e782e3b1b3 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCDifficulty.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCDifficulty.java @@ -6,23 +6,19 @@ import com.laytonsmith.annotations.abstractionenum; import org.bukkit.Difficulty; -/** - * - * @author Hekta - */ @abstractionenum( - implementation=Implementation.Type.BUKKIT, - forAbstractEnum=MCDifficulty.class, - forConcreteEnum=Difficulty.class - ) + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCDifficulty.class, + forConcreteEnum = Difficulty.class +) public class BukkitMCDifficulty extends EnumConvertor { private static com.laytonsmith.abstraction.enums.bukkit.BukkitMCDifficulty instance; public static com.laytonsmith.abstraction.enums.bukkit.BukkitMCDifficulty getConvertor() { - if (instance == null) { + if(instance == null) { instance = new com.laytonsmith.abstraction.enums.bukkit.BukkitMCDifficulty(); } return instance; } -} \ No newline at end of file +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCDisplaySlot.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCDisplaySlot.java index f2831045af..93ad6ca614 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCDisplaySlot.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCDisplaySlot.java @@ -1,28 +1,43 @@ package com.laytonsmith.abstraction.enums.bukkit; import com.laytonsmith.abstraction.Implementation; +import com.laytonsmith.abstraction.bukkit.BukkitMCServer; import com.laytonsmith.abstraction.enums.EnumConvertor; import com.laytonsmith.abstraction.enums.MCDisplaySlot; import com.laytonsmith.annotations.abstractionenum; +import com.laytonsmith.commandhelper.CommandHelperPlugin; import org.bukkit.scoreboard.DisplaySlot; -/** - * - * @author jb_aero - */ @abstractionenum( - implementation= Implementation.Type.BUKKIT, - forAbstractEnum=MCDisplaySlot.class, - forConcreteEnum=DisplaySlot.class + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCDisplaySlot.class, + forConcreteEnum = DisplaySlot.class ) -public class BukkitMCDisplaySlot extends EnumConvertor{ +public class BukkitMCDisplaySlot extends EnumConvertor { private static BukkitMCDisplaySlot instance; - + public static BukkitMCDisplaySlot getConvertor() { - if (instance == null) { + if(instance == null) { instance = new BukkitMCDisplaySlot(); } return instance; } + + @Override + protected MCDisplaySlot getAbstractedEnumCustom(DisplaySlot concrete) { + if(!((BukkitMCServer) CommandHelperPlugin.myServer).isPaper()) { + // name is different on Spigot + return MCDisplaySlot.valueOf(concrete.name().replace("SIDEBAR_", "SIDEBAR_TEAM_")); + } + return super.getAbstractedEnumCustom(concrete); + } + + @Override + protected DisplaySlot getConcreteEnumCustom(MCDisplaySlot abstracted) { + if(!((BukkitMCServer) CommandHelperPlugin.myServer).isPaper()) { + return DisplaySlot.valueOf(abstracted.name().replace("TEAM_", "")); + } + return super.getConcreteEnumCustom(abstracted); + } } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCDragType.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCDragType.java index 6ab7b27e90..0d84fbe584 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCDragType.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCDragType.java @@ -1,4 +1,3 @@ - package com.laytonsmith.abstraction.enums.bukkit; import com.laytonsmith.abstraction.Implementation; @@ -7,20 +6,17 @@ import com.laytonsmith.annotations.abstractionenum; import org.bukkit.event.inventory.DragType; -/** - * - * @author MariuszT - */ @abstractionenum( implementation = Implementation.Type.BUKKIT, -forAbstractEnum = MCDragType.class, -forConcreteEnum = DragType.class) + forAbstractEnum = MCDragType.class, + forConcreteEnum = DragType.class +) public class BukkitMCDragType extends EnumConvertor { private static com.laytonsmith.abstraction.enums.bukkit.BukkitMCDragType instance; public static com.laytonsmith.abstraction.enums.bukkit.BukkitMCDragType getConvertor() { - if (instance == null) { + if(instance == null) { instance = new com.laytonsmith.abstraction.enums.bukkit.BukkitMCDragType(); } return instance; diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCDyeColor.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCDyeColor.java index 0a7569a5ad..616b09e00c 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCDyeColor.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCDyeColor.java @@ -1,4 +1,3 @@ - package com.laytonsmith.abstraction.enums.bukkit; import com.laytonsmith.abstraction.Implementation; @@ -7,21 +6,17 @@ import com.laytonsmith.annotations.abstractionenum; import org.bukkit.DyeColor; -/** - * - * - */ @abstractionenum( - implementation=Implementation.Type.BUKKIT, - forAbstractEnum=MCDyeColor.class, - forConcreteEnum=DyeColor.class - ) + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCDyeColor.class, + forConcreteEnum = DyeColor.class +) public class BukkitMCDyeColor extends EnumConvertor { - + private static com.laytonsmith.abstraction.enums.bukkit.BukkitMCDyeColor instance; public static com.laytonsmith.abstraction.enums.bukkit.BukkitMCDyeColor getConvertor() { - if (instance == null) { + if(instance == null) { instance = new com.laytonsmith.abstraction.enums.bukkit.BukkitMCDyeColor(); } return instance; diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCEffect.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCEffect.java index 22433ae466..f714dbbf37 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCEffect.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCEffect.java @@ -1,4 +1,3 @@ - package com.laytonsmith.abstraction.enums.bukkit; import com.laytonsmith.abstraction.Implementation; @@ -7,19 +6,17 @@ import com.laytonsmith.annotations.abstractionenum; import org.bukkit.Effect; -/** - * - * - */ @abstractionenum( implementation = Implementation.Type.BUKKIT, -forAbstractEnum = MCEffect.class, -forConcreteEnum = Effect.class) + forAbstractEnum = MCEffect.class, + forConcreteEnum = Effect.class +) public class BukkitMCEffect extends EnumConvertor { + private static com.laytonsmith.abstraction.enums.bukkit.BukkitMCEffect instance; public static com.laytonsmith.abstraction.enums.bukkit.BukkitMCEffect getConvertor() { - if (instance == null) { + if(instance == null) { instance = new com.laytonsmith.abstraction.enums.bukkit.BukkitMCEffect(); } return instance; diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCEnchantment.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCEnchantment.java new file mode 100644 index 0000000000..9cd2d76737 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCEnchantment.java @@ -0,0 +1,86 @@ +package com.laytonsmith.abstraction.enums.bukkit; + +import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.enums.MCEnchantment; +import com.laytonsmith.abstraction.enums.MCVersion; +import com.laytonsmith.core.MSLog; +import com.laytonsmith.core.Static; +import com.laytonsmith.core.constructs.Target; +import org.bukkit.NamespacedKey; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemStack; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +public class BukkitMCEnchantment extends MCEnchantment { + + private static final Map BUKKIT_MAP = new HashMap<>(); + + public BukkitMCEnchantment(MCVanillaEnchantment vanillaType, Enchantment effect) { + super(vanillaType, effect); + } + + @Override + public String name() { + if(getAbstracted() == MCVanillaEnchantment.UNKNOWN) { + return getConcrete().getKey().getKey().toUpperCase(Locale.ROOT); + } + return getAbstracted().name(); + } + + public static MCEnchantment valueOfConcrete(Enchantment test) { + MCEnchantment type = BUKKIT_MAP.get(test); + if(type == null) { + MSLog.GetLogger().e(MSLog.Tags.GENERAL, "Bukkit Enchantment missing in BUKKIT_MAP: " + + test.getKey().getKey().toUpperCase(Locale.ROOT), Target.UNKNOWN); + return new BukkitMCEnchantment(MCVanillaEnchantment.UNKNOWN, test); + } + return type; + } + + @Override + public boolean canEnchantItem(MCItemStack is) { + return getConcrete().canEnchantItem((ItemStack) is.getHandle()); + } + + @Override + public int getMaxLevel() { + return getConcrete().getMaxLevel(); + } + + public static void build() { + for(MCVanillaEnchantment v : MCVanillaEnchantment.values()) { + if(v.existsIn(Static.getServer().getMinecraftVersion())) { + Enchantment type = getBukkitType(v); + if(type == null) { + MSLog.GetLogger().w(MSLog.Tags.GENERAL, "Could not find a Bukkit enchantment for " + v.name(), Target.UNKNOWN); + continue; + } + BukkitMCEnchantment wrapper = new BukkitMCEnchantment(v, type); + MAP.put(v.name(), wrapper); + BUKKIT_MAP.put(type, wrapper); + } + } + for(Enchantment pt : Enchantment.values()) { + if(pt != null && !BUKKIT_MAP.containsKey(pt)) { + String name = pt.getKey().getKey().toUpperCase(Locale.ROOT); + MSLog.GetLogger().w(MSLog.Tags.GENERAL, "Could not find MCEnchantment for " + + name, Target.UNKNOWN); + MCEnchantment wrapper = new BukkitMCEnchantment(MCVanillaEnchantment.UNKNOWN, pt); + MAP.put(name, wrapper); + BUKKIT_MAP.put(pt, wrapper); + } + } + } + + private static Enchantment getBukkitType(MCVanillaEnchantment v) { + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_20_6)) { + if(v == MCVanillaEnchantment.SWEEPING) { + return Enchantment.SWEEPING_EDGE; + } + } + return Enchantment.getByKey(NamespacedKey.minecraft(v.name().toLowerCase(Locale.ROOT))); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCEnderDragonPhase.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCEnderDragonPhase.java new file mode 100644 index 0000000000..cebf59141a --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCEnderDragonPhase.java @@ -0,0 +1,24 @@ +package com.laytonsmith.abstraction.enums.bukkit; + +import com.laytonsmith.abstraction.Implementation; +import com.laytonsmith.abstraction.enums.EnumConvertor; +import com.laytonsmith.abstraction.enums.MCEnderDragonPhase; +import com.laytonsmith.annotations.abstractionenum; +import org.bukkit.entity.EnderDragon; + +@abstractionenum( + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCEnderDragonPhase.class, + forConcreteEnum = EnderDragon.Phase.class +) +public class BukkitMCEnderDragonPhase extends EnumConvertor { + + private static BukkitMCEnderDragonPhase instance; + + public static BukkitMCEnderDragonPhase getConvertor() { + if(instance == null) { + instance = new BukkitMCEnderDragonPhase(); + } + return instance; + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCEnterBedResult.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCEnterBedResult.java new file mode 100644 index 0000000000..e37c6b63c2 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCEnterBedResult.java @@ -0,0 +1,24 @@ +package com.laytonsmith.abstraction.enums.bukkit; + +import com.laytonsmith.abstraction.Implementation; +import com.laytonsmith.abstraction.enums.EnumConvertor; +import com.laytonsmith.abstraction.enums.MCEnterBedResult; +import com.laytonsmith.annotations.abstractionenum; +import org.bukkit.event.player.PlayerBedEnterEvent; + +@abstractionenum( + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCEnterBedResult.class, + forConcreteEnum = PlayerBedEnterEvent.BedEnterResult.class +) +public class BukkitMCEnterBedResult extends EnumConvertor { + + private static BukkitMCEnterBedResult instance; + + public static BukkitMCEnterBedResult getConvertor() { + if(instance == null) { + instance = new BukkitMCEnterBedResult(); + } + return instance; + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCEntityEffect.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCEntityEffect.java index e9c8e2460e..71132c7d53 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCEntityEffect.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCEntityEffect.java @@ -1,4 +1,3 @@ - package com.laytonsmith.abstraction.enums.bukkit; import com.laytonsmith.abstraction.Implementation; @@ -7,19 +6,17 @@ import com.laytonsmith.annotations.abstractionenum; import org.bukkit.EntityEffect; -/** - * - * - */ @abstractionenum( implementation = Implementation.Type.BUKKIT, -forAbstractEnum = MCEntityEffect.class, -forConcreteEnum = EntityEffect.class) + forAbstractEnum = MCEntityEffect.class, + forConcreteEnum = EntityEffect.class +) public class BukkitMCEntityEffect extends EnumConvertor { + private static com.laytonsmith.abstraction.enums.bukkit.BukkitMCEntityEffect instance; public static com.laytonsmith.abstraction.enums.bukkit.BukkitMCEntityEffect getConvertor() { - if (instance == null) { + if(instance == null) { instance = new com.laytonsmith.abstraction.enums.bukkit.BukkitMCEntityEffect(); } return instance; diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCEntityType.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCEntityType.java index a6a3cfa77d..e54fee5b2d 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCEntityType.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCEntityType.java @@ -1,27 +1,209 @@ - package com.laytonsmith.abstraction.enums.bukkit; -import com.laytonsmith.abstraction.Implementation; -import com.laytonsmith.abstraction.enums.EnumConvertor; +import com.laytonsmith.abstraction.MCEntity; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCCommandMinecart; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCEnderSignal; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCFishHook; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCHopperMinecart; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCItem; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCLightningStrike; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCLlama; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCPigZombie; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCSizedFireball; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCStorageMinecart; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCTNT; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCThrownPotion; import com.laytonsmith.abstraction.enums.MCEntityType; -import com.laytonsmith.annotations.abstractionenum; +import com.laytonsmith.abstraction.enums.MCVersion; +import com.laytonsmith.core.MSLog; +import com.laytonsmith.core.Static; +import com.laytonsmith.core.constructs.Target; import org.bukkit.entity.EntityType; +import org.bukkit.entity.Projectile; + +import java.util.HashMap; +import java.util.Map; + +public class BukkitMCEntityType extends MCEntityType { + + protected static final Map BUKKIT_MAP = new HashMap<>(); + + public BukkitMCEntityType(EntityType concreteType, MCVanillaEntityType abstractedType) { + super(abstractedType, concreteType); + } + + public static void build() { + for(MCVanillaEntityType v : MCVanillaEntityType.values()) { + if(v.existsIn(Static.getServer().getMinecraftVersion())) { + EntityType type; + try { + type = getBukkitType(v); + } catch (IllegalArgumentException | NoSuchFieldError ex) { + MSLog.GetLogger().w(MSLog.Tags.GENERAL, "Could not find a Bukkit EntityType for " + v.name(), Target.UNKNOWN); + continue; + } + BukkitMCEntityType wrapper = new BukkitMCEntityType(type, v); + wrapper.setWrapperClass(); + VANILLA_MAP.put(v, wrapper); + MAP.put(v.name(), wrapper); + BUKKIT_MAP.put(type, wrapper); + } + } + for(EntityType b : EntityType.values()) { + if(!BUKKIT_MAP.containsKey(b)) { + MSLog.GetLogger().w(MSLog.Tags.GENERAL, "Could not find MCEntityType for " + b.name(), Target.UNKNOWN); + MCEntityType wrapper = new BukkitMCEntityType(b, MCVanillaEntityType.UNKNOWN); + MAP.put(b.name(), wrapper); + BUKKIT_MAP.put(b, wrapper); + } + } + } + + @Override + public String name() { + return getAbstracted() == MCVanillaEntityType.UNKNOWN ? getConcrete().name() : getAbstracted().name(); + } + + @Override + public boolean isSpawnable() { + if(getAbstracted() == MCVanillaEntityType.UNKNOWN) { + return getConcrete() != EntityType.UNKNOWN && getConcrete().isSpawnable(); + } else { + return getAbstracted().isSpawnable() || getConcrete().isSpawnable(); + } + } + + @Override + public boolean isProjectile() { + return getConcrete().getEntityClass() != null + && Projectile.class.isAssignableFrom(getConcrete().getEntityClass()); + } + + public static BukkitMCEntityType valueOfConcrete(EntityType test) { + MCEntityType type = BUKKIT_MAP.get(test); + if(type != null) { + return (BukkitMCEntityType) type; + } + MSLog.GetLogger().e(MSLog.Tags.GENERAL, "Bukkit EntityType missing in BUKKIT_MAP: " + test.name(), Target.UNKNOWN); + return new BukkitMCEntityType(test, MCVanillaEntityType.UNKNOWN); + } + + // Add exceptions here + private static EntityType getBukkitType(MCVanillaEntityType v) { + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_20_6)) { + if(Static.getServer().getMinecraftVersion().lte(MCVersion.MC1_21_4)) { + if(v == MCVanillaEntityType.SPLASH_POTION) { + return EntityType.valueOf("POTION"); + } + } + switch(v) { + case ENDER_EYE: + return EntityType.EYE_OF_ENDER; + case DROPPED_ITEM: + return EntityType.ITEM; + case LEASH_HITCH: + return EntityType.LEASH_KNOT; + case THROWN_EXP_BOTTLE: + return EntityType.EXPERIENCE_BOTTLE; + case PRIMED_TNT: + return EntityType.TNT; + case FIREWORK: + return EntityType.FIREWORK_ROCKET; + case MINECART_COMMAND: + return EntityType.COMMAND_BLOCK_MINECART; + case MINECART_CHEST: + return EntityType.CHEST_MINECART; + case MINECART_FURNACE: + return EntityType.FURNACE_MINECART; + case MINECART_TNT: + return EntityType.TNT_MINECART; + case MINECART_HOPPER: + return EntityType.HOPPER_MINECART; + case MUSHROOM_COW: + return EntityType.MOOSHROOM; + case SNOWMAN: + return EntityType.SNOW_GOLEM; + case ENDER_CRYSTAL: + return EntityType.END_CRYSTAL; + case FISHING_HOOK: + return EntityType.FISHING_BOBBER; + case LIGHTNING: + return EntityType.LIGHTNING_BOLT; + } + } else if(v == MCVanillaEntityType.ENDER_EYE) { + return EntityType.valueOf("ENDER_SIGNAL"); + } + return EntityType.valueOf(v.name()); + } + + // This is here because it shouldn't be getting changed from API + public void setWrapperClass(Class clazz) { + wrapperClass = clazz; + } -/** - * - * - */ -@abstractionenum( - implementation = Implementation.Type.BUKKIT, -forAbstractEnum = MCEntityType.class, -forConcreteEnum = EntityType.class) -public class BukkitMCEntityType extends EnumConvertor { - private static com.laytonsmith.abstraction.enums.bukkit.BukkitMCEntityType instance; - - public static com.laytonsmith.abstraction.enums.bukkit.BukkitMCEntityType getConvertor() { - if (instance == null) { - instance = new com.laytonsmith.abstraction.enums.bukkit.BukkitMCEntityType(); + // run once on setup + private void setWrapperClass() { + switch(getAbstracted()) { + case DROPPED_ITEM: + wrapperClass = BukkitMCItem.class; + break; + case ENDER_EYE: + wrapperClass = BukkitMCEnderSignal.class; + break; + case FIREBALL: + wrapperClass = BukkitMCSizedFireball.class; + break; + case FISHING_HOOK: + wrapperClass = BukkitMCFishHook.class; + break; + case LIGHTNING: + wrapperClass = BukkitMCLightningStrike.class; + break; + case LINGERING_POTION: + case SPLASH_POTION: + wrapperClass = BukkitMCThrownPotion.class; + break; + case MINECART_CHEST: + wrapperClass = BukkitMCStorageMinecart.class; + break; + case MINECART_COMMAND: + wrapperClass = BukkitMCCommandMinecart.class; + break; + case MINECART_HOPPER: + wrapperClass = BukkitMCHopperMinecart.class; + break; + case PRIMED_TNT: + wrapperClass = BukkitMCTNT.class; + break; + case TRADER_LLAMA: + wrapperClass = BukkitMCLlama.class; + break; + case ZOMBIFIED_PIGLIN: + wrapperClass = BukkitMCPigZombie.class; + break; + case UNKNOWN: + wrapperClass = null; + break; + default: + String[] split = abstracted.name().toLowerCase().split("_"); + if(split.length == 0 || "".equals(split[0])) { + break; + } + String name = "com.laytonsmith.abstraction.bukkit.entities.BukkitMC"; + for(String s : split) { + name = name.concat(Character.toUpperCase(s.charAt(0)) + s.substring(1)); + } + try { + wrapperClass = (Class) Class.forName(name); + } catch (ClassNotFoundException e) { + String url = "https://github.com/sk89q/CommandHelper/tree/master/src/main/java/" + + "com/laytonsmith/abstraction/bukkit/entities"; + MSLog.GetLogger().d(MSLog.Tags.RUNTIME, "While trying to find the correct entity class for " + + getAbstracted().name() + "(attempted " + name + "), we could not find a wrapper class." + + " This is not necessarily an error, we just don't have any special handling for" + + " this entity yet, and will treat it generically. If there is a matching file at" + + url + ", please alert the developers of this notice.", Target.UNKNOWN); + } } - return instance; } } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCEquipmentSlot.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCEquipmentSlot.java new file mode 100644 index 0000000000..3bbf6c0b5b --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCEquipmentSlot.java @@ -0,0 +1,58 @@ +package com.laytonsmith.abstraction.enums.bukkit; + +import com.laytonsmith.abstraction.Implementation; +import com.laytonsmith.abstraction.enums.EnumConvertor; +import com.laytonsmith.abstraction.enums.MCEquipmentSlot; +import com.laytonsmith.annotations.abstractionenum; +import org.bukkit.inventory.EquipmentSlot; + +@abstractionenum( + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCEquipmentSlot.class, + forConcreteEnum = EquipmentSlot.class +) +public class BukkitMCEquipmentSlot extends EnumConvertor { + + private static BukkitMCEquipmentSlot instance; + + public static BukkitMCEquipmentSlot getConvertor() { + if(instance == null) { + instance = new BukkitMCEquipmentSlot(); + } + return instance; + } + + @Override + protected MCEquipmentSlot getAbstractedEnumCustom(EquipmentSlot concrete) { + switch(concrete) { + case HAND: + return MCEquipmentSlot.WEAPON; + case FEET: + return MCEquipmentSlot.BOOTS; + case LEGS: + return MCEquipmentSlot.LEGGINGS; + case CHEST: + return MCEquipmentSlot.CHESTPLATE; + case HEAD: + return MCEquipmentSlot.HELMET; + } + return super.getAbstractedEnumCustom(concrete); + } + + @Override + protected EquipmentSlot getConcreteEnumCustom(MCEquipmentSlot abstracted) { + switch(abstracted) { + case WEAPON: + return EquipmentSlot.HAND; + case BOOTS: + return EquipmentSlot.FEET; + case LEGGINGS: + return EquipmentSlot.LEGS; + case CHESTPLATE: + return EquipmentSlot.CHEST; + case HELMET: + return EquipmentSlot.HEAD; + } + return super.getConcreteEnumCustom(abstracted); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCFireworkType.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCFireworkType.java index 08d27fb0af..754e5b2169 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCFireworkType.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCFireworkType.java @@ -1,4 +1,3 @@ - package com.laytonsmith.abstraction.enums.bukkit; import com.laytonsmith.abstraction.Implementation; @@ -7,19 +6,17 @@ import com.laytonsmith.annotations.abstractionenum; import org.bukkit.FireworkEffect; -/** - * - * - */ @abstractionenum( implementation = Implementation.Type.BUKKIT, -forAbstractEnum = MCFireworkType.class, -forConcreteEnum = FireworkEffect.Type.class) + forAbstractEnum = MCFireworkType.class, + forConcreteEnum = FireworkEffect.Type.class +) public class BukkitMCFireworkType extends EnumConvertor { + private static com.laytonsmith.abstraction.enums.bukkit.BukkitMCFireworkType instance; public static com.laytonsmith.abstraction.enums.bukkit.BukkitMCFireworkType getConvertor() { - if (instance == null) { + if(instance == null) { instance = new com.laytonsmith.abstraction.enums.bukkit.BukkitMCFireworkType(); } return instance; diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCFishingState.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCFishingState.java index 57c87ac65f..eefafbb3dd 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCFishingState.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCFishingState.java @@ -7,16 +7,16 @@ import org.bukkit.event.player.PlayerFishEvent; @abstractionenum( - implementation= Implementation.Type.BUKKIT, - forAbstractEnum=MCFishingState.class, - forConcreteEnum=PlayerFishEvent.State.class + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCFishingState.class, + forConcreteEnum = PlayerFishEvent.State.class ) public class BukkitMCFishingState extends EnumConvertor { - + private static BukkitMCFishingState instance; - - public static BukkitMCFishingState getConvertor(){ - if(instance == null){ + + public static BukkitMCFishingState getConvertor() { + if(instance == null) { instance = new BukkitMCFishingState(); } return instance; diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCGameMode.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCGameMode.java index 6266ca2a1f..f7743a1b64 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCGameMode.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCGameMode.java @@ -1,4 +1,3 @@ - package com.laytonsmith.abstraction.enums.bukkit; import com.laytonsmith.abstraction.Implementation; @@ -7,19 +6,17 @@ import com.laytonsmith.annotations.abstractionenum; import org.bukkit.GameMode; -/** - * - * - */ @abstractionenum( implementation = Implementation.Type.BUKKIT, -forAbstractEnum = MCGameMode.class, -forConcreteEnum = GameMode.class) + forAbstractEnum = MCGameMode.class, + forConcreteEnum = GameMode.class +) public class BukkitMCGameMode extends EnumConvertor { + private static com.laytonsmith.abstraction.enums.bukkit.BukkitMCGameMode instance; public static com.laytonsmith.abstraction.enums.bukkit.BukkitMCGameMode getConvertor() { - if (instance == null) { + if(instance == null) { instance = new com.laytonsmith.abstraction.enums.bukkit.BukkitMCGameMode(); } return instance; diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCIgniteCause.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCIgniteCause.java index 9e8a830096..ec1bfab1fe 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCIgniteCause.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCIgniteCause.java @@ -6,10 +6,6 @@ import com.laytonsmith.annotations.abstractionenum; import org.bukkit.event.block.BlockIgniteEvent.IgniteCause; -/** - * - * @author MariuszT - */ @abstractionenum( implementation = Implementation.Type.BUKKIT, forAbstractEnum = MCIgniteCause.class, @@ -19,7 +15,7 @@ public class BukkitMCIgniteCause extends EnumConvertor { + private static com.laytonsmith.abstraction.enums.bukkit.BukkitMCInstrument instance; public static com.laytonsmith.abstraction.enums.bukkit.BukkitMCInstrument getConvertor() { - if (instance == null) { + if(instance == null) { instance = new com.laytonsmith.abstraction.enums.bukkit.BukkitMCInstrument(); } return instance; diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCInventoryAction.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCInventoryAction.java index 812008037a..cbc8334ef3 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCInventoryAction.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCInventoryAction.java @@ -6,21 +6,17 @@ import com.laytonsmith.annotations.abstractionenum; import org.bukkit.event.inventory.InventoryAction; -/** - * - * @author jb_aero - */ @abstractionenum( - implementation= Implementation.Type.BUKKIT, - forAbstractEnum=MCInventoryAction.class, - forConcreteEnum=InventoryAction.class + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCInventoryAction.class, + forConcreteEnum = InventoryAction.class ) -public class BukkitMCInventoryAction extends EnumConvertor{ +public class BukkitMCInventoryAction extends EnumConvertor { private static BukkitMCInventoryAction instance; - + public static BukkitMCInventoryAction getConvertor() { - if (instance == null) { + if(instance == null) { instance = new BukkitMCInventoryAction(); } return instance; diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCInventoryType.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCInventoryType.java index 26c5f8ca43..9e1582fa65 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCInventoryType.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCInventoryType.java @@ -1,4 +1,3 @@ - package com.laytonsmith.abstraction.enums.bukkit; import com.laytonsmith.abstraction.Implementation; @@ -7,19 +6,16 @@ import com.laytonsmith.annotations.abstractionenum; import org.bukkit.event.inventory.InventoryType; -/** - * - * - */ @abstractionenum( implementation = Implementation.Type.BUKKIT, -forAbstractEnum = MCInventoryType.class, -forConcreteEnum = InventoryType.class) + forAbstractEnum = MCInventoryType.class, + forConcreteEnum = InventoryType.class) public class BukkitMCInventoryType extends EnumConvertor { + private static com.laytonsmith.abstraction.enums.bukkit.BukkitMCInventoryType instance; public static com.laytonsmith.abstraction.enums.bukkit.BukkitMCInventoryType getConvertor() { - if (instance == null) { + if(instance == null) { instance = new com.laytonsmith.abstraction.enums.bukkit.BukkitMCInventoryType(); } return instance; diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCItemFlag.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCItemFlag.java new file mode 100644 index 0000000000..d52b6af3bd --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCItemFlag.java @@ -0,0 +1,42 @@ +package com.laytonsmith.abstraction.enums.bukkit; + +import com.laytonsmith.abstraction.Implementation; +import com.laytonsmith.abstraction.bukkit.BukkitMCServer; +import com.laytonsmith.abstraction.enums.EnumConvertor; +import com.laytonsmith.abstraction.enums.MCItemFlag; +import com.laytonsmith.abstraction.enums.MCVersion; +import com.laytonsmith.annotations.abstractionenum; +import com.laytonsmith.core.Static; +import org.bukkit.inventory.ItemFlag; + +/** + * A BukkitMCItemFlag can hide some Attributes from BukkitMCItemStacks, through BukkitMCItemMeta. + */ +@abstractionenum( + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCItemFlag.class, + forConcreteEnum = ItemFlag.class +) +public class BukkitMCItemFlag extends EnumConvertor { + + private static BukkitMCItemFlag instance; + + public static BukkitMCItemFlag getConvertor() { + if(instance == null) { + instance = new BukkitMCItemFlag(); + } + return instance; + } + + @Override + protected ItemFlag getConcreteEnumCustom(MCItemFlag abstracted) { + if((abstracted == MCItemFlag.HIDE_ADDITIONAL_TOOLTIP || abstracted == MCItemFlag.HIDE_STORED_ENCHANTS) + && Static.getServer().getMinecraftVersion().lt(MCVersion.MC1_20_6)) { + return ItemFlag.valueOf("HIDE_POTION_EFFECTS"); + } else if(abstracted == MCItemFlag.HIDE_STORED_ENCHANTS + && !((BukkitMCServer) Static.getServer()).isPaper()) { + return ItemFlag.HIDE_ADDITIONAL_TOOLTIP; + } + return ItemFlag.valueOf(abstracted.name()); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCLegacyMaterial.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCLegacyMaterial.java new file mode 100644 index 0000000000..782c990138 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCLegacyMaterial.java @@ -0,0 +1,596 @@ +package com.laytonsmith.abstraction.enums.bukkit; + +import com.laytonsmith.abstraction.enums.MCVersion; +import com.laytonsmith.core.Static; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.block.data.BlockData; + +import java.util.HashMap; + +@SuppressWarnings("deprecation") +public class BukkitMCLegacyMaterial { + public enum LegacyMaterialId { + AIR(0), + STONE(1), + GRASS(2), + DIRT(3), + COBBLESTONE(4), + WOOD(5), + SAPLING(6), + BEDROCK(7), + WATER(8), + STATIONARY_WATER(9), + LAVA(10), + STATIONARY_LAVA(11), + SAND(12), + GRAVEL(13), + GOLD_ORE(14), + IRON_ORE(15), + COAL_ORE(16), + LOG(17), + LEAVES(18), + SPONGE(19), + GLASS(20), + LAPIS_ORE(21), + LAPIS_BLOCK(22), + DISPENSER(23), + SANDSTONE(24), + NOTE_BLOCK(25), + BED_BLOCK(26), + POWERED_RAIL(27), + DETECTOR_RAIL(28), + PISTON_STICKY_BASE(29), + WEB(30), + LONG_GRASS(31), + DEAD_BUSH(32), + PISTON_BASE(33), + PISTON_EXTENSION(34), + WOOL(35), + PISTON_MOVING_PIECE(36), + YELLOW_FLOWER(37), + RED_ROSE(38), + BROWN_MUSHROOM(39), + RED_MUSHROOM(40), + GOLD_BLOCK(41), + IRON_BLOCK(42), + DOUBLE_STEP(43), + STEP(44), + BRICK(45), + TNT(46), + BOOKSHELF(47), + MOSSY_COBBLESTONE(48), + OBSIDIAN(49), + TORCH(50), + FIRE(51), + MOB_SPAWNER(52), + WOOD_STAIRS(53), + CHEST(54), + REDSTONE_WIRE(55), + DIAMOND_ORE(56), + DIAMOND_BLOCK(57), + WORKBENCH(58), + CROPS(59), + SOIL(60), + FURNACE(61), + BURNING_FURNACE(62), + SIGN_POST(63), + WOODEN_DOOR(64), + LADDER(65), + RAILS(66), + COBBLESTONE_STAIRS(67), + WALL_SIGN(68), + LEVER(69), + STONE_PLATE(70), + IRON_DOOR_BLOCK(71), + WOOD_PLATE(72), + REDSTONE_ORE(73), + GLOWING_REDSTONE_ORE(74), + REDSTONE_TORCH_OFF(75), + REDSTONE_TORCH_ON(76), + STONE_BUTTON(77), + SNOW(78), + ICE(79), + SNOW_BLOCK(80), + CACTUS(81), + CLAY(82), + SUGAR_CANE_BLOCK(83), + JUKEBOX(84), + FENCE(85), + PUMPKIN(86), + NETHERRACK(87), + SOUL_SAND(88), + GLOWSTONE(89), + PORTAL(90), + JACK_O_LANTERN(91), + CAKE_BLOCK(92), + DIODE_BLOCK_OFF(93), + DIODE_BLOCK_ON(94), + STAINED_GLASS(95), + TRAP_DOOR(96), + MONSTER_EGGS(97), + SMOOTH_BRICK(98), + HUGE_MUSHROOM_1(99), + HUGE_MUSHROOM_2(100), + IRON_FENCE(101), + THIN_GLASS(102), + MELON_BLOCK(103), + PUMPKIN_STEM(104), + MELON_STEM(105), + VINE(106), + FENCE_GATE(107), + BRICK_STAIRS(108), + SMOOTH_STAIRS(109), + MYCEL(110), + WATER_LILY(111), + NETHER_BRICK(112), + NETHER_FENCE(113), + NETHER_BRICK_STAIRS(114), + NETHER_WARTS(115), + ENCHANTMENT_TABLE(116), + BREWING_STAND(117), + CAULDRON(118), + ENDER_PORTAL(119), + ENDER_PORTAL_FRAME(120), + ENDER_STONE(121), + DRAGON_EGG(122), + REDSTONE_LAMP_OFF(123), + REDSTONE_LAMP_ON(124), + WOOD_DOUBLE_STEP(125), + WOOD_STEP(126), + COCOA(127), + SANDSTONE_STAIRS(128), + EMERALD_ORE(129), + ENDER_CHEST(130), + TRIPWIRE_HOOK(131), + TRIPWIRE(132), + EMERALD_BLOCK(133), + SPRUCE_WOOD_STAIRS(134), + BIRCH_WOOD_STAIRS(135), + JUNGLE_WOOD_STAIRS(136), + COMMAND(137), + BEACON(138), + COBBLE_WALL(139), + FLOWER_POT(140), + CARROT(141), + POTATO(142), + WOOD_BUTTON(143), + SKULL(144), + ANVIL(145), + TRAPPED_CHEST(146), + GOLD_PLATE(147), + IRON_PLATE(148), + REDSTONE_COMPARATOR_OFF(149), + REDSTONE_COMPARATOR_ON(150), + DAYLIGHT_DETECTOR(151), + REDSTONE_BLOCK(152), + QUARTZ_ORE(153), + HOPPER(154), + QUARTZ_BLOCK(155), + QUARTZ_STAIRS(156), + ACTIVATOR_RAIL(157), + DROPPER(158), + STAINED_CLAY(159), + STAINED_GLASS_PANE(160), + LEAVES_2(161), + LOG_2(162), + ACACIA_STAIRS(163), + DARK_OAK_STAIRS(164), + SLIME_BLOCK(165), + BARRIER(166), + IRON_TRAPDOOR(167), + PRISMARINE(168), + SEA_LANTERN(169), + HAY_BLOCK(170), + CARPET(171), + HARD_CLAY(172), + COAL_BLOCK(173), + PACKED_ICE(174), + DOUBLE_PLANT(175), + STANDING_BANNER(176), + WALL_BANNER(177), + DAYLIGHT_DETECTOR_INVERTED(178), + RED_SANDSTONE(179), + RED_SANDSTONE_STAIRS(180), + DOUBLE_STONE_SLAB2(181), + STONE_SLAB2(182), + SPRUCE_FENCE_GATE(183), + BIRCH_FENCE_GATE(184), + JUNGLE_FENCE_GATE(185), + DARK_OAK_FENCE_GATE(186), + ACACIA_FENCE_GATE(187), + SPRUCE_FENCE(188), + BIRCH_FENCE(189), + JUNGLE_FENCE(190), + DARK_OAK_FENCE(191), + ACACIA_FENCE(192), + SPRUCE_DOOR(193), + BIRCH_DOOR(194), + JUNGLE_DOOR(195), + ACACIA_DOOR(196), + DARK_OAK_DOOR(197), + END_ROD(198), + CHORUS_PLANT(199), + CHORUS_FLOWER(200), + PURPUR_BLOCK(201), + PURPUR_PILLAR(202), + PURPUR_STAIRS(203), + PURPUR_DOUBLE_SLAB(204), + PURPUR_SLAB(205), + END_BRICKS(206), + BEETROOT_BLOCK(207), + GRASS_PATH(208), + END_GATEWAY(209), + COMMAND_REPEATING(210), + COMMAND_CHAIN(211), + FROSTED_ICE(212), + MAGMA(213), + NETHER_WART_BLOCK(214), + RED_NETHER_BRICK(215), + BONE_BLOCK(216), + STRUCTURE_VOID(217), + OBSERVER(218), + WHITE_SHULKER_BOX(219), + ORANGE_SHULKER_BOX(220), + MAGENTA_SHULKER_BOX(221), + LIGHT_BLUE_SHULKER_BOX(222), + YELLOW_SHULKER_BOX(223), + LIME_SHULKER_BOX(224), + PINK_SHULKER_BOX(225), + GRAY_SHULKER_BOX(226), + SILVER_SHULKER_BOX(227), + CYAN_SHULKER_BOX(228), + PURPLE_SHULKER_BOX(229), + BLUE_SHULKER_BOX(230), + BROWN_SHULKER_BOX(231), + GREEN_SHULKER_BOX(232), + RED_SHULKER_BOX(233), + BLACK_SHULKER_BOX(234), + WHITE_GLAZED_TERRACOTTA(235), + ORANGE_GLAZED_TERRACOTTA(236), + MAGENTA_GLAZED_TERRACOTTA(237), + LIGHT_BLUE_GLAZED_TERRACOTTA(238), + YELLOW_GLAZED_TERRACOTTA(239), + LIME_GLAZED_TERRACOTTA(240), + PINK_GLAZED_TERRACOTTA(241), + GRAY_GLAZED_TERRACOTTA(242), + SILVER_GLAZED_TERRACOTTA(243), + CYAN_GLAZED_TERRACOTTA(244), + PURPLE_GLAZED_TERRACOTTA(245), + BLUE_GLAZED_TERRACOTTA(246), + BROWN_GLAZED_TERRACOTTA(247), + GREEN_GLAZED_TERRACOTTA(248), + RED_GLAZED_TERRACOTTA(249), + BLACK_GLAZED_TERRACOTTA(250), + CONCRETE(251), + CONCRETE_POWDER(252), + STRUCTURE_BLOCK(255), + IRON_SPADE(256), + IRON_PICKAXE(257), + IRON_AXE(258), + FLINT_AND_STEEL(259), + APPLE(260), + BOW(261), + ARROW(262), + COAL(263), + DIAMOND(264), + IRON_INGOT(265), + GOLD_INGOT(266), + IRON_SWORD(267), + WOOD_SWORD(268), + WOOD_SPADE(269), + WOOD_PICKAXE(270), + WOOD_AXE(271), + STONE_SWORD(272), + STONE_SPADE(273), + STONE_PICKAXE(274), + STONE_AXE(275), + DIAMOND_SWORD(276), + DIAMOND_SPADE(277), + DIAMOND_PICKAXE(278), + DIAMOND_AXE(279), + STICK(280), + BOWL(281), + MUSHROOM_SOUP(282), + GOLD_SWORD(283), + GOLD_SPADE(284), + GOLD_PICKAXE(285), + GOLD_AXE(286), + STRING(287), + FEATHER(288), + SULPHUR(289), + WOOD_HOE(290), + STONE_HOE(291), + IRON_HOE(292), + DIAMOND_HOE(293), + GOLD_HOE(294), + SEEDS(295), + WHEAT(296), + BREAD(297), + LEATHER_HELMET(298), + LEATHER_CHESTPLATE(299), + LEATHER_LEGGINGS(300), + LEATHER_BOOTS(301), + CHAINMAIL_HELMET(302), + CHAINMAIL_CHESTPLATE(303), + CHAINMAIL_LEGGINGS(304), + CHAINMAIL_BOOTS(305), + IRON_HELMET(306), + IRON_CHESTPLATE(307), + IRON_LEGGINGS(308), + IRON_BOOTS(309), + DIAMOND_HELMET(310), + DIAMOND_CHESTPLATE(311), + DIAMOND_LEGGINGS(312), + DIAMOND_BOOTS(313), + GOLD_HELMET(314), + GOLD_CHESTPLATE(315), + GOLD_LEGGINGS(316), + GOLD_BOOTS(317), + FLINT(318), + PORK(319), + GRILLED_PORK(320), + PAINTING(321), + GOLDEN_APPLE(322), + SIGN(323), + WOOD_DOOR(324), + BUCKET(325), + WATER_BUCKET(326), + LAVA_BUCKET(327), + MINECART(328), + SADDLE(329), + IRON_DOOR(330), + REDSTONE(331), + SNOW_BALL(332), + BOAT(333), + LEATHER(334), + MILK_BUCKET(335), + CLAY_BRICK(336), + CLAY_BALL(337), + SUGAR_CANE(338), + PAPER(339), + BOOK(340), + SLIME_BALL(341), + STORAGE_MINECART(342), + POWERED_MINECART(343), + EGG(344), + COMPASS(345), + FISHING_ROD(346), + WATCH(347), + GLOWSTONE_DUST(348), + RAW_FISH(349), + COOKED_FISH(350), + INK_SACK(351), + BONE(352), + SUGAR(353), + CAKE(354), + BED(355), + DIODE(356), + COOKIE(357), + MAP(358), + SHEARS(359), + MELON(360), + PUMPKIN_SEEDS(361), + MELON_SEEDS(362), + RAW_BEEF(363), + COOKED_BEEF(364), + RAW_CHICKEN(365), + COOKED_CHICKEN(366), + ROTTEN_FLESH(367), + ENDER_PEARL(368), + BLAZE_ROD(369), + GHAST_TEAR(370), + GOLD_NUGGET(371), + NETHER_STALK(372), + POTION(373), + GLASS_BOTTLE(374), + SPIDER_EYE(375), + FERMENTED_SPIDER_EYE(376), + BLAZE_POWDER(377), + MAGMA_CREAM(378), + BREWING_STAND_ITEM(379), + CAULDRON_ITEM(380), + EYE_OF_ENDER(381), + SPECKLED_MELON(382), + MONSTER_EGG(383), + EXP_BOTTLE(384), + FIREBALL(385), + BOOK_AND_QUILL(386), + WRITTEN_BOOK(387), + EMERALD(388), + ITEM_FRAME(389), + FLOWER_POT_ITEM(390), + CARROT_ITEM(391), + POTATO_ITEM(392), + BAKED_POTATO(393), + POISONOUS_POTATO(394), + EMPTY_MAP(395), + GOLDEN_CARROT(396), + SKULL_ITEM(397), + CARROT_STICK(398), + NETHER_STAR(399), + PUMPKIN_PIE(400), + FIREWORK(401), + FIREWORK_CHARGE(402), + ENCHANTED_BOOK(403), + REDSTONE_COMPARATOR(404), + NETHER_BRICK_ITEM(405), + QUARTZ(406), + EXPLOSIVE_MINECART(407), + HOPPER_MINECART(408), + PRISMARINE_SHARD(409), + PRISMARINE_CRYSTALS(410), + RABBIT(411), + COOKED_RABBIT(412), + RABBIT_STEW(413), + RABBIT_FOOT(414), + RABBIT_HIDE(415), + ARMOR_STAND(416), + IRON_BARDING(417), + GOLD_BARDING(418), + DIAMOND_BARDING(419), + LEASH(420), + NAME_TAG(421), + COMMAND_MINECART(422), + MUTTON(423), + COOKED_MUTTON(424), + BANNER(425), + END_CRYSTAL(426), + SPRUCE_DOOR_ITEM(427), + BIRCH_DOOR_ITEM(428), + JUNGLE_DOOR_ITEM(429), + ACACIA_DOOR_ITEM(430), + DARK_OAK_DOOR_ITEM(431), + CHORUS_FRUIT(432), + CHORUS_FRUIT_POPPED(433), + BEETROOT(434), + BEETROOT_SEEDS(435), + BEETROOT_SOUP(436), + DRAGONS_BREATH(437), + SPLASH_POTION(438), + SPECTRAL_ARROW(439), + TIPPED_ARROW(440), + LINGERING_POTION(441), + SHIELD(442), + ELYTRA(443), + BOAT_SPRUCE(444), + BOAT_BIRCH(445), + BOAT_JUNGLE(446), + BOAT_ACACIA(447), + BOAT_DARK_OAK(448), + TOTEM(449), + SHULKER_SHELL(450), + IRON_NUGGET(452), + KNOWLEDGE_BOOK(453), + GOLD_RECORD(2256), + GREEN_RECORD(2257), + RECORD_3(2258), + RECORD_4(2259), + RECORD_5(2260), + RECORD_6(2261), + RECORD_7(2262), + RECORD_8(2263), + RECORD_9(2264), + RECORD_10(2265), + RECORD_11(2266), + RECORD_12(2267); + + private final int id; + + LegacyMaterialId(int id) { + this.id = id; + } + + public int getId() { + return id; + } + } + + /** + * All material name changes after 1.13, as they are absent in Spigot's legacy material lookup. + */ + public enum LegacyMaterialName { + CACTUS_GREEN("GREEN_DYE", MCVersion.MC1_14), + DANDELION_YELLOW("YELLOW_DYE", MCVersion.MC1_14), + ROSE_RED("RED_DYE", MCVersion.MC1_14), + SIGN("OAK_SIGN", MCVersion.MC1_14), + WALL_SIGN("OAK_WALL_SIGN", MCVersion.MC1_14), + ZOMBIE_PIGMAN_SPAWN_EGG("ZOMBIFIED_PIGLIN_SPAWN_EGG", MCVersion.MC1_16), + GRASS_PATH("DIRT_PATH", MCVersion.MC1_17), + POTTERY_SHARD_ARCHER("ARCHER_POTTERY_SHERD", MCVersion.MC1_20), + POTTERY_SHARD_ARMS_UP("ARMS_UP_POTTERY_SHERD", MCVersion.MC1_20), + POTTERY_SHARD_PRIZE("PRIZE_POTTERY_SHERD", MCVersion.MC1_20), + POTTERY_SHARD_SKULL("SKULL_POTTERY_SHERD", MCVersion.MC1_20), + GRASS("SHORT_GRASS", MCVersion.MC1_20_4), + SCUTE("TURTLE_SCUTE", MCVersion.MC1_20_6), + CHAIN("IRON_CHAIN", MCVersion.MC1_21_9); + + private final String mat; + private final MCVersion version; + + LegacyMaterialName(String mat, MCVersion changed) { + this.mat = mat; + this.version = changed; + } + + public String getNewMaterial() { + return mat; + } + + public MCVersion getVersion() { + return version; + } + } + + private static final HashMap BY_NAME = new HashMap<>(); + private static final HashMap BY_ID = new HashMap<>(); + + public static void build() { + for(LegacyMaterialId mat : LegacyMaterialId.values()) { + BY_ID.put(mat.getId(), mat); + } + MCVersion thisVersion = Static.getServer().getMinecraftVersion(); + for(LegacyMaterialName mat : LegacyMaterialName.values()) { + if(thisVersion.gte(mat.getVersion())) { + BY_NAME.put(mat.name(), Material.getMaterial(mat.getNewMaterial())); + } + } + } + + public static Material getMaterial(int id) { + LegacyMaterialId legacy = BY_ID.get(id); + if(legacy == null) { + return null; + } + return getMaterial(legacy.name()); + } + + public static Material getMaterial(String name) { + Material mat = BY_NAME.get(name); + if(mat == null) { + mat = Material.getMaterial(name, true); + // we should never get air here since we're checking for legacy materials only + if(mat == Material.AIR) { + return null; + } + } + return mat; + } + + public static Material getMaterial(int id, int data) { + LegacyMaterialId legacy = BY_ID.get(id); + if(legacy == null) { + return null; + } + return getMaterial(legacy.name(), data); + } + + public static Material getMaterial(String type, int data) { + return getMaterial(Material.getMaterial("LEGACY_" + type), data); + } + + public static Material getMaterial(Material legacymat, int data) { + if(legacymat == null) { + return null; + } + if(legacymat.getMaxDurability() == 0) { + if(legacymat.equals(Material.LEGACY_ANVIL)) { + // special cases where we prioritize item conversion + return Bukkit.getUnsafe().fromLegacy(new org.bukkit.material.MaterialData(legacymat, (byte) data), true); + } + return Bukkit.getUnsafe().fromLegacy(new org.bukkit.material.MaterialData(legacymat, (byte) data)); + } else { + // ignore data when it's actually durability + return Bukkit.getUnsafe().fromLegacy(legacymat); + } + } + + public static BlockData getBlockData(int id, int data) { + LegacyMaterialId legacy = BY_ID.get(id); + if(legacy == null) { + return Material.AIR.createBlockData(); + } + Material mat = Material.getMaterial("LEGACY_" + legacy.name()); + return Bukkit.getUnsafe().fromLegacy(mat, (byte) data); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCOcelotType.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCOcelotType.java deleted file mode 100644 index d7d47eb32a..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCOcelotType.java +++ /dev/null @@ -1,29 +0,0 @@ - -package com.laytonsmith.abstraction.enums.bukkit; - -import com.laytonsmith.abstraction.Implementation; -import com.laytonsmith.abstraction.enums.EnumConvertor; -import com.laytonsmith.abstraction.enums.MCOcelotType; -import com.laytonsmith.annotations.abstractionenum; -import org.bukkit.entity.Ocelot; - -/** - * - * @author jb_aero - */ -@abstractionenum( - implementation=Implementation.Type.BUKKIT, - forAbstractEnum=MCOcelotType.class, - forConcreteEnum=Ocelot.Type.class - ) -public class BukkitMCOcelotType extends EnumConvertor { - - private static com.laytonsmith.abstraction.enums.bukkit.BukkitMCOcelotType instance; - - public static com.laytonsmith.abstraction.enums.bukkit.BukkitMCOcelotType getConvertor() { - if (instance == null) { - instance = new com.laytonsmith.abstraction.enums.bukkit.BukkitMCOcelotType(); - } - return instance; - } -} \ No newline at end of file diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCOption.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCOption.java new file mode 100644 index 0000000000..5eaa1e47e0 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCOption.java @@ -0,0 +1,26 @@ +package com.laytonsmith.abstraction.enums.bukkit; + +import org.bukkit.scoreboard.Team; +import org.bukkit.scoreboard.Team.Option; + +import com.laytonsmith.abstraction.Implementation; +import com.laytonsmith.abstraction.enums.EnumConvertor; +import com.laytonsmith.abstraction.enums.MCOption; +import com.laytonsmith.annotations.abstractionenum; + +@abstractionenum( + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCOption.class, + forConcreteEnum = Team.Option.class +) +public class BukkitMCOption extends EnumConvertor { + + private static BukkitMCOption instance; + + public static BukkitMCOption getConvertor() { + if(instance == null) { + instance = new BukkitMCOption(); + } + return instance; + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCOptionStatus.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCOptionStatus.java new file mode 100644 index 0000000000..7c46e5d91f --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCOptionStatus.java @@ -0,0 +1,26 @@ +package com.laytonsmith.abstraction.enums.bukkit; + +import org.bukkit.scoreboard.Team; +import org.bukkit.scoreboard.Team.OptionStatus; + +import com.laytonsmith.abstraction.Implementation; +import com.laytonsmith.abstraction.enums.EnumConvertor; +import com.laytonsmith.abstraction.enums.MCOptionStatus; +import com.laytonsmith.annotations.abstractionenum; + +@abstractionenum( + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCOptionStatus.class, + forConcreteEnum = Team.OptionStatus.class +) +public class BukkitMCOptionStatus extends EnumConvertor { + + private static BukkitMCOptionStatus instance; + + public static BukkitMCOptionStatus getConvertor() { + if(instance == null) { + instance = new BukkitMCOptionStatus(); + } + return instance; + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCParrotType.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCParrotType.java new file mode 100644 index 0000000000..3ee1e45a36 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCParrotType.java @@ -0,0 +1,24 @@ +package com.laytonsmith.abstraction.enums.bukkit; + +import com.laytonsmith.abstraction.Implementation; +import com.laytonsmith.abstraction.enums.EnumConvertor; +import com.laytonsmith.abstraction.enums.MCParrotType; +import com.laytonsmith.annotations.abstractionenum; +import org.bukkit.entity.Parrot; + +@abstractionenum( + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCParrotType.class, + forConcreteEnum = Parrot.Variant.class +) +public class BukkitMCParrotType extends EnumConvertor { + + private static BukkitMCParrotType instance; + + public static BukkitMCParrotType getConvertor() { + if(instance == null) { + instance = new BukkitMCParrotType(); + } + return instance; + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCParticle.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCParticle.java new file mode 100644 index 0000000000..4ab869d37b --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCParticle.java @@ -0,0 +1,200 @@ +package com.laytonsmith.abstraction.enums.bukkit; + +import com.laytonsmith.abstraction.MCColor; +import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.MCLocation; +import com.laytonsmith.abstraction.MCParticleData; +import com.laytonsmith.abstraction.blocks.MCBlockData; +import com.laytonsmith.abstraction.bukkit.BukkitMCColor; +import com.laytonsmith.abstraction.bukkit.BukkitMCVibration; +import com.laytonsmith.abstraction.enums.MCParticle; +import com.laytonsmith.abstraction.enums.MCVersion; +import com.laytonsmith.core.MSLog; +import com.laytonsmith.core.MSLog.Tags; +import com.laytonsmith.core.Static; +import com.laytonsmith.core.constructs.Target; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.inventory.ItemStack; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.EnumMap; +import java.util.Map; +import java.util.Random; + +public class BukkitMCParticle extends MCParticle { + + private static final Map BUKKIT_MAP = new EnumMap<>(Particle.class); + + public BukkitMCParticle(MCVanillaParticle vanillaParticle, Particle particle) { + super(vanillaParticle, particle); + } + + @Override + public String name() { + return getAbstracted() == MCVanillaParticle.UNKNOWN ? getConcrete().name() : getAbstracted().name(); + } + + public static MCParticle valueOfConcrete(Particle test) { + MCParticle type = BUKKIT_MAP.get(test); + if(type == null) { + MSLog.GetLogger().e(MSLog.Tags.GENERAL, "Bukkit Particle missing in BUKKIT_MAP: " + test.name(), Target.UNKNOWN); + return new BukkitMCParticle(MCVanillaParticle.UNKNOWN, test); + } + return type; + } + + public static void build() { + for(MCVanillaParticle v : MCVanillaParticle.values()) { + if(v.existsIn(Static.getServer().getMinecraftVersion())) { + Particle type; + try { + type = Particle.valueOf(v.name()); + } catch (IllegalArgumentException | NoSuchFieldError ex) { + MSLog.GetLogger().w(Tags.GENERAL, "Could not find a Bukkit Particle for " + v.name(), Target.UNKNOWN); + continue; + } + BukkitMCParticle wrapper = new BukkitMCParticle(v, type); + BUKKIT_MAP.put(type, wrapper); + MAP.put(v.name(), wrapper); + } + } + for(Particle p : Particle.values()) { + if(!p.name().startsWith("LEGACY_") && !BUKKIT_MAP.containsKey(p)) { + MSLog.GetLogger().w(Tags.GENERAL, "Could not find MCParticle for " + p.name(), Target.UNKNOWN); + MCParticle wrapper = new BukkitMCParticle(MCVanillaParticle.UNKNOWN, p); + MAP.put(p.name(), wrapper); + BUKKIT_MAP.put(p, wrapper); + } + } + } + + public Object getParticleData(MCLocation l, Object data) { + switch(getAbstracted()) { + case BLOCK_DUST: + case BLOCK_CRACK: + case BLOCK_CRUMBLE: + case BLOCK_MARKER: + case DUST_PILLAR: + case FALLING_DUST: + if(data instanceof MCBlockData) { + return ((MCBlockData) data).getHandle(); + } else if(getAbstracted() == MCVanillaParticle.BLOCK_MARKER) { + // Barrier (and light) particles were replaced by block markers, so this is the best fallback. + return Material.BARRIER.createBlockData(); + } else { + return Material.STONE.createBlockData(); + } + case ITEM_CRACK: + ItemStack is; + if(data instanceof MCItemStack) { + is = (ItemStack) ((MCItemStack) data).getHandle(); + } else { + is = new ItemStack(Material.STONE, 1); + } + return is; + case SPELL_MOB: + case TINTED_LEAVES: + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_20_6)) { + if(data instanceof MCColor) { + return BukkitMCColor.GetColor((MCColor) data); + } else { + return Color.WHITE; + } + } + break; + case FLASH: + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_21_9)) { + if(data instanceof MCColor) { + return BukkitMCColor.GetColor((MCColor) data); + } else { + return Color.WHITE; + } + } + break; + case REDSTONE: + if(data instanceof MCColor) { + return new Particle.DustOptions(BukkitMCColor.GetColor((MCColor) data), 1.0F); + } else { + return new Particle.DustOptions(Color.RED, 1.0F); + } + case DUST_COLOR_TRANSITION: + if(data instanceof MCParticleData.DustTransition transition) { + return new Particle.DustTransition(BukkitMCColor.GetColor(transition.from()), + BukkitMCColor.GetColor(transition.to()), 1.0F); + } else { + return new Particle.DustTransition(Color.TEAL, Color.RED, 1.0F); + } + case VIBRATION: + BukkitMCVibration vibe; + if(data instanceof MCParticleData.VibrationBlockDestination destination) { + vibe = new BukkitMCVibration(l, destination.location(), destination.arrivalTime()); + } else if(data instanceof MCParticleData.VibrationEntityDestination destination) { + vibe = new BukkitMCVibration(l, destination.entity(), destination.arrivalTime()); + } else { + vibe = new BukkitMCVibration(l, l, 5); + } + return vibe.getHandle(); + case SCULK_CHARGE: + if(data instanceof Float) { + return data; + } else { + return 0.0F; + } + case DRAGON_BREATH: + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_21_9)) { + if(data instanceof Float) { + return data; + } else { + return 1.0F; + } + } + break; + case SHRIEK: + if(data instanceof Integer) { + return data; + } else { + return 0; + } + case TRAIL: + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_21_4)) { + if(data instanceof MCParticleData.Trail trail) { + return new Particle.Trail((Location) trail.location().getHandle(), + BukkitMCColor.GetColor(trail.color()), trail.duration()); + } else { + return new Particle.Trail((Location) l.getHandle(), Color.fromRGB(252, 120, 18), + new Random().nextInt(40) + 10); + } + } else { + // 1.21.3 only + try { + Class clazz = Class.forName("org.bukkit.Particle$TargetColor"); + Constructor constructor = clazz.getConstructor(Location.class, Color.class); + constructor.setAccessible(true); + if(data instanceof MCParticleData.Trail trail) { + return constructor.newInstance((Location) trail.location().getHandle(), + BukkitMCColor.GetColor(trail.color())); + } else { + return constructor.newInstance((Location) l.getHandle(), Color.fromRGB(252, 120, 18)); + } + } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException + | IllegalAccessException | InvocationTargetException ignore) {} + } + break; + case SPELL: + case SPELL_INSTANT: + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_21_9)) { + if(data instanceof MCParticleData.Spell spellData) { + return new Particle.Spell(BukkitMCColor.GetColor(spellData.color()), spellData.power()); + } else { + return new Particle.Spell(Color.WHITE, 1.0F); + } + } + break; + } + return null; + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCPatternShape.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCPatternShape.java new file mode 100644 index 0000000000..61bdabefb4 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCPatternShape.java @@ -0,0 +1,113 @@ +package com.laytonsmith.abstraction.enums.bukkit; + +import com.laytonsmith.PureUtilities.Common.ReflectionUtils; +import com.laytonsmith.PureUtilities.Common.ReflectionUtils.ReflectionException; +import com.laytonsmith.abstraction.enums.MCPatternShape; +import com.laytonsmith.core.MSLog; +import com.laytonsmith.core.Static; +import com.laytonsmith.core.constructs.Target; +import org.bukkit.Keyed; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; +import org.bukkit.block.banner.PatternType; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +public class BukkitMCPatternShape extends MCPatternShape { + + private static final Map BUKKIT_MAP = new HashMap<>(); + + public BukkitMCPatternShape(MCVanillaPatternShape vanillaPatternShape, PatternType pattern) { + super(vanillaPatternShape, pattern); + } + + @Override + public String name() { + if(getAbstracted() == MCVanillaPatternShape.UNKNOWN) { + // changed from enum to interface in 1.21, so cannot call methods from PatternType + try { + NamespacedKey key = ReflectionUtils.invokeMethod(Keyed.class, getConcrete(), "getKey"); + return key.getKey().toUpperCase(Locale.ROOT); + } catch(ReflectionException ex) { + // probably before 1.20.4, so something went wrong + MSLog.GetLogger().e(MSLog.Tags.GENERAL, "Could not resolve unknown PatternType", Target.UNKNOWN); + } + } + return getAbstracted().name(); + } + + public static MCPatternShape valueOfConcrete(PatternType test) { + MCPatternShape type = BUKKIT_MAP.get(test); + if(type == null) { + MSLog.GetLogger().w(MSLog.Tags.GENERAL, "PatternType missing in BUKKIT_MAP: " + test, Target.UNKNOWN); + return new BukkitMCPatternShape(MCVanillaPatternShape.UNKNOWN, test); + } + return type; + } + + public static void build() { + for(MCVanillaPatternShape v : MCVanillaPatternShape.values()) { + if(v.existsIn(Static.getServer().getMinecraftVersion())) { + PatternType type; + try { + type = getBukkitType(v); + } catch (IllegalArgumentException ex) { + MSLog.GetLogger().w(MSLog.Tags.GENERAL, "Could not find Bukkit PatternType for " + v.name(), + Target.UNKNOWN); + continue; + } + BukkitMCPatternShape wrapper = new BukkitMCPatternShape(v, type); + BUKKIT_MAP.put(type, wrapper); + MAP.put(v.name(), wrapper); + } + } + try { + for(PatternType type : Registry.BANNER_PATTERN) { + if(!BUKKIT_MAP.containsKey(type)) { + String name = type.getKey().getKey().toUpperCase(Locale.ROOT); + MSLog.GetLogger().w(MSLog.Tags.GENERAL, "Could not find MCPatternShape for " + name, + Target.UNKNOWN); + MCPatternShape wrapper = new BukkitMCPatternShape(MCVanillaPatternShape.UNKNOWN, type); + MAP.put(name, wrapper); + BUKKIT_MAP.put(type, wrapper); + } + } + } catch (IncompatibleClassChangeError ignore) { + // probably before 1.20.4 so we do not have to check for new missing values + } + } + + private static PatternType getBukkitType(MCVanillaPatternShape v) throws IllegalArgumentException { + // changed from enum to interface in 1.21, so cannot call methods from PatternType + try { + String typeName = v.name(); + typeName = switch(typeName) { + case "DIAGONAL_RIGHT_MIRROR" -> "diagonal_right"; + case "DIAGONAL_RIGHT" -> "diagonal_up_right"; + case "STRIPE_SMALL" -> "small_stripes"; + case "DIAGONAL_LEFT_MIRROR" -> "diagonal_up_left"; + case "CIRCLE_MIDDLE" -> "circle"; + case "RHOMBUS_MIDDLE" -> "rhombus"; + case "HALF_VERTICAL_MIRROR" -> "half_vertical_right"; + case "HALF_HORIZONTAL_MIRROR" -> "half_horizontal_bottom"; + default -> typeName.toLowerCase(Locale.ROOT); + }; + PatternType t = Registry.BANNER_PATTERN.get(NamespacedKey.minecraft(typeName)); + if(t == null) { + throw new IllegalArgumentException(); + } + return t; + } catch(NoSuchFieldError ex) { + // probably before 1.20.4 when registry field was added + try { + Class cls = Class.forName("org.bukkit.block.banner.PatternType"); + return ReflectionUtils.invokeMethod(cls, null, "valueOf", + new Class[]{String.class}, new Object[]{v.name()}); + } catch (ClassNotFoundException exc) { + throw new IllegalArgumentException(); + } + } + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCPlayerStatistic.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCPlayerStatistic.java new file mode 100644 index 0000000000..7a5663dcaa --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCPlayerStatistic.java @@ -0,0 +1,24 @@ +package com.laytonsmith.abstraction.enums.bukkit; + +import com.laytonsmith.abstraction.Implementation; +import com.laytonsmith.abstraction.enums.EnumConvertor; +import com.laytonsmith.abstraction.enums.MCPlayerStatistic; +import com.laytonsmith.annotations.abstractionenum; +import org.bukkit.Statistic; + +@abstractionenum( + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCPlayerStatistic.class, + forConcreteEnum = Statistic.class +) +public class BukkitMCPlayerStatistic extends EnumConvertor { + + private static BukkitMCPlayerStatistic instance; + + public static BukkitMCPlayerStatistic getConvertor() { + if(instance == null) { + instance = new BukkitMCPlayerStatistic(); + } + return instance; + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCPose.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCPose.java new file mode 100644 index 0000000000..e59f7aefd6 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCPose.java @@ -0,0 +1,24 @@ +package com.laytonsmith.abstraction.enums.bukkit; + +import com.laytonsmith.abstraction.Implementation; +import com.laytonsmith.abstraction.enums.EnumConvertor; +import com.laytonsmith.abstraction.enums.MCPose; +import com.laytonsmith.annotations.abstractionenum; +import org.bukkit.entity.Pose; + +@abstractionenum( + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCPose.class, + forConcreteEnum = Pose.class +) +public class BukkitMCPose extends EnumConvertor { + + private static BukkitMCPose instance; + + public static BukkitMCPose getConvertor() { + if(instance == null) { + instance = new BukkitMCPose(); + } + return instance; + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCPotionAction.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCPotionAction.java new file mode 100644 index 0000000000..d597bdfd81 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCPotionAction.java @@ -0,0 +1,24 @@ +package com.laytonsmith.abstraction.enums.bukkit; + +import com.laytonsmith.abstraction.Implementation; +import com.laytonsmith.abstraction.enums.EnumConvertor; +import com.laytonsmith.abstraction.enums.MCPotionAction; +import com.laytonsmith.annotations.abstractionenum; +import org.bukkit.event.entity.EntityPotionEffectEvent; + +@abstractionenum( + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCPotionAction.class, + forConcreteEnum = EntityPotionEffectEvent.Action.class +) +public class BukkitMCPotionAction extends EnumConvertor { + + private static BukkitMCPotionAction instance; + + public static BukkitMCPotionAction getConvertor() { + if(instance == null) { + instance = new BukkitMCPotionAction(); + } + return instance; + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCPotionCause.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCPotionCause.java new file mode 100644 index 0000000000..e927906233 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCPotionCause.java @@ -0,0 +1,25 @@ +package com.laytonsmith.abstraction.enums.bukkit; + +import com.laytonsmith.abstraction.Implementation; +import com.laytonsmith.abstraction.enums.EnumConvertor; +import com.laytonsmith.abstraction.enums.MCPotionCause; +import com.laytonsmith.annotations.abstractionenum; +import org.bukkit.event.entity.EntityPotionEffectEvent; + +@abstractionenum( + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCPotionCause.class, + forConcreteEnum = EntityPotionEffectEvent.Cause.class +) +public class BukkitMCPotionCause extends EnumConvertor { + + private static BukkitMCPotionCause instance; + + public static BukkitMCPotionCause getConvertor() { + if(instance == null) { + instance = new BukkitMCPotionCause(); + } + return instance; + } +} + diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCPotionEffectType.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCPotionEffectType.java new file mode 100644 index 0000000000..9e1d2089b2 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCPotionEffectType.java @@ -0,0 +1,89 @@ +package com.laytonsmith.abstraction.enums.bukkit; + +import com.laytonsmith.abstraction.enums.MCPotionEffectType; +import com.laytonsmith.abstraction.enums.MCVersion; +import com.laytonsmith.core.MSLog; +import com.laytonsmith.core.Static; +import com.laytonsmith.core.constructs.Target; +import org.bukkit.potion.PotionEffectType; + +import java.util.HashMap; +import java.util.Map; + +public class BukkitMCPotionEffectType extends MCPotionEffectType { + + private static final Map BUKKIT_MAP = new HashMap<>(); + + public BukkitMCPotionEffectType(MCVanillaPotionEffectType vanillaEffect, PotionEffectType effect) { + super(vanillaEffect, effect); + } + + @Override + public String name() { + return getAbstracted() == MCVanillaPotionEffectType.UNKNOWN ? getConcrete().getName() : getAbstracted().name(); + } + + public static MCPotionEffectType valueOfConcrete(PotionEffectType test) { + MCPotionEffectType type = BUKKIT_MAP.get(test); + if(type == null) { + MSLog.GetLogger().e(MSLog.Tags.GENERAL, "Bukkit PotionEffectType missing in BUKKIT_MAP: " + test.getName(), + Target.UNKNOWN); + return new BukkitMCPotionEffectType(MCVanillaPotionEffectType.UNKNOWN, test); + } + return type; + } + + public static void build() { + for(MCVanillaPotionEffectType v : MCVanillaPotionEffectType.values()) { + if(v.existsIn(Static.getServer().getMinecraftVersion())) { + PotionEffectType effect = getBukkitType(v); + if(effect == null) { + MSLog.GetLogger().w(MSLog.Tags.GENERAL, "Could not find a Bukkit potion effect type for " + v.name(), Target.UNKNOWN); + continue; + } + BukkitMCPotionEffectType wrapper = new BukkitMCPotionEffectType(v, effect); + MAP.put(v.name(), wrapper); + ID_MAP.put(v.getId(), wrapper); + BUKKIT_MAP.put(effect, wrapper); + } + } + for(PotionEffectType pe : PotionEffectType.values()) { + if(pe != null && !BUKKIT_MAP.containsKey(pe)) { + MSLog.GetLogger().w(MSLog.Tags.GENERAL, "Could not find MCPotionEffectTYpe for " + pe.getName(), Target.UNKNOWN); + MCPotionEffectType wrapper = new BukkitMCPotionEffectType(MCVanillaPotionEffectType.UNKNOWN, pe); + MAP.put(pe.getName(), wrapper); + ID_MAP.put(pe.getId(), wrapper); + BUKKIT_MAP.put(pe, wrapper); + } + } + } + + private static PotionEffectType getBukkitType(MCVanillaPotionEffectType v) { + if(Static.getServer().getMinecraftVersion().lt(MCVersion.MC1_20_6)) { + switch(v) { + case SLOWNESS: + return PotionEffectType.getByName("SLOW"); + case HASTE: + return PotionEffectType.getByName("FAST_DIGGING"); + case MINING_FATIGUE: + return PotionEffectType.getByName("SLOW_DIGGING"); + case STRENGTH: + return PotionEffectType.getByName("INCREASE_DAMAGE"); + case INSTANT_HEALTH: + return PotionEffectType.getByName("HEAL"); + case INSTANT_DAMAGE: + return PotionEffectType.getByName("HARM"); + case JUMP_BOOST: + return PotionEffectType.getByName("JUMP"); + case NAUSEA: + return PotionEffectType.getByName("CONFUSION"); + case RESISTANCE: + return PotionEffectType.getByName("DAMAGE_RESISTANCE"); + } + } + if(v == MCVanillaPotionEffectType.BAD_LUCK) { + return PotionEffectType.UNLUCK; + } + return PotionEffectType.getByName(v.name()); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCPotionType.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCPotionType.java new file mode 100644 index 0000000000..7f44b06db2 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCPotionType.java @@ -0,0 +1,61 @@ +package com.laytonsmith.abstraction.enums.bukkit; + +import com.laytonsmith.abstraction.enums.MCPotionType; +import com.laytonsmith.core.MSLog; +import com.laytonsmith.core.Static; +import com.laytonsmith.core.constructs.Target; +import org.bukkit.potion.PotionType; + +import java.util.HashMap; +import java.util.Map; + +public class BukkitMCPotionType extends MCPotionType { + + private static final Map BUKKIT_MAP = new HashMap<>(); + + public BukkitMCPotionType(MCVanillaPotionType vanillaEffect, PotionType effect) { + super(vanillaEffect, effect); + } + + @Override + public String name() { + return getAbstracted() == MCVanillaPotionType.UNKNOWN ? getConcrete().name() : getAbstracted().name(); + } + + public static MCPotionType valueOfConcrete(PotionType test) { + MCPotionType type = BUKKIT_MAP.get(test); + if(type == null) { + MSLog.GetLogger().e(MSLog.Tags.GENERAL, "Bukkit PotionType missing in BUKKIT_MAP: " + test.name(), + Target.UNKNOWN); + return new BukkitMCPotionType(MCVanillaPotionType.UNKNOWN, test); + } + return type; + } + + public static void build() { + for(MCVanillaPotionType v : MCVanillaPotionType.values()) { + if(v.existsIn(Static.getServer().getMinecraftVersion())) { + PotionType type = getBukkitType(v); + if(type == null) { + MSLog.GetLogger().w(MSLog.Tags.GENERAL, "Could not find a Bukkit potion type for " + v.name(), Target.UNKNOWN); + continue; + } + BukkitMCPotionType wrapper = new BukkitMCPotionType(v, type); + MAP.put(v.name(), wrapper); + BUKKIT_MAP.put(type, wrapper); + } + } + for(PotionType pt : PotionType.values()) { + if(pt != null && !BUKKIT_MAP.containsKey(pt)) { + MSLog.GetLogger().w(MSLog.Tags.GENERAL, "Could not find MCPotionType for " + pt.name(), Target.UNKNOWN); + MCPotionType wrapper = new BukkitMCPotionType(MCVanillaPotionType.UNKNOWN, pt); + MAP.put(pt.name(), wrapper); + BUKKIT_MAP.put(pt, wrapper); + } + } + } + + private static PotionType getBukkitType(MCVanillaPotionType v) { + return PotionType.valueOf(v.name()); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCProfession.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCProfession.java index b6ccb0dff7..647ca21a19 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCProfession.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCProfession.java @@ -1,29 +1,72 @@ - package com.laytonsmith.abstraction.enums.bukkit; -import com.laytonsmith.abstraction.Implementation; -import com.laytonsmith.abstraction.enums.EnumConvertor; +import com.laytonsmith.PureUtilities.Common.ReflectionUtils; import com.laytonsmith.abstraction.enums.MCProfession; -import com.laytonsmith.annotations.abstractionenum; +import com.laytonsmith.core.MSLog; +import com.laytonsmith.core.Static; +import com.laytonsmith.core.constructs.Target; +import org.bukkit.Keyed; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; import org.bukkit.entity.Villager; -/** - * - * @author jb_aero - */ -@abstractionenum( - implementation=Implementation.Type.BUKKIT, - forAbstractEnum=MCProfession.class, - forConcreteEnum=Villager.Profession.class - ) -public class BukkitMCProfession extends EnumConvertor { - - private static com.laytonsmith.abstraction.enums.bukkit.BukkitMCProfession instance; - - public static com.laytonsmith.abstraction.enums.bukkit.BukkitMCProfession getConvertor() { - if (instance == null) { - instance = new com.laytonsmith.abstraction.enums.bukkit.BukkitMCProfession(); +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +public class BukkitMCProfession extends MCProfession { + + private static final Map BUKKIT_MAP = new HashMap<>(); + + public BukkitMCProfession(MCVanillaProfession vanillaProfession, Villager.Profession profession) { + super(vanillaProfession, profession); + } + + @Override + public String name() { + if(getAbstracted() == MCVanillaProfession.UNKNOWN) { + // changed from enum to interface in 1.21 + NamespacedKey key = ReflectionUtils.invokeMethod(Keyed.class, getConcrete(), "getKey"); + return key.getKey().toUpperCase(Locale.ROOT); + } + return getAbstracted().name(); + } + + public static MCProfession valueOfConcrete(Villager.Profession test) { + MCProfession profession = BUKKIT_MAP.get(test); + if(profession == null) { + NamespacedKey key = ReflectionUtils.invokeMethod(Keyed.class, test, "getKey"); + MSLog.GetLogger().e(MSLog.Tags.GENERAL, "Bukkit Villager Profession missing in BUKKIT_MAP: " + + key.getKey().toUpperCase(Locale.ROOT), Target.UNKNOWN); + return new BukkitMCProfession(MCVanillaProfession.UNKNOWN, test); + } + return profession; + } + + public static void build() { + for(MCVanillaProfession v : MCVanillaProfession.values()) { + if(v.existsIn(Static.getServer().getMinecraftVersion())) { + Villager.Profession profession = Registry.VILLAGER_PROFESSION.get( + NamespacedKey.minecraft(v.name().toLowerCase(Locale.ROOT))); + if(profession == null) { + MSLog.GetLogger().w(MSLog.Tags.GENERAL, "Could not find a Bukkit villager profession type for " + + v.name(), Target.UNKNOWN); + continue; + } + BukkitMCProfession wrapper = new BukkitMCProfession(v, profession); + MAP.put(v.name(), wrapper); + BUKKIT_MAP.put(profession, wrapper); + } + } + for(Villager.Profession pr : Registry.VILLAGER_PROFESSION) { + if(pr != null && !BUKKIT_MAP.containsKey(pr)) { + NamespacedKey key = ReflectionUtils.invokeMethod(Keyed.class, pr, "getKey"); + MSLog.GetLogger().w(MSLog.Tags.GENERAL, "Could not find an MCProfession for " + + key.getKey().toUpperCase(Locale.ROOT), Target.UNKNOWN); + MCProfession wrapper = new BukkitMCProfession(MCVanillaProfession.UNKNOWN, pr); + MAP.put(key.getKey().toUpperCase(Locale.ROOT), wrapper); + BUKKIT_MAP.put(pr, new BukkitMCProfession(MCVanillaProfession.UNKNOWN, pr)); + } } - return instance; } -} \ No newline at end of file +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCRabbitType.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCRabbitType.java new file mode 100644 index 0000000000..d57a7c1d75 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCRabbitType.java @@ -0,0 +1,24 @@ +package com.laytonsmith.abstraction.enums.bukkit; + +import com.laytonsmith.abstraction.Implementation; +import com.laytonsmith.abstraction.enums.EnumConvertor; +import com.laytonsmith.abstraction.enums.MCRabbitType; +import com.laytonsmith.annotations.abstractionenum; +import org.bukkit.entity.Rabbit; + +@abstractionenum( + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCRabbitType.class, + forConcreteEnum = Rabbit.Type.class +) +public class BukkitMCRabbitType extends EnumConvertor { + + private static BukkitMCRabbitType instance; + + public static BukkitMCRabbitType getConvertor() { + if(instance == null) { + instance = new BukkitMCRabbitType(); + } + return instance; + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCRegainReason.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCRegainReason.java new file mode 100644 index 0000000000..e0f93e90a0 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCRegainReason.java @@ -0,0 +1,24 @@ +package com.laytonsmith.abstraction.enums.bukkit; + +import com.laytonsmith.abstraction.Implementation; +import com.laytonsmith.abstraction.enums.EnumConvertor; +import com.laytonsmith.abstraction.enums.MCRegainReason; +import com.laytonsmith.annotations.abstractionenum; +import org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason; + +@abstractionenum( + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCRegainReason.class, + forConcreteEnum = RegainReason.class +) +public class BukkitMCRegainReason extends EnumConvertor { + + private static BukkitMCRegainReason instance; + + public static BukkitMCRegainReason getConvertor() { + if(instance == null) { + instance = new BukkitMCRegainReason(); + } + return instance; + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCRemoveCause.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCRemoveCause.java index 70d3d1f6a3..57f01d369f 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCRemoveCause.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCRemoveCause.java @@ -6,23 +6,19 @@ import com.laytonsmith.annotations.abstractionenum; import org.bukkit.event.hanging.HangingBreakEvent.RemoveCause; -/** - * - * @author Hekta - */ @abstractionenum( - implementation=Implementation.Type.BUKKIT, - forAbstractEnum=MCRemoveCause.class, - forConcreteEnum=RemoveCause.class - ) + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCRemoveCause.class, + forConcreteEnum = RemoveCause.class +) public class BukkitMCRemoveCause extends EnumConvertor { private static com.laytonsmith.abstraction.enums.bukkit.BukkitMCRemoveCause instance; public static com.laytonsmith.abstraction.enums.bukkit.BukkitMCRemoveCause getConvertor() { - if (instance == null) { + if(instance == null) { instance = new com.laytonsmith.abstraction.enums.bukkit.BukkitMCRemoveCause(); } return instance; } -} \ No newline at end of file +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCResourcePackStatus.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCResourcePackStatus.java new file mode 100644 index 0000000000..940267d02a --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCResourcePackStatus.java @@ -0,0 +1,24 @@ +package com.laytonsmith.abstraction.enums.bukkit; + +import com.laytonsmith.abstraction.Implementation; +import com.laytonsmith.abstraction.enums.EnumConvertor; +import com.laytonsmith.abstraction.enums.MCResourcePackStatus; +import com.laytonsmith.annotations.abstractionenum; +import org.bukkit.event.player.PlayerResourcePackStatusEvent; + +@abstractionenum( + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCResourcePackStatus.class, + forConcreteEnum = PlayerResourcePackStatusEvent.Status.class +) +public class BukkitMCResourcePackStatus extends EnumConvertor { + + private static BukkitMCResourcePackStatus instance; + + public static BukkitMCResourcePackStatus getConvertor() { + if(instance == null) { + instance = new BukkitMCResourcePackStatus(); + } + return instance; + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCResult.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCResult.java index d1ff37008c..8f0f3a12d5 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCResult.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCResult.java @@ -1,4 +1,3 @@ - package com.laytonsmith.abstraction.enums.bukkit; import com.laytonsmith.abstraction.Implementation; @@ -7,20 +6,17 @@ import com.laytonsmith.annotations.abstractionenum; import org.bukkit.event.Event.Result; -/** - * - * @author MariuszT - */ @abstractionenum( implementation = Implementation.Type.BUKKIT, -forAbstractEnum = MCResult.class, -forConcreteEnum = Result.class) + forAbstractEnum = MCResult.class, + forConcreteEnum = Result.class +) public class BukkitMCResult extends EnumConvertor { private static com.laytonsmith.abstraction.enums.bukkit.BukkitMCResult instance; public static com.laytonsmith.abstraction.enums.bukkit.BukkitMCResult getConvertor() { - if (instance == null) { + if(instance == null) { instance = new com.laytonsmith.abstraction.enums.bukkit.BukkitMCResult(); } return instance; diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCRotation.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCRotation.java index 5876de72eb..2540752aaa 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCRotation.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCRotation.java @@ -1,4 +1,3 @@ - package com.laytonsmith.abstraction.enums.bukkit; import com.laytonsmith.abstraction.Implementation; @@ -7,19 +6,17 @@ import com.laytonsmith.annotations.abstractionenum; import org.bukkit.Rotation; -/** - * - * @author Jason Unger - */ @abstractionenum( implementation = Implementation.Type.BUKKIT, -forAbstractEnum = MCRotation.class, -forConcreteEnum = Rotation.class) -public class BukkitMCRotation extends EnumConvertor{ + forAbstractEnum = MCRotation.class, + forConcreteEnum = Rotation.class +) +public class BukkitMCRotation extends EnumConvertor { + private static BukkitMCRotation instance; public static BukkitMCRotation getConvertor() { - if (instance == null) { + if(instance == null) { instance = new com.laytonsmith.abstraction.enums.bukkit.BukkitMCRotation(); } return instance; diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCSkeletonType.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCSkeletonType.java deleted file mode 100644 index e27d355afe..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCSkeletonType.java +++ /dev/null @@ -1,29 +0,0 @@ - -package com.laytonsmith.abstraction.enums.bukkit; - -import com.laytonsmith.abstraction.Implementation; -import com.laytonsmith.abstraction.enums.EnumConvertor; -import com.laytonsmith.abstraction.enums.MCSkeletonType; -import com.laytonsmith.annotations.abstractionenum; -import org.bukkit.entity.Skeleton; - -/** - * - * @author jb_aero - */ -@abstractionenum( - implementation=Implementation.Type.BUKKIT, - forAbstractEnum=MCSkeletonType.class, - forConcreteEnum=Skeleton.SkeletonType.class - ) -public class BukkitMCSkeletonType extends EnumConvertor { - - private static com.laytonsmith.abstraction.enums.bukkit.BukkitMCSkeletonType instance; - - public static com.laytonsmith.abstraction.enums.bukkit.BukkitMCSkeletonType getConvertor() { - if (instance == null) { - instance = new com.laytonsmith.abstraction.enums.bukkit.BukkitMCSkeletonType(); - } - return instance; - } -} \ No newline at end of file diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCSound.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCSound.java index d13821e49d..82f18f9b75 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCSound.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCSound.java @@ -1,28 +1,36 @@ package com.laytonsmith.abstraction.enums.bukkit; -import com.laytonsmith.abstraction.Implementation; -import com.laytonsmith.abstraction.enums.EnumConvertor; import com.laytonsmith.abstraction.enums.MCSound; -import com.laytonsmith.annotations.abstractionenum; +import com.laytonsmith.core.MSLog; +import com.laytonsmith.core.MSLog.Tags; +import com.laytonsmith.core.Static; +import com.laytonsmith.core.constructs.Target; import org.bukkit.Sound; -/** - * - * @author jb_aero - */ -@abstractionenum( - implementation=Implementation.Type.BUKKIT, - forAbstractEnum=MCSound.class, - forConcreteEnum=Sound.class - ) -public class BukkitMCSound extends EnumConvertor { +public class BukkitMCSound extends MCSound { - private static com.laytonsmith.abstraction.enums.bukkit.BukkitMCSound instance; + public BukkitMCSound(MCVanillaSound vanillaSound, Sound sound) { + super(vanillaSound, sound); + } + + @Override + public String name() { + return getAbstracted().name(); + } - public static com.laytonsmith.abstraction.enums.bukkit.BukkitMCSound getConvertor() { - if (instance == null) { - instance = new com.laytonsmith.abstraction.enums.bukkit.BukkitMCSound(); + public static void build() { + for(MCVanillaSound v : MCVanillaSound.values()) { + if(v.existsIn(Static.getServer().getMinecraftVersion())) { + Sound sound; + try { + sound = (Sound) Sound.class.getDeclaredField(v.name()).get(null); + } catch (IllegalAccessException | NoSuchFieldException e) { + MSLog.GetLogger().w(Tags.GENERAL, "Could not find a Bukkit Sound for " + v.name(), Target.UNKNOWN); + continue; + } + BukkitMCSound wrapper = new BukkitMCSound(v, sound); + MAP.put(v.name(), wrapper); + } } - return instance; } } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCSoundCategory.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCSoundCategory.java new file mode 100644 index 0000000000..74ff18ad0b --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCSoundCategory.java @@ -0,0 +1,24 @@ +package com.laytonsmith.abstraction.enums.bukkit; + +import com.laytonsmith.abstraction.Implementation; +import com.laytonsmith.abstraction.enums.EnumConvertor; +import com.laytonsmith.abstraction.enums.MCSoundCategory; +import com.laytonsmith.annotations.abstractionenum; +import org.bukkit.SoundCategory; + +@abstractionenum( + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCSoundCategory.class, + forConcreteEnum = SoundCategory.class +) +public class BukkitMCSoundCategory extends EnumConvertor { + + private static BukkitMCSoundCategory instance; + + public static BukkitMCSoundCategory getConvertor() { + if(instance == null) { + instance = new BukkitMCSoundCategory(); + } + return instance; + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCSpawnReason.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCSpawnReason.java index 1d162fed95..45e1cf3b16 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCSpawnReason.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCSpawnReason.java @@ -6,23 +6,19 @@ import com.laytonsmith.annotations.abstractionenum; import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; -/** - * - * @author jb_aero - */ @abstractionenum( - implementation=Implementation.Type.BUKKIT, - forAbstractEnum=MCSpawnReason.class, - forConcreteEnum=SpawnReason.class - ) + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCSpawnReason.class, + forConcreteEnum = SpawnReason.class +) public class BukkitMCSpawnReason extends EnumConvertor { private static com.laytonsmith.abstraction.enums.bukkit.BukkitMCSpawnReason instance; public static com.laytonsmith.abstraction.enums.bukkit.BukkitMCSpawnReason getConvertor() { - if (instance == null) { + if(instance == null) { instance = new com.laytonsmith.abstraction.enums.bukkit.BukkitMCSpawnReason(); } return instance; } -} \ No newline at end of file +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCTargetReason.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCTargetReason.java index b429af882b..9d6c2efefb 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCTargetReason.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCTargetReason.java @@ -1,4 +1,3 @@ - package com.laytonsmith.abstraction.enums.bukkit; import com.laytonsmith.abstraction.Implementation; @@ -7,19 +6,17 @@ import com.laytonsmith.annotations.abstractionenum; import org.bukkit.event.entity.EntityTargetEvent.TargetReason; -/** - * - * - */ @abstractionenum( implementation = Implementation.Type.BUKKIT, -forAbstractEnum = MCTargetReason.class, -forConcreteEnum = TargetReason.class) + forAbstractEnum = MCTargetReason.class, + forConcreteEnum = TargetReason.class +) public class BukkitMCTargetReason extends EnumConvertor { + private static com.laytonsmith.abstraction.enums.bukkit.BukkitMCTargetReason instance; public static com.laytonsmith.abstraction.enums.bukkit.BukkitMCTargetReason getConvertor() { - if (instance == null) { + if(instance == null) { instance = new com.laytonsmith.abstraction.enums.bukkit.BukkitMCTargetReason(); } return instance; diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCTeleportCause.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCTeleportCause.java index ddcf8a6625..26bfa9e6d5 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCTeleportCause.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCTeleportCause.java @@ -1,26 +1,46 @@ package com.laytonsmith.abstraction.enums.bukkit; import com.laytonsmith.abstraction.Implementation; +import com.laytonsmith.abstraction.bukkit.BukkitMCServer; import com.laytonsmith.abstraction.enums.EnumConvertor; import com.laytonsmith.abstraction.enums.MCTeleportCause; +import com.laytonsmith.abstraction.enums.MCVersion; import com.laytonsmith.annotations.abstractionenum; +import com.laytonsmith.core.Static; import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; -/** - * - * - */ @abstractionenum( implementation = Implementation.Type.BUKKIT, -forAbstractEnum = MCTeleportCause.class, -forConcreteEnum = TeleportCause.class) + forAbstractEnum = MCTeleportCause.class, + forConcreteEnum = TeleportCause.class +) public class BukkitMCTeleportCause extends EnumConvertor { + private static com.laytonsmith.abstraction.enums.bukkit.BukkitMCTeleportCause instance; public static com.laytonsmith.abstraction.enums.bukkit.BukkitMCTeleportCause getConvertor() { - if (instance == null) { + if(instance == null) { instance = new com.laytonsmith.abstraction.enums.bukkit.BukkitMCTeleportCause(); } return instance; } + + @Override + protected MCTeleportCause getAbstractedEnumCustom(TeleportCause concrete) { + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_21_5) + && concrete.name().equals("CONSUMABLE_EFFECT")) { + return MCTeleportCause.CHORUS_FRUIT; + } + return super.getAbstractedEnumCustom(concrete); + } + + @Override + protected TeleportCause getConcreteEnumCustom(MCTeleportCause abstracted) { + BukkitMCServer server = (BukkitMCServer) Static.getServer(); + if(server.isPaper() && server.getMinecraftVersion().gte(MCVersion.MC1_21_5) + && abstracted == MCTeleportCause.CHORUS_FRUIT) { + return TeleportCause.valueOf("CONSUMABLE_EFFECT"); + } + return super.getConcreteEnumCustom(abstracted); + } } diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCTreeSpecies.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCTreeSpecies.java new file mode 100644 index 0000000000..f01dd724db --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCTreeSpecies.java @@ -0,0 +1,54 @@ +package com.laytonsmith.abstraction.enums.bukkit; + +import com.laytonsmith.abstraction.Implementation; +import com.laytonsmith.abstraction.enums.EnumConvertor; +import com.laytonsmith.abstraction.enums.MCTreeSpecies; +import com.laytonsmith.annotations.abstractionenum; +import org.bukkit.TreeSpecies; + +/** + * @deprecated To be removed when MC versions prior to 1.21.2 are no longer supported + */ +@Deprecated +@abstractionenum( + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCTreeSpecies.class, + forConcreteEnum = TreeSpecies.class +) +public class BukkitMCTreeSpecies extends EnumConvertor { + + private static BukkitMCTreeSpecies instance; + + public static BukkitMCTreeSpecies getConvertor() { + if(instance == null) { + instance = new BukkitMCTreeSpecies(); + } + return instance; + } + + @Override + protected MCTreeSpecies getAbstractedEnumCustom(TreeSpecies concrete) { + switch(concrete) { + case GENERIC: + return MCTreeSpecies.OAK; + case REDWOOD: + return MCTreeSpecies.SPRUCE; + } + return super.getAbstractedEnumCustom(concrete); + } + + @Override + protected TreeSpecies getConcreteEnumCustom(MCTreeSpecies abstracted) { + switch(abstracted) { + case BAMBOO: + case CHERRY: + case MANGROVE: + case OAK: + case PALE_OAK: + return TreeSpecies.GENERIC; + case SPRUCE: + return TreeSpecies.REDWOOD; + } + return super.getConcreteEnumCustom(abstracted); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCTreeType.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCTreeType.java index c958ebb941..1965d8f223 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCTreeType.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCTreeType.java @@ -6,23 +6,19 @@ import com.laytonsmith.annotations.abstractionenum; import org.bukkit.TreeType; -/** - * - * @author Hekta - */ @abstractionenum( - implementation=Implementation.Type.BUKKIT, - forAbstractEnum=MCTreeType.class, - forConcreteEnum=TreeType.class - ) + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCTreeType.class, + forConcreteEnum = TreeType.class +) public class BukkitMCTreeType extends EnumConvertor { private static com.laytonsmith.abstraction.enums.bukkit.BukkitMCTreeType instance; public static com.laytonsmith.abstraction.enums.bukkit.BukkitMCTreeType getConvertor() { - if (instance == null) { + if(instance == null) { instance = new com.laytonsmith.abstraction.enums.bukkit.BukkitMCTreeType(); } return instance; } -} \ No newline at end of file +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCTrimMaterial.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCTrimMaterial.java new file mode 100644 index 0000000000..5fc51dd99d --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCTrimMaterial.java @@ -0,0 +1,68 @@ +package com.laytonsmith.abstraction.enums.bukkit; + +import com.laytonsmith.abstraction.enums.MCTrimMaterial; +import com.laytonsmith.core.MSLog; +import com.laytonsmith.core.constructs.Target; +import org.bukkit.inventory.meta.trim.TrimMaterial; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +public class BukkitMCTrimMaterial extends MCTrimMaterial { + + private static final Map BUKKIT_MAP = new HashMap<>(); + + public BukkitMCTrimMaterial(MCVanillaTrimMaterial vanillaTrimMaterial, TrimMaterial trimMaterial) { + super(vanillaTrimMaterial, trimMaterial); + } + + @Override + public String name() { + if(getAbstracted() == MCVanillaTrimMaterial.UNKNOWN) { + return getConcrete().getKey().getKey().toUpperCase(); + } + return getAbstracted().name(); + } + + public static MCTrimMaterial valueOfConcrete(TrimMaterial test) { + MCTrimMaterial trimMaterial = BUKKIT_MAP.get(test); + if(trimMaterial == null) { + MSLog.GetLogger().e(MSLog.Tags.GENERAL, "Bukkit Trim Material missing in BUKKIT_MAP: " + + test, Target.UNKNOWN); + return new BukkitMCTrimMaterial(MCVanillaTrimMaterial.UNKNOWN, test); + } + return trimMaterial; + } + + public static void build() { + for(MCVanillaTrimMaterial v : MCVanillaTrimMaterial.values()) { + if(v == MCVanillaTrimMaterial.UNKNOWN) { + continue; + } + TrimMaterial trimMaterial; + try { + trimMaterial = (TrimMaterial) TrimMaterial.class.getField(v.name()).get(null); + } catch (IllegalAccessException | NoSuchFieldException ex) { + MSLog.GetLogger().w(MSLog.Tags.GENERAL, "Could not find a Bukkit trim material type for " + + v.name(), Target.UNKNOWN); + continue; + } + BukkitMCTrimMaterial wrapper = new BukkitMCTrimMaterial(v, trimMaterial); + MAP.put(v.name(), wrapper); + BUKKIT_MAP.put(trimMaterial, wrapper); + } + for(Field field : TrimMaterial.class.getFields()) { + try { + TrimMaterial trimMaterial = (TrimMaterial) field.get(null); + if(!BUKKIT_MAP.containsKey(trimMaterial)) { + MSLog.GetLogger().w(MSLog.Tags.GENERAL, "Could not find MCTrimMaterial for " + + field.getName(), Target.UNKNOWN); + MCTrimMaterial wrapper = new BukkitMCTrimMaterial(MCVanillaTrimMaterial.UNKNOWN, trimMaterial); + MAP.put(field.getName(), wrapper); + BUKKIT_MAP.put(trimMaterial, wrapper); + } + } catch (IllegalAccessException | ClassCastException ignore) {} + } + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCTrimPattern.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCTrimPattern.java new file mode 100644 index 0000000000..628a3e99ce --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCTrimPattern.java @@ -0,0 +1,73 @@ +package com.laytonsmith.abstraction.enums.bukkit; + +import com.laytonsmith.abstraction.enums.MCTrimPattern; +import com.laytonsmith.core.MSLog; +import com.laytonsmith.core.Static; +import com.laytonsmith.core.constructs.Target; +import org.bukkit.inventory.meta.trim.TrimPattern; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +public class BukkitMCTrimPattern extends MCTrimPattern { + + private static final Map BUKKIT_MAP = new HashMap<>(); + + public BukkitMCTrimPattern(MCVanillaTrimPattern vanillaTrimPattern, TrimPattern trimPattern) { + super(vanillaTrimPattern, trimPattern); + } + + @Override + public String name() { + if(getAbstracted() == MCVanillaTrimPattern.UNKNOWN) { + getConcrete().getKey().getKey().toUpperCase(); + } + return getAbstracted().name(); + } + + public static MCTrimPattern valueOfConcrete(TrimPattern test) { + MCTrimPattern trimPattern = BUKKIT_MAP.get(test); + if(trimPattern == null) { + MSLog.GetLogger().e(MSLog.Tags.GENERAL, "Bukkit Trim Pattern missing in BUKKIT_MAP: " + + test, Target.UNKNOWN); + return new BukkitMCTrimPattern(MCVanillaTrimPattern.UNKNOWN, test); + } + return trimPattern; + } + + public static void build() { + for(MCVanillaTrimPattern v : MCVanillaTrimPattern.values()) { + if(v == MCVanillaTrimPattern.UNKNOWN || !v.existsIn(Static.getServer().getMinecraftVersion())) { + continue; + } + TrimPattern trimPattern; + try { + trimPattern = getBukkitType(v); + } catch (IllegalAccessException | NoSuchFieldException ex) { + MSLog.GetLogger().w(MSLog.Tags.GENERAL, "Could not find a Bukkit trim pattern type for " + + v.name(), Target.UNKNOWN); + continue; + } + BukkitMCTrimPattern wrapper = new BukkitMCTrimPattern(v, trimPattern); + MAP.put(v.name(), wrapper); + BUKKIT_MAP.put(trimPattern, wrapper); + } + for(Field field : TrimPattern.class.getFields()) { + try { + TrimPattern trimPattern = (TrimPattern) field.get(null); + if(!BUKKIT_MAP.containsKey(trimPattern)) { + MSLog.GetLogger().w(MSLog.Tags.GENERAL, "Could not find MCTrimPattern for " + + field.getName(), Target.UNKNOWN); + MCTrimPattern wrapper = new BukkitMCTrimPattern(MCVanillaTrimPattern.UNKNOWN, trimPattern); + MAP.put(field.getName(), wrapper); + BUKKIT_MAP.put(trimPattern, wrapper); + } + } catch (IllegalAccessException | ClassCastException ignore) {} + } + } + + private static TrimPattern getBukkitType(MCVanillaTrimPattern v) throws NoSuchFieldException, IllegalAccessException { + return (TrimPattern) TrimPattern.class.getField(v.name()).get(null); + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCUnleashReason.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCUnleashReason.java new file mode 100644 index 0000000000..c3307cba9e --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCUnleashReason.java @@ -0,0 +1,24 @@ +package com.laytonsmith.abstraction.enums.bukkit; + +import com.laytonsmith.abstraction.Implementation; +import com.laytonsmith.abstraction.enums.EnumConvertor; +import com.laytonsmith.abstraction.enums.MCUnleashReason; +import com.laytonsmith.annotations.abstractionenum; +import org.bukkit.event.entity.EntityUnleashEvent.UnleashReason; + +@abstractionenum( + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCUnleashReason.class, + forConcreteEnum = UnleashReason.class +) +public class BukkitMCUnleashReason extends EnumConvertor { + + private static BukkitMCUnleashReason instance; + + public static BukkitMCUnleashReason getConvertor() { + if(instance == null) { + instance = new BukkitMCUnleashReason(); + } + return instance; + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCWeather.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCWeather.java index 02b3fd051b..dc49a21be8 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCWeather.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCWeather.java @@ -6,21 +6,17 @@ import com.laytonsmith.annotations.abstractionenum; import org.bukkit.WeatherType; -/** - * - * @author jb_aero - */ @abstractionenum( - implementation=Implementation.Type.BUKKIT, - forAbstractEnum=MCWeather.class, - forConcreteEnum=WeatherType.class - ) + implementation = Implementation.Type.BUKKIT, + forAbstractEnum = MCWeather.class, + forConcreteEnum = WeatherType.class +) public class BukkitMCWeather extends EnumConvertor { private static BukkitMCWeather instance; public static BukkitMCWeather getConvertor() { - if (instance == null) { + if(instance == null) { instance = new BukkitMCWeather(); } return instance; diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCWorldEnvironment.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCWorldEnvironment.java index 0b2d6e627b..604253cacd 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCWorldEnvironment.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCWorldEnvironment.java @@ -1,4 +1,3 @@ - package com.laytonsmith.abstraction.enums.bukkit; import com.laytonsmith.abstraction.Implementation; @@ -7,19 +6,17 @@ import com.laytonsmith.annotations.abstractionenum; import org.bukkit.World; -/** - * - * - */ @abstractionenum( implementation = Implementation.Type.BUKKIT, -forAbstractEnum = MCWorldEnvironment.class, -forConcreteEnum = World.Environment.class) -public class BukkitMCWorldEnvironment extends EnumConvertor{ + forAbstractEnum = MCWorldEnvironment.class, + forConcreteEnum = World.Environment.class +) +public class BukkitMCWorldEnvironment extends EnumConvertor { + private static com.laytonsmith.abstraction.enums.bukkit.BukkitMCWorldEnvironment instance; public static com.laytonsmith.abstraction.enums.bukkit.BukkitMCWorldEnvironment getConvertor() { - if (instance == null) { + if(instance == null) { instance = new com.laytonsmith.abstraction.enums.bukkit.BukkitMCWorldEnvironment(); } return instance; diff --git a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCWorldType.java b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCWorldType.java index 4c971f61dd..e5c11f81e2 100644 --- a/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCWorldType.java +++ b/src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCWorldType.java @@ -1,4 +1,3 @@ - package com.laytonsmith.abstraction.enums.bukkit; import com.laytonsmith.abstraction.Implementation; @@ -7,19 +6,17 @@ import com.laytonsmith.annotations.abstractionenum; import org.bukkit.WorldType; -/** - * - * - */ @abstractionenum( implementation = Implementation.Type.BUKKIT, -forAbstractEnum = MCWorldType.class, -forConcreteEnum = WorldType.class) -public class BukkitMCWorldType extends EnumConvertor{ + forAbstractEnum = MCWorldType.class, + forConcreteEnum = WorldType.class +) +public class BukkitMCWorldType extends EnumConvertor { + private static com.laytonsmith.abstraction.enums.bukkit.BukkitMCWorldType instance; public static com.laytonsmith.abstraction.enums.bukkit.BukkitMCWorldType getConvertor() { - if (instance == null) { + if(instance == null) { instance = new com.laytonsmith.abstraction.enums.bukkit.BukkitMCWorldType(); } return instance; diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCBlockBreakEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCBlockBreakEvent.java index 446a2c801c..fd92ceedf8 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCBlockBreakEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCBlockBreakEvent.java @@ -1,20 +1,25 @@ package com.laytonsmith.abstraction.events; +import com.laytonsmith.abstraction.MCItemStack; import com.laytonsmith.abstraction.MCPlayer; import com.laytonsmith.abstraction.blocks.MCBlock; import com.laytonsmith.core.events.BindableEvent; -/** - * - * @author EntityReborn - */ +import java.util.List; + public interface MCBlockBreakEvent extends BindableEvent { - public MCPlayer getPlayer(); + MCPlayer getPlayer(); + + MCBlock getBlock(); + + int getExpToDrop(); + + void setExpToDrop(int exp); - public MCBlock getBlock(); + List getDrops(); - public int getExpToDrop(); + void setDrops(List drops); - public void setExpToDrop(int exp); -} \ No newline at end of file + boolean isModified(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCBlockBurnEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCBlockBurnEvent.java index fd35d19450..3cd2f49d14 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCBlockBurnEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCBlockBurnEvent.java @@ -3,12 +3,10 @@ import com.laytonsmith.abstraction.blocks.MCBlock; import com.laytonsmith.core.events.BindableEvent; -/** - * - * @author cgallarno - */ public interface MCBlockBurnEvent extends BindableEvent { - - public MCBlock getBlock(); - + + MCBlock getBlock(); + + MCBlock getFireBlock(); + } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCBlockDispenseEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCBlockDispenseEvent.java index 7a0c0c3d54..36f5b393ff 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCBlockDispenseEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCBlockDispenseEvent.java @@ -1,23 +1,19 @@ package com.laytonsmith.abstraction.events; +import com.laytonsmith.PureUtilities.Vector3D; import com.laytonsmith.abstraction.MCItemStack; -import com.laytonsmith.abstraction.Velocity; -/** - * - * @author MariuszT - */ public interface MCBlockDispenseEvent extends MCBlockEvent { - public MCItemStack getItem(); + MCItemStack getItem(); - public void setItem(MCItemStack item); + void setItem(MCItemStack item); - public Velocity getVelocity(); + Vector3D getVelocity(); - public void setVelocity(Velocity vel); + void setVelocity(Vector3D vel); - public boolean isCancelled(); + boolean isCancelled(); - public void setCancelled(boolean cancel); -} \ No newline at end of file + void setCancelled(boolean cancel); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCBlockEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCBlockEvent.java index 16d8e2793d..da04673ce3 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCBlockEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCBlockEvent.java @@ -3,11 +3,7 @@ import com.laytonsmith.abstraction.blocks.MCBlock; import com.laytonsmith.core.events.BindableEvent; -/** - * - * @author MariuszT - */ public interface MCBlockEvent extends BindableEvent { - public MCBlock getBlock(); -} \ No newline at end of file + MCBlock getBlock(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCBlockExplodeEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCBlockExplodeEvent.java new file mode 100644 index 0000000000..67d6ff92d9 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCBlockExplodeEvent.java @@ -0,0 +1,17 @@ +package com.laytonsmith.abstraction.events; + +import com.laytonsmith.abstraction.blocks.MCBlock; + +import java.util.List; + +public interface MCBlockExplodeEvent extends MCBlockEvent { + + List getBlocks(); + + void setBlocks(List blocks); + + float getYield(); + + void setYield(float power); + +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCBlockFadeEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCBlockFadeEvent.java new file mode 100644 index 0000000000..b4cba37907 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCBlockFadeEvent.java @@ -0,0 +1,8 @@ +package com.laytonsmith.abstraction.events; + +import com.laytonsmith.abstraction.blocks.MCBlockState; + +public interface MCBlockFadeEvent extends MCBlockEvent { + + MCBlockState getNewState(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCBlockFormEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCBlockFormEvent.java new file mode 100644 index 0000000000..a1c0997307 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCBlockFormEvent.java @@ -0,0 +1,8 @@ +package com.laytonsmith.abstraction.events; + +import com.laytonsmith.abstraction.blocks.MCBlockState; + +public interface MCBlockFormEvent extends MCBlockEvent { + + MCBlockState getNewState(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCBlockFromToEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCBlockFromToEvent.java new file mode 100644 index 0000000000..b42b9dc716 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCBlockFromToEvent.java @@ -0,0 +1,18 @@ +package com.laytonsmith.abstraction.events; + +import com.laytonsmith.abstraction.blocks.MCBlock; +import com.laytonsmith.abstraction.blocks.MCBlockFace; +import com.laytonsmith.core.events.BindableEvent; + +public interface MCBlockFromToEvent extends BindableEvent { + + MCBlock getBlock(); + + MCBlock getToBlock(); + + MCBlockFace getBlockFace(); + + boolean isCancelled(); + + void setCancelled(boolean cancelled); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCBlockGrowEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCBlockGrowEvent.java index 3ed0cb32b7..771ec99a38 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCBlockGrowEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCBlockGrowEvent.java @@ -3,14 +3,10 @@ import com.laytonsmith.abstraction.blocks.MCBlock; import com.laytonsmith.abstraction.blocks.MCBlockState; -/** - * - * @author Hekta - */ public interface MCBlockGrowEvent extends MCBlockEvent { @Override - public MCBlock getBlock(); + MCBlock getBlock(); - public MCBlockState getNewState(); + MCBlockState getNewState(); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCBlockIgniteEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCBlockIgniteEvent.java index ca342c4761..bfc1ffd9bd 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCBlockIgniteEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCBlockIgniteEvent.java @@ -5,17 +5,13 @@ import com.laytonsmith.abstraction.blocks.MCBlock; import com.laytonsmith.abstraction.enums.MCIgniteCause; -/** - * - * @author MariuszT - */ public interface MCBlockIgniteEvent extends MCBlockEvent { - public MCIgniteCause getCause(); + MCIgniteCause getCause(); - public MCPlayer getPlayer(); + MCPlayer getPlayer(); - public MCEntity getIgnitingEntity(); + MCEntity getIgnitingEntity(); - public MCBlock getIgnitingBlock(); + MCBlock getIgnitingBlock(); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCBlockPistonEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCBlockPistonEvent.java new file mode 100644 index 0000000000..f135583109 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCBlockPistonEvent.java @@ -0,0 +1,27 @@ +package com.laytonsmith.abstraction.events; + +import java.util.List; + +import com.laytonsmith.abstraction.blocks.MCBlock; +import com.laytonsmith.abstraction.blocks.MCBlockFace; + +public interface MCBlockPistonEvent extends MCBlockEvent { + + /** + * Get the direction in which the pushed or pulled blocks are moved. + * @return The direction. + */ + MCBlockFace getDirection(); + + /** + * Get all blocks that will be moved by the extending or retracting piston. + * @return A {@link List} containing all pushed or pulled blocks. + */ + List getAffectedBlocks(); + + boolean isSticky(); + + boolean isCancelled(); + + void setCancelled(boolean cancelled); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCBlockPistonExtendEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCBlockPistonExtendEvent.java new file mode 100644 index 0000000000..f0a577cc9e --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCBlockPistonExtendEvent.java @@ -0,0 +1,4 @@ +package com.laytonsmith.abstraction.events; + +public interface MCBlockPistonExtendEvent extends MCBlockPistonEvent { +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCBlockPistonRetractEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCBlockPistonRetractEvent.java new file mode 100644 index 0000000000..aeadae6081 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCBlockPistonRetractEvent.java @@ -0,0 +1,4 @@ +package com.laytonsmith.abstraction.events; + +public interface MCBlockPistonRetractEvent extends MCBlockPistonEvent { +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCBlockPlaceEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCBlockPlaceEvent.java index 2274ca0bb9..f2ff4cee36 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCBlockPlaceEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCBlockPlaceEvent.java @@ -4,17 +4,22 @@ import com.laytonsmith.abstraction.MCPlayer; import com.laytonsmith.abstraction.blocks.MCBlock; import com.laytonsmith.abstraction.blocks.MCBlockState; +import com.laytonsmith.abstraction.enums.MCEquipmentSlot; import com.laytonsmith.core.events.BindableEvent; -/** - * - * @author EntityReborn - */ -public interface MCBlockPlaceEvent extends BindableEvent{ - public MCPlayer getPlayer(); - public MCBlock getBlock(); - public MCBlock getBlockAgainst(); - public MCBlockState getBlockReplacedState(); - public MCItemStack getItemInHand(); - public boolean canBuild(); -} \ No newline at end of file +public interface MCBlockPlaceEvent extends BindableEvent { + + MCPlayer getPlayer(); + + MCBlock getBlock(); + + MCBlock getBlockAgainst(); + + MCBlockState getBlockReplacedState(); + + MCItemStack getItemInHand(); + + MCEquipmentSlot getHand(); + + boolean canBuild(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCBroadcastMessageEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCBroadcastMessageEvent.java new file mode 100644 index 0000000000..c5be2fab04 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCBroadcastMessageEvent.java @@ -0,0 +1,16 @@ +package com.laytonsmith.abstraction.events; + +import java.util.Set; + +import com.laytonsmith.abstraction.MCCommandSender; +import com.laytonsmith.abstraction.MCPlayer; +import com.laytonsmith.core.events.BindableEvent; +import com.laytonsmith.core.events.CancellableEvent; + +public interface MCBroadcastMessageEvent extends BindableEvent, CancellableEvent { + String getMessage(); + void setMessage(String message); + Set getRecipients(); + Set getPlayerRecipients(); + boolean isCancelled(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCChatTabCompleteEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCChatTabCompleteEvent.java deleted file mode 100644 index 5d9f242803..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/events/MCChatTabCompleteEvent.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.laytonsmith.abstraction.events; - -import com.laytonsmith.abstraction.MCPlayer; -import com.laytonsmith.core.events.BindableEvent; -import java.util.Collection; - -public interface MCChatTabCompleteEvent extends BindableEvent { - public MCPlayer getPlayer(); - public String getChatMessage(); - public String getLastToken(); - public Collection getTabCompletions(); -} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCCommandTabCompleteEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCCommandTabCompleteEvent.java index 8d73cbd478..1261f135a8 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCCommandTabCompleteEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCCommandTabCompleteEvent.java @@ -7,13 +7,15 @@ public interface MCCommandTabCompleteEvent extends BindableEvent { - public MCCommandSender getCommandSender(); - - public MCCommand getCommand(); - - public String getAlias(); - - public String[] getArguments(); - - public List getCompletions(); + MCCommandSender getCommandSender(); + + MCCommand getCommand(); + + String getAlias(); + + String[] getArguments(); + + List getCompletions(); + + void setCompletions(List completions); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCConsoleCommandEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCConsoleCommandEvent.java deleted file mode 100644 index 1e4a8daaee..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/events/MCConsoleCommandEvent.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ -package com.laytonsmith.abstraction.events; - -import com.laytonsmith.core.events.BindableEvent; - -/** - * - * - */ -public interface MCConsoleCommandEvent extends BindableEvent { - - String getCommand(); - void setCommand(String command); - -} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCCreatureSpawnEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCCreatureSpawnEvent.java index a26ae90c7a..3d33da7160 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCCreatureSpawnEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCCreatureSpawnEvent.java @@ -2,21 +2,17 @@ import com.laytonsmith.abstraction.MCLivingEntity; import com.laytonsmith.abstraction.MCLocation; -import com.laytonsmith.abstraction.enums.MCMobs; +import com.laytonsmith.abstraction.enums.MCEntityType; import com.laytonsmith.abstraction.enums.MCSpawnReason; import com.laytonsmith.core.events.BindableEvent; -/** - * - * @author jb_aero - */ public interface MCCreatureSpawnEvent extends BindableEvent { - public MCLivingEntity getEntity(); - - public MCLocation getLocation(); - - public MCSpawnReason getSpawnReason(); - - public void setType(MCMobs type); + MCLivingEntity getEntity(); + + MCLocation getLocation(); + + MCSpawnReason getSpawnReason(); + + void setType(MCEntityType type); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCEnchantItemEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCEnchantItemEvent.java index dc5de35859..0a15a8d4ff 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCEnchantItemEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCEnchantItemEvent.java @@ -1,23 +1,34 @@ package com.laytonsmith.abstraction.events; -import com.laytonsmith.abstraction.MCEnchantment; import com.laytonsmith.abstraction.MCItemStack; import com.laytonsmith.abstraction.MCPlayer; import com.laytonsmith.abstraction.blocks.MCBlock; +import com.laytonsmith.abstraction.enums.MCEnchantment; + import java.util.Map; -/** - * - * @author cgallarno - */ public interface MCEnchantItemEvent extends MCInventoryEvent { - public MCBlock getEnchantBlock(); - public MCPlayer GetEnchanter(); - public Map getEnchantsToAdd(); - public void setEnchantsToAdd(Map enchants); - public MCItemStack getItem(); - public void setItem(MCItemStack i); - public void setExpLevelCost(int level); - public int getExpLevelCost(); - public int whichButton(); + + MCBlock getEnchantBlock(); + + MCPlayer GetEnchanter(); + + Map getEnchantsToAdd(); + + void setEnchantsToAdd(Map enchants); + + MCItemStack getItem(); + + void setItem(MCItemStack i); + + void setExpLevelCost(int level); + + int getExpLevelCost(); + + int whichButton(); + + int getLevelHint(); + + MCEnchantment getEnchantmentHint(); + } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCEntityChangeBlockEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCEntityChangeBlockEvent.java index 5cd21f5f2e..86ead5fa88 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCEntityChangeBlockEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCEntityChangeBlockEvent.java @@ -2,24 +2,21 @@ import com.laytonsmith.abstraction.MCEntity; import com.laytonsmith.abstraction.blocks.MCBlock; +import com.laytonsmith.abstraction.blocks.MCBlockData; import com.laytonsmith.abstraction.blocks.MCMaterial; import com.laytonsmith.core.events.BindableEvent; -/** - * - * @author MariuszT - */ public interface MCEntityChangeBlockEvent extends BindableEvent { - public MCEntity getEntity(); + MCEntity getEntity(); - public MCBlock getBlock(); + MCBlock getBlock(); - public MCMaterial getTo(); + MCMaterial getTo(); - public byte getData(); + MCBlockData getBlockData(); - public boolean isCancelled(); + boolean isCancelled(); - public void setCancelled(boolean cancel); -} \ No newline at end of file + void setCancelled(boolean cancel); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCEntityDamageByEntityEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCEntityDamageByEntityEvent.java index cf9f0fe5fc..e04b240d2f 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCEntityDamageByEntityEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCEntityDamageByEntityEvent.java @@ -2,10 +2,7 @@ import com.laytonsmith.abstraction.MCEntity; -/** - * - * @author EntityReborn - */ public interface MCEntityDamageByEntityEvent extends MCEntityDamageEvent { - public MCEntity getDamager(); -} \ No newline at end of file + + MCEntity getDamager(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCEntityDamageEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCEntityDamageEvent.java index 41512c3a15..111d793cf8 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCEntityDamageEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCEntityDamageEvent.java @@ -4,17 +4,15 @@ import com.laytonsmith.abstraction.enums.MCDamageCause; import com.laytonsmith.core.events.BindableEvent; -/** - * - * @author EntityReborn - */ public interface MCEntityDamageEvent extends BindableEvent { - public MCDamageCause getCause(); + MCDamageCause getCause(); - public MCEntity getEntity(); + MCEntity getEntity(); - public double getDamage(); + double getFinalDamage(); - public void setDamage(double damage); -} \ No newline at end of file + double getDamage(); + + void setDamage(double damage); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCEntityDeathEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCEntityDeathEvent.java index 81529b0833..00b8df786e 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCEntityDeathEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCEntityDeathEvent.java @@ -5,16 +5,17 @@ import com.laytonsmith.core.events.BindableEvent; import java.util.List; -/** - * - * @author jb_aero - */ public interface MCEntityDeathEvent extends BindableEvent { - public int getDroppedExp(); - public List getDrops(); - public MCLivingEntity getEntity(); - public void setDroppedExp(int exp); - public void clearDrops(); - public void addDrop(MCItemStack is); + int getDroppedExp(); + + List getDrops(); + + MCLivingEntity getEntity(); + + void setDroppedExp(int exp); + + void clearDrops(); + + void addDrop(MCItemStack is); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCEntityEnterPortalEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCEntityEnterPortalEvent.java index a534ccb798..cfee17039c 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCEntityEnterPortalEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCEntityEnterPortalEvent.java @@ -5,6 +5,8 @@ import com.laytonsmith.core.events.BindableEvent; public interface MCEntityEnterPortalEvent extends BindableEvent { - public MCEntity getEntity(); - public MCLocation getLocation(); + + MCEntity getEntity(); + + MCLocation getLocation(); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCEntityEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCEntityEvent.java new file mode 100644 index 0000000000..9547bf1137 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCEntityEvent.java @@ -0,0 +1,12 @@ +package com.laytonsmith.abstraction.events; + +import com.laytonsmith.abstraction.MCEntity; +import com.laytonsmith.abstraction.enums.MCEntityType; +import com.laytonsmith.core.events.BindableEvent; + +public interface MCEntityEvent extends BindableEvent { + + MCEntity getEntity(); + + MCEntityType getEntityType(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCEntityExplodeEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCEntityExplodeEvent.java index d4e8c82d0d..25b8d5e1f2 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCEntityExplodeEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCEntityExplodeEvent.java @@ -8,15 +8,15 @@ public interface MCEntityExplodeEvent extends BindableEvent { - public MCEntity getEntity(); - - public List getBlocks(); - - public void setBlocks(List blocks); - - public MCLocation getLocation(); - - public float getYield(); - - public void setYield(float power); + MCEntity getEntity(); + + List getBlocks(); + + void setBlocks(List blocks); + + MCLocation getLocation(); + + float getYield(); + + void setYield(float power); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCEntityInteractEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCEntityInteractEvent.java new file mode 100644 index 0000000000..fb4fd2eaee --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCEntityInteractEvent.java @@ -0,0 +1,16 @@ +package com.laytonsmith.abstraction.events; + +import com.laytonsmith.abstraction.MCEntity; +import com.laytonsmith.abstraction.blocks.MCBlock; +import com.laytonsmith.core.events.BindableEvent; + +public interface MCEntityInteractEvent extends BindableEvent { + + MCEntity getEntity(); + + MCBlock getBlock(); + + boolean isCancelled(); + + void setCancelled(boolean cancelled); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCEntityPortalEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCEntityPortalEvent.java new file mode 100644 index 0000000000..1ad8829a07 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCEntityPortalEvent.java @@ -0,0 +1,24 @@ +package com.laytonsmith.abstraction.events; + +import com.laytonsmith.abstraction.MCEntity; +import com.laytonsmith.abstraction.MCLocation; +import com.laytonsmith.core.events.BindableEvent; + +public interface MCEntityPortalEvent extends BindableEvent { + + MCEntity getEntity(); + + void setTo(MCLocation newloc); + + MCLocation getFrom(); + + MCLocation getTo(); + + void setCancelled(boolean state); + + boolean isCancelled(); + + int getSearchRadius(); + + void setSearchRadius(int radius); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCEntityPotionEffectEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCEntityPotionEffectEvent.java new file mode 100644 index 0000000000..556f839a70 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCEntityPotionEffectEvent.java @@ -0,0 +1,21 @@ +package com.laytonsmith.abstraction.events; + +import com.laytonsmith.abstraction.MCLivingEntity; +import com.laytonsmith.abstraction.enums.MCPotionAction; +import com.laytonsmith.abstraction.enums.MCPotionCause; +import com.laytonsmith.core.events.BindableEvent; + +import java.util.Optional; + +public interface MCEntityPotionEffectEvent extends BindableEvent { + + MCLivingEntity getEntity(); + + Optional getNewEffect(); + + Optional getOldEffect(); + + MCPotionAction getAction(); + + MCPotionCause getCause(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCEntityRegainHealthEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCEntityRegainHealthEvent.java new file mode 100644 index 0000000000..d1b6fb6df5 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCEntityRegainHealthEvent.java @@ -0,0 +1,16 @@ +package com.laytonsmith.abstraction.events; + +import com.laytonsmith.abstraction.MCEntity; +import com.laytonsmith.abstraction.enums.MCRegainReason; +import com.laytonsmith.core.events.BindableEvent; + +public interface MCEntityRegainHealthEvent extends BindableEvent { + + double getAmount(); + + void setAmount(double amount); + + MCEntity getEntity(); + + MCRegainReason getRegainReason(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCEntityTargetEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCEntityTargetEvent.java index 156f10dd4a..51416101fd 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCEntityTargetEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCEntityTargetEvent.java @@ -2,20 +2,18 @@ import com.laytonsmith.abstraction.MCEntity; import com.laytonsmith.abstraction.enums.MCEntityType; +import com.laytonsmith.abstraction.enums.MCTargetReason; import com.laytonsmith.core.events.BindableEvent; -/** - * - * @author EntityReborn - */ public interface MCEntityTargetEvent extends BindableEvent { - public MCEntity getTarget(); + MCEntity getTarget(); - public void setTarget(MCEntity target); + void setTarget(MCEntity target); - public MCEntity getEntity(); + MCEntity getEntity(); - public MCEntityType getEntityType(); + MCEntityType getEntityType(); -} \ No newline at end of file + MCTargetReason getReason(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCEntityToggleGlideEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCEntityToggleGlideEvent.java new file mode 100644 index 0000000000..835a1c6c04 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCEntityToggleGlideEvent.java @@ -0,0 +1,14 @@ +package com.laytonsmith.abstraction.events; + +import com.laytonsmith.abstraction.MCEntity; +import com.laytonsmith.abstraction.enums.MCEntityType; +import com.laytonsmith.core.events.BindableEvent; + +public interface MCEntityToggleGlideEvent extends BindableEvent { + + boolean isGliding(); + + MCEntity getEntity(); + + MCEntityType getEntityType(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCEntityToggleSwimEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCEntityToggleSwimEvent.java new file mode 100644 index 0000000000..7d9a553f26 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCEntityToggleSwimEvent.java @@ -0,0 +1,7 @@ +package com.laytonsmith.abstraction.events; + + +public interface MCEntityToggleSwimEvent extends MCEntityEvent { + + boolean isSwimming(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCEntityUnleashEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCEntityUnleashEvent.java new file mode 100644 index 0000000000..794f1f1b35 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCEntityUnleashEvent.java @@ -0,0 +1,12 @@ +package com.laytonsmith.abstraction.events; +import com.laytonsmith.abstraction.MCEntity; +import com.laytonsmith.abstraction.enums.MCUnleashReason; +import com.laytonsmith.core.events.BindableEvent; + +public interface MCEntityUnleashEvent extends BindableEvent { + + MCEntity getEntity(); + + MCUnleashReason getReason(); + +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCExpChangeEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCExpChangeEvent.java index fce4d2187a..bd0f7cf3ab 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCExpChangeEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCExpChangeEvent.java @@ -1,6 +1,8 @@ package com.laytonsmith.abstraction.events; public interface MCExpChangeEvent extends MCPlayerEvent { - public int getAmount(); - public void setAmount(int amount); + + int getAmount(); + + void setAmount(int amount); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCFireworkExplodeEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCFireworkExplodeEvent.java new file mode 100644 index 0000000000..c4692160a8 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCFireworkExplodeEvent.java @@ -0,0 +1,9 @@ +package com.laytonsmith.abstraction.events; + +import com.laytonsmith.abstraction.entities.MCFirework; +import com.laytonsmith.core.events.BindableEvent; + +public interface MCFireworkExplodeEvent extends BindableEvent { + + MCFirework getEntity(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCFoodLevelChangeEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCFoodLevelChangeEvent.java new file mode 100644 index 0000000000..537f198335 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCFoodLevelChangeEvent.java @@ -0,0 +1,22 @@ +package com.laytonsmith.abstraction.events; + +import com.laytonsmith.abstraction.MCHumanEntity; +import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.core.events.BindableEvent; + +public interface MCFoodLevelChangeEvent extends BindableEvent { + + MCHumanEntity getEntity(); + + int getDifference(); + + int getFoodLevel(); + + void setFoodLevel(int level); + + MCItemStack getItem(); + + boolean isCancelled(); + + void setCancelled(boolean cancel); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCGamemodeChangeEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCGamemodeChangeEvent.java index db291c7479..ad2fdf6572 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCGamemodeChangeEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCGamemodeChangeEvent.java @@ -6,6 +6,7 @@ public interface MCGamemodeChangeEvent extends BindableEvent { - public MCPlayer getPlayer(); - public MCGameMode getNewGameMode(); + MCPlayer getPlayer(); + + MCGameMode getNewGameMode(); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCHangingBreakEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCHangingBreakEvent.java index f3bcb475ac..3509925f75 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCHangingBreakEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCHangingBreakEvent.java @@ -1,17 +1,15 @@ package com.laytonsmith.abstraction.events; import com.laytonsmith.abstraction.MCEntity; -import com.laytonsmith.abstraction.MCHanging; +import com.laytonsmith.abstraction.entities.MCHanging; import com.laytonsmith.abstraction.enums.MCRemoveCause; import com.laytonsmith.core.events.BindableEvent; -/** - * - * @author Hekta - */ public interface MCHangingBreakEvent extends BindableEvent { - public MCHanging getEntity(); - public MCRemoveCause getCause(); - public MCEntity getRemover(); -} \ No newline at end of file + MCHanging getEntity(); + + MCRemoveCause getCause(); + + MCEntity getRemover(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCHangingPlaceEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCHangingPlaceEvent.java new file mode 100644 index 0000000000..7d9982cc3b --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCHangingPlaceEvent.java @@ -0,0 +1,25 @@ +package com.laytonsmith.abstraction.events; + +import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.MCPlayer; +import com.laytonsmith.abstraction.blocks.MCBlock; +import com.laytonsmith.abstraction.blocks.MCBlockFace; +import com.laytonsmith.abstraction.entities.MCHanging; +import com.laytonsmith.abstraction.enums.MCEquipmentSlot; +import com.laytonsmith.core.events.BindableEvent; + +public interface MCHangingPlaceEvent extends BindableEvent { + + MCHanging getEntity(); + + MCPlayer getPlayer(); + + MCBlock getBlock(); + + MCBlockFace getBlockFace(); + + MCItemStack getItem(); + + MCEquipmentSlot getHand(); + +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCInventoryClickEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCInventoryClickEvent.java index 2ce90d05ed..819264fcd4 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCInventoryClickEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCInventoryClickEvent.java @@ -6,28 +6,38 @@ import com.laytonsmith.abstraction.enums.MCInventoryAction; import com.laytonsmith.abstraction.enums.MCSlotType; -/** - * - * @author jb_aero - */ public interface MCInventoryClickEvent extends MCInventoryInteractEvent { - public MCInventoryAction getAction(); - public MCClickType getClickType(); - - public MCItemStack getCurrentItem(); - public MCItemStack getCursor(); - public int getSlot(); - public int getRawSlot(); - public MCSlotType getSlotType(); + + MCInventoryAction getAction(); + + MCClickType getClickType(); + + MCItemStack getCurrentItem(); + + MCItemStack getCursor(); + + int getSlot(); + + int getRawSlot(); + + int getHotbarButton(); + + MCSlotType getSlotType(); + @Override - public MCHumanEntity getWhoClicked(); + MCHumanEntity getWhoClicked(); + + boolean isLeftClick(); + + boolean isRightClick(); + + boolean isShiftClick(); + + boolean isCreativeClick(); + + boolean isKeyboardClick(); - public boolean isLeftClick(); - public boolean isRightClick(); - public boolean isShiftClick(); - public boolean isCreativeClick(); - public boolean isKeyboardClick(); + void setCurrentItem(MCItemStack slot); - public void setCurrentItem(MCItemStack slot); - public void setCursor(MCItemStack cursor); + void setCursor(MCItemStack cursor); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCInventoryCloseEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCInventoryCloseEvent.java index 023f81ef88..e7e35b2070 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCInventoryCloseEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCInventoryCloseEvent.java @@ -2,10 +2,7 @@ import com.laytonsmith.abstraction.MCHumanEntity; -/** - * - * @author import - */ public interface MCInventoryCloseEvent extends MCInventoryEvent { - public MCHumanEntity getPlayer(); + + MCHumanEntity getPlayer(); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCInventoryDragEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCInventoryDragEvent.java index 9437a59c58..aaa240dddd 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCInventoryDragEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCInventoryDragEvent.java @@ -5,16 +5,19 @@ import java.util.Map; import java.util.Set; -/** - * - * @author import - */ public interface MCInventoryDragEvent extends MCInventoryInteractEvent { - public Map getNewItems(); - public Set getRawSlots(); - public Set getInventorySlots(); - public MCItemStack getCursor(); - public void setCursor(MCItemStack newCursor); - public MCItemStack getOldCursor(); - public MCDragType getType(); + + Map getNewItems(); + + Set getRawSlots(); + + Set getInventorySlots(); + + MCItemStack getCursor(); + + void setCursor(MCItemStack newCursor); + + MCItemStack getOldCursor(); + + MCDragType getType(); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCInventoryEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCInventoryEvent.java index a7f7277d64..b6772730cf 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCInventoryEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCInventoryEvent.java @@ -6,12 +6,11 @@ import com.laytonsmith.core.events.BindableEvent; import java.util.List; -/** - * - * @author import - */ public interface MCInventoryEvent extends BindableEvent { - public MCInventory getInventory(); - public MCInventoryView getView(); - public List getViewers(); + + MCInventory getInventory(); + + MCInventoryView getView(); + + List getViewers(); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCInventoryInteractEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCInventoryInteractEvent.java index b7e988c88e..cbd8c300ac 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCInventoryInteractEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCInventoryInteractEvent.java @@ -3,14 +3,15 @@ import com.laytonsmith.abstraction.MCHumanEntity; import com.laytonsmith.abstraction.enums.MCResult; -/** - * - * @author import - */ public interface MCInventoryInteractEvent extends MCInventoryEvent { - public MCHumanEntity getWhoClicked(); - public void setResult(MCResult newResult); - public MCResult getResult(); - public boolean isCanceled(); - public void setCancelled(boolean toCancel); + + MCHumanEntity getWhoClicked(); + + void setResult(MCResult newResult); + + MCResult getResult(); + + boolean isCancelled(); + + void setCancelled(boolean toCancel); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCInventoryOpenEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCInventoryOpenEvent.java index 7bb5c597a1..d091a7a832 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCInventoryOpenEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCInventoryOpenEvent.java @@ -2,10 +2,7 @@ import com.laytonsmith.abstraction.MCHumanEntity; -/** - * - * @author import - */ public interface MCInventoryOpenEvent extends MCInventoryEvent { - public MCHumanEntity getPlayer(); + + MCHumanEntity getPlayer(); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCItemDespawnEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCItemDespawnEvent.java new file mode 100644 index 0000000000..b1731e0366 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCItemDespawnEvent.java @@ -0,0 +1,12 @@ +package com.laytonsmith.abstraction.events; + +import com.laytonsmith.abstraction.entities.MCItem; +import com.laytonsmith.abstraction.MCLocation; +import com.laytonsmith.core.events.BindableEvent; + +public interface MCItemDespawnEvent extends BindableEvent { + + MCItem getEntity(); + + MCLocation getLocation(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCItemHeldEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCItemHeldEvent.java index 368004515a..0a5c7d8d63 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCItemHeldEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCItemHeldEvent.java @@ -1,6 +1,8 @@ package com.laytonsmith.abstraction.events; public interface MCItemHeldEvent extends MCPlayerEvent { - public int getNewSlot(); - public int getPreviousSlot(); + + int getNewSlot(); + + int getPreviousSlot(); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCItemSpawnEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCItemSpawnEvent.java index ae2d954630..c528647800 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCItemSpawnEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCItemSpawnEvent.java @@ -1,12 +1,12 @@ package com.laytonsmith.abstraction.events; -import com.laytonsmith.abstraction.MCItem; +import com.laytonsmith.abstraction.entities.MCItem; import com.laytonsmith.abstraction.MCLocation; import com.laytonsmith.core.events.BindableEvent; public interface MCItemSpawnEvent extends BindableEvent { - public MCItem getEntity(); - - public MCLocation getLocation(); + MCItem getEntity(); + + MCLocation getLocation(); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCItemSwapEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCItemSwapEvent.java new file mode 100644 index 0000000000..7a4677bee6 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCItemSwapEvent.java @@ -0,0 +1,14 @@ +package com.laytonsmith.abstraction.events; + +import com.laytonsmith.abstraction.MCItemStack; + +public interface MCItemSwapEvent extends MCPlayerEvent { + + MCItemStack getMainHandItem(); + + MCItemStack getOffHandItem(); + + void setMainHandItem(MCItemStack item); + + void setOffHandItem(MCItemStack item); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCLightningStrikeEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCLightningStrikeEvent.java new file mode 100644 index 0000000000..5dbc28dcd0 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCLightningStrikeEvent.java @@ -0,0 +1,21 @@ +package com.laytonsmith.abstraction.events; + +import com.laytonsmith.abstraction.entities.MCLightningStrike; + +public interface MCLightningStrikeEvent extends MCWeatherEvent { + + MCLightningStrike getLightning(); + + Cause getCause(); + + enum Cause { + COMMAND, + CUSTOM, + ENCHANTMENT, + SPAWNER, + TRIDENT, + TRAP, + WEATHER, + UNKNOWN + } +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCNotePlayEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCNotePlayEvent.java new file mode 100644 index 0000000000..f0f538b7a5 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCNotePlayEvent.java @@ -0,0 +1,9 @@ +package com.laytonsmith.abstraction.events; + +import com.laytonsmith.abstraction.MCNote; +import com.laytonsmith.abstraction.enums.MCInstrument; + +public interface MCNotePlayEvent extends MCBlockEvent { + MCNote getNote(); + MCInstrument getInstrument(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerAdvancementDoneEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerAdvancementDoneEvent.java new file mode 100644 index 0000000000..cc516d6208 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerAdvancementDoneEvent.java @@ -0,0 +1,8 @@ +package com.laytonsmith.abstraction.events; + +import com.laytonsmith.abstraction.MCNamespacedKey; + +public interface MCPlayerAdvancementDoneEvent extends MCPlayerEvent { + MCNamespacedKey getAdvancementKey(); + String getTitle(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerBedEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerBedEvent.java deleted file mode 100644 index 26546ed3a9..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerBedEvent.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.laytonsmith.abstraction.events; - -import com.laytonsmith.abstraction.blocks.MCBlock; - -/** - * - * @author Jason Unger - */ -public interface MCPlayerBedEvent extends MCPlayerEvent { - public MCBlock getBed(); -} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerBucketEmptyEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerBucketEmptyEvent.java new file mode 100644 index 0000000000..b3a71153b2 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerBucketEmptyEvent.java @@ -0,0 +1,4 @@ +package com.laytonsmith.abstraction.events; + +public interface MCPlayerBucketEmptyEvent extends MCPlayerEvent { +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerBucketEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerBucketEvent.java new file mode 100644 index 0000000000..9e3c7fce8a --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerBucketEvent.java @@ -0,0 +1,21 @@ +package com.laytonsmith.abstraction.events; + +import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.blocks.MCBlock; +import com.laytonsmith.abstraction.blocks.MCBlockFace; +import com.laytonsmith.abstraction.blocks.MCMaterial; +import com.laytonsmith.abstraction.enums.MCEquipmentSlot; + +public interface MCPlayerBucketEvent extends MCPlayerEvent { + MCBlock getBlock(); + + MCBlock getBlockClicked(); + + MCBlockFace getBlockFace(); + + MCMaterial getBucket(); + + MCEquipmentSlot getHand(); + + MCItemStack getItemStack(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerBucketFillEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerBucketFillEvent.java new file mode 100644 index 0000000000..b95c7e6bd7 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerBucketFillEvent.java @@ -0,0 +1,4 @@ +package com.laytonsmith.abstraction.events; + +public interface MCPlayerBucketFillEvent extends MCPlayerEvent { +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerChatEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerChatEvent.java index 5f6478fd07..f1f2445292 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerChatEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerChatEvent.java @@ -3,20 +3,17 @@ import com.laytonsmith.abstraction.MCPlayer; import java.util.List; -/** - * - * - */ -public interface MCPlayerChatEvent extends MCPlayerEvent{ - public String getMessage(); - - public void setMessage(String message); - - public String getFormat(); - - public void setFormat(String format); - - public List getRecipients(); - - public void setRecipients(List list); +public interface MCPlayerChatEvent extends MCPlayerEvent { + + String getMessage(); + + void setMessage(String message); + + String getFormat(); + + void setFormat(String format); + + List getRecipients(); + + void setRecipients(List list); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerCommandEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerCommandEvent.java index 0a4754bad5..e425f550d6 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerCommandEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerCommandEvent.java @@ -1,15 +1,12 @@ package com.laytonsmith.abstraction.events; -/** - * - * - */ public interface MCPlayerCommandEvent extends MCPlayerEvent { - public String getCommand(); - public void cancel(); + String getCommand(); - public void setCommand(String val); + void cancel(); - public boolean isCancelled(); + void setCommand(String val); + + boolean isCancelled(); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerDeathEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerDeathEvent.java index 7146805948..243628f29c 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerDeathEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerDeathEvent.java @@ -3,34 +3,34 @@ import com.laytonsmith.abstraction.MCEntity; import com.laytonsmith.abstraction.MCPlayer; -/** - * - * - */ public interface MCPlayerDeathEvent extends MCEntityDeathEvent { @Override - public MCPlayer getEntity(); - - public String getDeathMessage(); - - public void setDeathMessage(String nval); - - public boolean getKeepLevel(); - - public void setKeepLevel(boolean keepLevel); - - public int getNewExp(); - - public void setNewExp(int exp); - - public int getNewLevel(); - - public void setNewLevel(int level); - - public int getNewTotalExp(); - - public void setNewTotalExp(int totalExp); - - public MCEntity getKiller(); + MCPlayer getEntity(); + + String getDeathMessage(); + + void setDeathMessage(String nval); + + boolean getKeepLevel(); + + void setKeepLevel(boolean keepLevel); + + boolean getKeepInventory(); + + void setKeepInventory(boolean keepLevel); + + int getNewExp(); + + void setNewExp(int exp); + + int getNewLevel(); + + void setNewLevel(int level); + + int getNewTotalExp(); + + void setNewTotalExp(int totalExp); + + MCEntity getKiller(); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerDropItemEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerDropItemEvent.java index e0d1e435b7..a25dd5b941 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerDropItemEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerDropItemEvent.java @@ -1,16 +1,15 @@ - package com.laytonsmith.abstraction.events; -import com.laytonsmith.abstraction.MCItem; +import com.laytonsmith.abstraction.entities.MCItem; import com.laytonsmith.abstraction.MCItemStack; -/** - * - * @author jb_aero - */ public interface MCPlayerDropItemEvent extends MCPlayerEvent { - public MCItem getItemDrop(); - public void setItemStack(MCItemStack stack); - public boolean isCancelled(); - public void setCancelled(boolean cancelled); + + MCItem getItemDrop(); + + void setItemStack(MCItemStack stack); + + boolean isCancelled(); + + void setCancelled(boolean cancelled); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerEditBookEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerEditBookEvent.java index 09cabf1d1a..83b38a0803 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerEditBookEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerEditBookEvent.java @@ -2,18 +2,17 @@ import com.laytonsmith.abstraction.MCBookMeta; -/** - * - * @author Hekta - */ public interface MCPlayerEditBookEvent extends MCPlayerEvent { - public MCBookMeta getNewBookMeta(); - public MCBookMeta getPreviousBookMeta(); - public void setNewBookMeta(MCBookMeta bookMeta); + MCBookMeta getNewBookMeta(); - public int getSlot(); + MCBookMeta getPreviousBookMeta(); - public boolean isSigning(); - public void setSigning(boolean isSigning); -} \ No newline at end of file + void setNewBookMeta(MCBookMeta bookMeta); + + int getSlot(); + + boolean isSigning(); + + void setSigning(boolean isSigning); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerEnterBedEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerEnterBedEvent.java new file mode 100644 index 0000000000..89d93501af --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerEnterBedEvent.java @@ -0,0 +1,10 @@ +package com.laytonsmith.abstraction.events; + +import com.laytonsmith.abstraction.blocks.MCBlock; +import com.laytonsmith.abstraction.enums.MCEnterBedResult; + +public interface MCPlayerEnterBedEvent extends MCPlayerEvent { + + MCBlock getBed(); + MCEnterBedResult getResult(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerEvent.java index c837d1e802..9fffce9593 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerEvent.java @@ -3,10 +3,7 @@ import com.laytonsmith.abstraction.MCPlayer; import com.laytonsmith.core.events.BindableEvent; -/** - * - * @author Jason Unger - */ public interface MCPlayerEvent extends BindableEvent { + MCPlayer getPlayer(); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerFishEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerFishEvent.java index c5e0b490b8..12ac569bf0 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerFishEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerFishEvent.java @@ -2,12 +2,21 @@ import com.laytonsmith.abstraction.MCEntity; import com.laytonsmith.abstraction.entities.MCFishHook; +import com.laytonsmith.abstraction.enums.MCEquipmentSlot; import com.laytonsmith.abstraction.enums.MCFishingState; public interface MCPlayerFishEvent extends MCPlayerEvent { - public MCEntity getCaught(); - public int getExpToDrop(); - public MCFishHook getHook(); - public MCFishingState getState(); - public void setExpToDrop(int exp); + + MCEntity getCaught(); + + int getExpToDrop(); + + MCFishHook getHook(); + + MCFishingState getState(); + + void setExpToDrop(int exp); + + MCEquipmentSlot getHand(); + } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerInteractAtEntityEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerInteractAtEntityEvent.java new file mode 100644 index 0000000000..ff7dd1a8fd --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerInteractAtEntityEvent.java @@ -0,0 +1,8 @@ +package com.laytonsmith.abstraction.events; + +import com.laytonsmith.PureUtilities.Vector3D; + +public interface MCPlayerInteractAtEntityEvent extends MCPlayerInteractEntityEvent { + + Vector3D getClickedPosition(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerInteractEntityEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerInteractEntityEvent.java index bd54bc75be..143d4f197b 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerInteractEntityEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerInteractEntityEvent.java @@ -1,13 +1,15 @@ package com.laytonsmith.abstraction.events; import com.laytonsmith.abstraction.MCEntity; +import com.laytonsmith.abstraction.enums.MCEquipmentSlot; -/** - * - * @author jb_aero - */ public interface MCPlayerInteractEntityEvent extends MCPlayerEvent { - public MCEntity getEntity(); - public boolean isCancelled(); - public void setCancelled(boolean cancelled); -} \ No newline at end of file + + MCEntity getEntity(); + + boolean isCancelled(); + + void setCancelled(boolean cancelled); + + MCEquipmentSlot getHand(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerInteractEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerInteractEvent.java index e8668ffbab..f367199657 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerInteractEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerInteractEvent.java @@ -1,22 +1,24 @@ package com.laytonsmith.abstraction.events; +import com.laytonsmith.PureUtilities.Vector3D; import com.laytonsmith.abstraction.MCItemStack; import com.laytonsmith.abstraction.blocks.MCBlock; import com.laytonsmith.abstraction.blocks.MCBlockFace; import com.laytonsmith.abstraction.enums.MCAction; +import com.laytonsmith.abstraction.enums.MCEquipmentSlot; -/** - * - * - */ -public interface MCPlayerInteractEvent extends MCPlayerEvent{ +public interface MCPlayerInteractEvent extends MCPlayerEvent { - public MCAction getAction(); + MCAction getAction(); - public MCBlock getClickedBlock(); + MCBlock getClickedBlock(); + + MCBlockFace getBlockFace(); + + MCItemStack getItem(); + + MCEquipmentSlot getHand(); + + Vector3D getClickedPosition(); - public MCBlockFace getBlockFace(); - - public MCItemStack getItem(); - } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerItemConsumeEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerItemConsumeEvent.java index 8dbeda7c8a..3e63da8e6c 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerItemConsumeEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerItemConsumeEvent.java @@ -1,12 +1,14 @@ package com.laytonsmith.abstraction.events; import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.enums.MCEquipmentSlot; -/** - * - * @author jb_aero - */ public interface MCPlayerItemConsumeEvent extends MCPlayerEvent { - public MCItemStack getItem(); - public void setItem(MCItemStack item); + + MCItemStack getItem(); + + void setItem(MCItemStack item); + + MCEquipmentSlot getHand(); + } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerJoinEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerJoinEvent.java index 9ab16ab239..b4bf277282 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerJoinEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerJoinEvent.java @@ -1,10 +1,8 @@ package com.laytonsmith.abstraction.events; -/** - * - * - */ -public interface MCPlayerJoinEvent extends MCPlayerEvent{ - public String getJoinMessage(); - public void setJoinMessage(String message); +public interface MCPlayerJoinEvent extends MCPlayerEvent { + + String getJoinMessage(); + + void setJoinMessage(String message); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerKickEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerKickEvent.java index fcf1c01d8b..3a73c28abe 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerKickEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerKickEvent.java @@ -1,14 +1,16 @@ package com.laytonsmith.abstraction.events; -/** - * - * @author jb_aero - */ -public interface MCPlayerKickEvent extends MCPlayerEvent{ - public String getMessage(); - public void setMessage(String message); - public String getReason(); - public void setReason(String message); - public boolean isCancelled(); - public void setCancelled(boolean cancelled); +public interface MCPlayerKickEvent extends MCPlayerEvent { + + String getMessage(); + + void setMessage(String message); + + String getReason(); + + void setReason(String message); + + boolean isCancelled(); + + void setCancelled(boolean cancelled); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerLeaveBedEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerLeaveBedEvent.java new file mode 100644 index 0000000000..c316223776 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerLeaveBedEvent.java @@ -0,0 +1,8 @@ +package com.laytonsmith.abstraction.events; + +import com.laytonsmith.abstraction.blocks.MCBlock; + +public interface MCPlayerLeaveBedEvent extends MCPlayerEvent { + + MCBlock getBed(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerLoginEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerLoginEvent.java index eee9ac5d64..e3fbb1df4e 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerLoginEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerLoginEvent.java @@ -1,14 +1,20 @@ package com.laytonsmith.abstraction.events; -/** - * - * @author EntityReborn - */ -public interface MCPlayerLoginEvent extends MCPlayerEvent{ - public String getName(); - public String getKickMessage(); - public void setKickMessage(String msg); - public String getResult(); - public void setResult(String rst); - public String getIP(); +public interface MCPlayerLoginEvent extends MCPlayerEvent { + + String getName(); + + String getUniqueId(); + + String getKickMessage(); + + void setKickMessage(String msg); + + String getResult(); + + void setResult(String rst); + + String getIP(); + + String getHostname(); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerMoveEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerMoveEvent.java index 987460dfa7..acfc8be2c4 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerMoveEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerMoveEvent.java @@ -2,13 +2,15 @@ import com.laytonsmith.abstraction.MCLocation; -/** - * - * - */ public interface MCPlayerMoveEvent extends MCPlayerEvent { - public MCLocation getFrom(); - public MCLocation getTo(); - public void setCancelled(boolean state); - public boolean isCancelled(); + + int getThreshold(); + + MCLocation getFrom(); + + MCLocation getTo(); + + void setCancelled(boolean state); + + boolean isCancelled(); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerPickupItemEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerPickupItemEvent.java index 1c32180d38..e681f6ca12 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerPickupItemEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerPickupItemEvent.java @@ -1,16 +1,17 @@ package com.laytonsmith.abstraction.events; -import com.laytonsmith.abstraction.MCItem; +import com.laytonsmith.abstraction.entities.MCItem; import com.laytonsmith.abstraction.MCItemStack; -/** - * - * @author import - */ public interface MCPlayerPickupItemEvent extends MCPlayerEvent { - public int getRemaining(); - public MCItem getItem(); - public void setItemStack(MCItemStack stack); - public boolean isCancelled(); - public void setCancelled(boolean cancelled); + + int getRemaining(); + + MCItem getItem(); + + void setItemStack(MCItemStack stack); + + boolean isCancelled(); + + void setCancelled(boolean cancelled); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerPortalEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerPortalEvent.java index 038b3adafd..c5d7b55bfa 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerPortalEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerPortalEvent.java @@ -1,10 +1,16 @@ package com.laytonsmith.abstraction.events; -import com.laytonsmith.abstraction.MCTravelAgent; - public interface MCPlayerPortalEvent extends MCPlayerTeleportEvent { - public void useTravelAgent(boolean useTravelAgent); - public boolean useTravelAgent(); - public MCTravelAgent getPortalTravelAgent(); - public void setPortalTravelAgent(MCTravelAgent travelAgent); + + int getSearchRadius(); + + void setSearchRadius(int radius); + + int getCreationRadius(); + + void setCreationRadius(int radius); + + boolean canCreatePortal(); + + void setCanCreatePortal(boolean canCreate); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerPreLoginEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerPreLoginEvent.java deleted file mode 100644 index 1e12224adc..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerPreLoginEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.laytonsmith.abstraction.events; - -import com.laytonsmith.core.events.BindableEvent; - -/** - * - * @author EntityReborn - */ -public interface MCPlayerPreLoginEvent extends BindableEvent{ - public String getName(); - public String getKickMessage(); - public void setKickMessage(String msg); - public String getResult(); - public void setResult(String rst); - public String getIP(); -} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerQuitEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerQuitEvent.java index 5f35bee6e1..dc39ccdaa5 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerQuitEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerQuitEvent.java @@ -1,10 +1,8 @@ package com.laytonsmith.abstraction.events; -/** - * - * @author EntityReborn - */ -public interface MCPlayerQuitEvent extends MCPlayerEvent{ - public String getMessage(); - public void setMessage(String message); +public interface MCPlayerQuitEvent extends MCPlayerEvent { + + String getMessage(); + + void setMessage(String message); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerResourcePackEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerResourcePackEvent.java new file mode 100644 index 0000000000..275450bf3d --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerResourcePackEvent.java @@ -0,0 +1,10 @@ +package com.laytonsmith.abstraction.events; + +import com.laytonsmith.abstraction.enums.MCResourcePackStatus; + +import java.util.UUID; + +public interface MCPlayerResourcePackEvent extends MCPlayerEvent { + MCResourcePackStatus getStatus(); + UUID getId(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerRespawnEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerRespawnEvent.java index c942a068be..33e38b7754 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerRespawnEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerRespawnEvent.java @@ -2,16 +2,21 @@ import com.laytonsmith.abstraction.MCLocation; -/** - * - * - */ public interface MCPlayerRespawnEvent extends MCPlayerEvent { - public void setRespawnLocation(MCLocation location); + void setRespawnLocation(MCLocation location); - public MCLocation getRespawnLocation(); + MCLocation getRespawnLocation(); - public Boolean isBedSpawn(); + Boolean isBedSpawn(); + boolean isAnchorSpawn(); + + Reason getReason(); + + enum Reason { + DEATH, + END_PORTAL, + PLUGIN + } } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerStopUsingItemEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerStopUsingItemEvent.java new file mode 100644 index 0000000000..72ada84f52 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerStopUsingItemEvent.java @@ -0,0 +1,8 @@ +package com.laytonsmith.abstraction.events; + +import com.laytonsmith.abstraction.MCItemStack; + +public interface MCPlayerStopUsingItemEvent extends MCPlayerEvent { + MCItemStack getItem(); + int getTicksHeldFor(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerTeleportEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerTeleportEvent.java index 7a00b827be..abe3a1f76e 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerTeleportEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerTeleportEvent.java @@ -3,12 +3,19 @@ import com.laytonsmith.abstraction.MCLocation; import com.laytonsmith.abstraction.enums.MCTeleportCause; -/** - * - * - */ -public interface MCPlayerTeleportEvent extends MCPlayerMoveEvent { - public MCTeleportCause getCause(); - public void setFrom(MCLocation oldloc); - public void setTo(MCLocation newloc); -} \ No newline at end of file +public interface MCPlayerTeleportEvent extends MCPlayerEvent { + + MCTeleportCause getCause(); + + void setFrom(MCLocation oldloc); + + void setTo(MCLocation newloc); + + MCLocation getFrom(); + + MCLocation getTo(); + + void setCancelled(boolean state); + + boolean isCancelled(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerToggleFlightEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerToggleFlightEvent.java index e4c13009cb..9f8312ea79 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerToggleFlightEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerToggleFlightEvent.java @@ -1,15 +1,10 @@ - package com.laytonsmith.abstraction.events; -import com.laytonsmith.abstraction.MCPlayer; +public interface MCPlayerToggleFlightEvent extends MCPlayerEvent { -/** - * - * @author Jason Unger - */ -public interface MCPlayerToggleFlightEvent { boolean isFlying(); - MCPlayer getPlayer(); + void setCancelled(boolean state); + boolean isCancelled(); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerToggleSneakEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerToggleSneakEvent.java index 99a0c5c936..a83a698970 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerToggleSneakEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerToggleSneakEvent.java @@ -1,15 +1,10 @@ - package com.laytonsmith.abstraction.events; -import com.laytonsmith.abstraction.MCPlayer; +public interface MCPlayerToggleSneakEvent extends MCPlayerEvent { -/** - * - * @author Jason Unger - */ -public interface MCPlayerToggleSneakEvent { boolean isSneaking(); - MCPlayer getPlayer(); + void setCancelled(boolean state); + boolean isCancelled(); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerToggleSprintEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerToggleSprintEvent.java index 5cb97fe924..d633ed47bc 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCPlayerToggleSprintEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPlayerToggleSprintEvent.java @@ -1,15 +1,10 @@ - package com.laytonsmith.abstraction.events; -import com.laytonsmith.abstraction.MCPlayer; +public interface MCPlayerToggleSprintEvent extends MCPlayerEvent { -/** - * - * @author Jason Unger - */ -public interface MCPlayerToggleSprintEvent { boolean isSprinting(); - MCPlayer getPlayer(); + void setCancelled(boolean state); + boolean isCancelled(); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPluginIncomingMessageEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPluginIncomingMessageEvent.java index 984ee84972..d1c9537f4d 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCPluginIncomingMessageEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPluginIncomingMessageEvent.java @@ -3,12 +3,11 @@ import com.laytonsmith.abstraction.MCPlayer; import com.laytonsmith.core.events.BindableEvent; -/** - * - * @author Jason Unger - */ public interface MCPluginIncomingMessageEvent extends BindableEvent { - public String getChannel(); - public byte[] getBytes(); - public MCPlayer getPlayer(); + + String getChannel(); + + byte[] getBytes(); + + MCPlayer getPlayer(); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPotionSplashEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPotionSplashEvent.java index 8e4610ca5b..696eb6e525 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCPotionSplashEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPotionSplashEvent.java @@ -1,14 +1,16 @@ package com.laytonsmith.abstraction.events; +import com.laytonsmith.abstraction.entities.MCThrownPotion; import com.laytonsmith.abstraction.MCLivingEntity; -import java.util.Set; - -/** - * - * @author jb_aero - */ -public interface MCPotionSplashEvent extends MCProjectileHitEvent { - public Set getAffectedEntities(); - public double getIntensity(MCLivingEntity le); - public void setIntensity(MCLivingEntity le, double intensity); +import com.laytonsmith.core.events.BindableEvent; + +import java.util.Map; + +public interface MCPotionSplashEvent extends BindableEvent { + + MCThrownPotion getEntity(); + + Map getAffectedEntities(); + + void setIntensity(MCLivingEntity le, double intensity); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPrepareAnvilEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPrepareAnvilEvent.java new file mode 100644 index 0000000000..eabe8e993f --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPrepareAnvilEvent.java @@ -0,0 +1,10 @@ +package com.laytonsmith.abstraction.events; + +import com.laytonsmith.abstraction.MCPlayer; +import com.laytonsmith.abstraction.MCItemStack; + +public interface MCPrepareAnvilEvent extends MCInventoryEvent { + MCPlayer getPlayer(); + + void setResult(MCItemStack i); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPrepareGrindstoneEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPrepareGrindstoneEvent.java new file mode 100644 index 0000000000..9d9a90b8f3 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPrepareGrindstoneEvent.java @@ -0,0 +1,10 @@ +package com.laytonsmith.abstraction.events; + +import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.MCPlayer; + +public interface MCPrepareGrindstoneEvent extends MCInventoryEvent { + MCPlayer getPlayer(); + + void setResult(MCItemStack stack); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPrepareItemCraftEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPrepareItemCraftEvent.java index 24ff536467..3941aa7cef 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCPrepareItemCraftEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPrepareItemCraftEvent.java @@ -1,11 +1,16 @@ package com.laytonsmith.abstraction.events; import com.laytonsmith.abstraction.MCCraftingInventory; +import com.laytonsmith.abstraction.MCPlayer; import com.laytonsmith.abstraction.MCRecipe; public interface MCPrepareItemCraftEvent extends MCInventoryEvent { - public MCRecipe getRecipe(); - public boolean isRepair(); + MCPlayer getPlayer(); + + MCRecipe getRecipe(); + + boolean isRepair(); + @Override - public MCCraftingInventory getInventory(); + MCCraftingInventory getInventory(); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPrepareItemEnchantEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPrepareItemEnchantEvent.java index 70f298b405..a1ee9afa98 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCPrepareItemEnchantEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPrepareItemEnchantEvent.java @@ -1,22 +1,21 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ package com.laytonsmith.abstraction.events; +import com.laytonsmith.abstraction.MCEnchantmentOffer; import com.laytonsmith.abstraction.MCItemStack; import com.laytonsmith.abstraction.MCPlayer; import com.laytonsmith.abstraction.blocks.MCBlock; -/** - * - * @author cgallarno - */ public interface MCPrepareItemEnchantEvent extends MCInventoryEvent { - public MCBlock getEnchantBlock(); - public MCPlayer getEnchanter(); - public int getEnchantmentBonus(); - public int[] getExpLevelCostsOffered(); - public MCItemStack getItem(); - public void setItem(MCItemStack i); + + MCBlock getEnchantBlock(); + + MCPlayer getEnchanter(); + + int getEnchantmentBonus(); + + MCEnchantmentOffer[] getOffers(); + + MCItemStack getItem(); + + void setItem(MCItemStack i); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCPrepareSmithingEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCPrepareSmithingEvent.java new file mode 100644 index 0000000000..ca773cd476 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCPrepareSmithingEvent.java @@ -0,0 +1,10 @@ +package com.laytonsmith.abstraction.events; + +import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.MCPlayer; + +public interface MCPrepareSmithingEvent extends MCInventoryEvent { + MCPlayer getPlayer(); + + void setResult(MCItemStack stack); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCProjectileHitEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCProjectileHitEvent.java index b1374db0b0..be7151ed32 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCProjectileHitEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCProjectileHitEvent.java @@ -1,14 +1,21 @@ package com.laytonsmith.abstraction.events; -import com.laytonsmith.abstraction.MCProjectile; +import com.laytonsmith.abstraction.MCEntity; +import com.laytonsmith.abstraction.entities.MCProjectile; +import com.laytonsmith.abstraction.blocks.MCBlock; +import com.laytonsmith.abstraction.blocks.MCBlockFace; import com.laytonsmith.abstraction.enums.MCEntityType; import com.laytonsmith.core.events.BindableEvent; -/** - * - * @author jb_aero - */ public interface MCProjectileHitEvent extends BindableEvent { - public MCProjectile getEntity(); - public MCEntityType getEntityType(); + + MCProjectile getEntity(); + + MCEntityType getEntityType(); + + MCEntity getHitEntity(); + + MCBlock getHitBlock(); + + MCBlockFace getHitFace(); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCProjectileLaunchEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCProjectileLaunchEvent.java index f84629a785..4089a062b0 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCProjectileLaunchEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCProjectileLaunchEvent.java @@ -1,16 +1,12 @@ package com.laytonsmith.abstraction.events; -import com.laytonsmith.abstraction.MCProjectile; +import com.laytonsmith.abstraction.entities.MCProjectile; import com.laytonsmith.abstraction.enums.MCEntityType; import com.laytonsmith.core.events.BindableEvent; -/** - * - * @author Hekta - */ public interface MCProjectileLaunchEvent extends BindableEvent { - public MCProjectile getEntity(); + MCProjectile getEntity(); - public MCEntityType getEntityType(); + MCEntityType getEntityType(); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCRedstoneChangedEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCRedstoneChangedEvent.java index dc0b828e02..f03448f06f 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCRedstoneChangedEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCRedstoneChangedEvent.java @@ -3,10 +3,9 @@ import com.laytonsmith.abstraction.MCLocation; import com.laytonsmith.core.events.BindableEvent; -/** - * - */ public interface MCRedstoneChangedEvent extends BindableEvent { + boolean isActive(); + MCLocation getLocation(); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCServerCommandEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCServerCommandEvent.java new file mode 100644 index 0000000000..dd8bc83a6a --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCServerCommandEvent.java @@ -0,0 +1,13 @@ +package com.laytonsmith.abstraction.events; + +import com.laytonsmith.abstraction.MCCommandSender; +import com.laytonsmith.core.events.BindableEvent; + +public interface MCServerCommandEvent extends BindableEvent { + + String getCommand(); + + void setCommand(String command); + + MCCommandSender getCommandSender(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCServerPingEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCServerPingEvent.java index 0c6d5b8494..e0e0e4660c 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCServerPingEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCServerPingEvent.java @@ -6,25 +6,21 @@ import java.util.Collection; import java.util.Set; -/** - * - * @author jb_aero - */ public interface MCServerPingEvent extends BindableEvent { - public InetAddress getAddress(); + InetAddress getAddress(); - public int getMaxPlayers(); + int getMaxPlayers(); - public String getMOTD(); + String getMOTD(); - public int getNumPlayers(); + int getNumPlayers(); - public void setMaxPlayers(int max); + void setMaxPlayers(int max); - public void setMOTD(String motd); + void setMOTD(String motd); - public Set getPlayers(); + Set getPlayers(); - public void setPlayers(Collection players); -} \ No newline at end of file + void setPlayers(Collection players); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCSignChangeEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCSignChangeEvent.java index b503391b35..eff7fcee8f 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCSignChangeEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCSignChangeEvent.java @@ -2,25 +2,24 @@ import com.laytonsmith.abstraction.MCPlayer; import com.laytonsmith.abstraction.blocks.MCBlock; +import com.laytonsmith.abstraction.blocks.MCSign; import com.laytonsmith.core.constructs.CArray; import com.laytonsmith.core.constructs.CString; import com.laytonsmith.core.events.BindableEvent; -/** - * - * @author EntityReborn - */ public interface MCSignChangeEvent extends BindableEvent { - public MCPlayer getPlayer(); + MCPlayer getPlayer(); - public MCBlock getBlock(); + MCBlock getBlock(); - public CString getLine(int index); + CString getLine(int index); - public void setLine(int index, String text); + void setLine(int index, String text); - public void setLines(String[] lines); + void setLines(String[] lines); - public CArray getLines(); -} \ No newline at end of file + CArray getLines(); + + MCSign.Side getSide(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCStructureGrowEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCStructureGrowEvent.java index 7bf5b8dca3..697cf2551d 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCStructureGrowEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCStructureGrowEvent.java @@ -1,24 +1,20 @@ -package com.laytonsmith.abstraction.events; - -import com.laytonsmith.abstraction.MCLocation; -import com.laytonsmith.abstraction.MCPlayer; -import com.laytonsmith.abstraction.blocks.MCBlockState; -import com.laytonsmith.abstraction.enums.MCTreeType; -import java.util.List; - -/** - * - * @author KingFisher - */ -public interface MCStructureGrowEvent extends MCWorldEvent { - - public List getBlocks(); - - public MCLocation getLocation(); - - public MCPlayer getPlayer(); - - public MCTreeType getSpecies(); - - public boolean isFromBonemeal(); -} \ No newline at end of file +package com.laytonsmith.abstraction.events; + +import com.laytonsmith.abstraction.MCLocation; +import com.laytonsmith.abstraction.MCPlayer; +import com.laytonsmith.abstraction.blocks.MCBlockState; +import com.laytonsmith.abstraction.enums.MCTreeType; +import java.util.List; + +public interface MCStructureGrowEvent extends MCWorldEvent { + + List getBlocks(); + + MCLocation getLocation(); + + MCPlayer getPlayer(); + + MCTreeType getSpecies(); + + boolean isFromBonemeal(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCThunderChangeEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCThunderChangeEvent.java new file mode 100644 index 0000000000..73de9df52d --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCThunderChangeEvent.java @@ -0,0 +1,6 @@ +package com.laytonsmith.abstraction.events; + +public interface MCThunderChangeEvent extends MCWeatherEvent { + + boolean toThunderState(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCVehicleBlockCollideEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCVehicleBlockCollideEvent.java index 07b5b72cd1..587f19011c 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCVehicleBlockCollideEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCVehicleBlockCollideEvent.java @@ -3,5 +3,6 @@ import com.laytonsmith.abstraction.blocks.MCBlock; public interface MCVehicleBlockCollideEvent extends MCVehicleCollideEvent { - public MCBlock getBlock(); + + MCBlock getBlock(); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCVehicleCollideEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCVehicleCollideEvent.java index 3574f3d82a..dba0819d0e 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCVehicleCollideEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCVehicleCollideEvent.java @@ -3,5 +3,6 @@ import com.laytonsmith.abstraction.enums.MCCollisionType; public interface MCVehicleCollideEvent extends MCVehicleEvent { - public MCCollisionType getCollisionType(); + + MCCollisionType getCollisionType(); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCVehicleDestroyEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCVehicleDestroyEvent.java new file mode 100644 index 0000000000..69c066cf0c --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCVehicleDestroyEvent.java @@ -0,0 +1,8 @@ +package com.laytonsmith.abstraction.events; + +import com.laytonsmith.abstraction.MCEntity; + +public interface MCVehicleDestroyEvent extends MCVehicleEvent { + + MCEntity getAttacker(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCVehicleEnitityCollideEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCVehicleEnitityCollideEvent.java deleted file mode 100644 index 451c56335e..0000000000 --- a/src/main/java/com/laytonsmith/abstraction/events/MCVehicleEnitityCollideEvent.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.laytonsmith.abstraction.events; - -import com.laytonsmith.abstraction.MCEntity; - -public interface MCVehicleEnitityCollideEvent extends MCVehicleCollideEvent { - public MCEntity getEntity(); - public boolean isCollisionCancelled(); - public boolean isPickupCancelled(); - public void setCollisionCancelled(boolean cancel); - public void setPickupCancelled(boolean cancel); -} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCVehicleEnterExitEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCVehicleEnterExitEvent.java index a24d4c7e04..975eceb81d 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCVehicleEnterExitEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCVehicleEnterExitEvent.java @@ -3,5 +3,6 @@ import com.laytonsmith.abstraction.MCEntity; public interface MCVehicleEnterExitEvent extends MCVehicleEvent { - public MCEntity getEntity(); + + MCEntity getEntity(); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCVehicleEntityCollideEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCVehicleEntityCollideEvent.java new file mode 100644 index 0000000000..801a78af53 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCVehicleEntityCollideEvent.java @@ -0,0 +1,16 @@ +package com.laytonsmith.abstraction.events; + +import com.laytonsmith.abstraction.MCEntity; + +public interface MCVehicleEntityCollideEvent extends MCVehicleCollideEvent { + + MCEntity getEntity(); + + boolean isCollisionCancelled(); + + boolean isPickupCancelled(); + + void setCollisionCancelled(boolean cancel); + + void setPickupCancelled(boolean cancel); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCVehicleEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCVehicleEvent.java index 2477c457d5..48b7fc7eff 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCVehicleEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCVehicleEvent.java @@ -1,8 +1,9 @@ package com.laytonsmith.abstraction.events; -import com.laytonsmith.abstraction.MCVehicle; +import com.laytonsmith.abstraction.MCEntity; import com.laytonsmith.core.events.BindableEvent; public interface MCVehicleEvent extends BindableEvent { - public MCVehicle getVehicle(); + + MCEntity getVehicle(); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCVehicleMoveEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCVehicleMoveEvent.java index 42e73b33c2..a9ba9c8bbb 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCVehicleMoveEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCVehicleMoveEvent.java @@ -2,17 +2,15 @@ import com.laytonsmith.abstraction.MCLocation; -/** - * - * @author MariuszT - */ public interface MCVehicleMoveEvent extends MCVehicleEvent { - public MCLocation getFrom(); + int getThreshold(); - public MCLocation getTo(); + MCLocation getFrom(); - public void setCancelled(boolean state); + MCLocation getTo(); - public boolean isCancelled(); + void setCancelled(boolean state); + + boolean isCancelled(); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCWeatherChangeEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCWeatherChangeEvent.java new file mode 100644 index 0000000000..a1c0162ed8 --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCWeatherChangeEvent.java @@ -0,0 +1,6 @@ +package com.laytonsmith.abstraction.events; + +public interface MCWeatherChangeEvent extends MCWeatherEvent { + + boolean toWeatherState(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCWeatherEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCWeatherEvent.java new file mode 100644 index 0000000000..ab9eb82bca --- /dev/null +++ b/src/main/java/com/laytonsmith/abstraction/events/MCWeatherEvent.java @@ -0,0 +1,9 @@ +package com.laytonsmith.abstraction.events; + +import com.laytonsmith.abstraction.MCWorld; +import com.laytonsmith.core.events.BindableEvent; + +public interface MCWeatherEvent extends BindableEvent { + + MCWorld getWorld(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCWorldChangedEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCWorldChangedEvent.java index f47b558e56..6275345157 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCWorldChangedEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCWorldChangedEvent.java @@ -1,15 +1,10 @@ package com.laytonsmith.abstraction.events; -import com.laytonsmith.abstraction.MCPlayer; import com.laytonsmith.abstraction.MCWorld; -import com.laytonsmith.core.events.BindableEvent; - -/** - * - * - */ -public interface MCWorldChangedEvent extends BindableEvent { - public MCPlayer getPlayer(); - public MCWorld getFrom(); - public MCWorld getTo(); + +public interface MCWorldChangedEvent extends MCPlayerEvent { + + MCWorld getFrom(); + + MCWorld getTo(); } diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCWorldEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCWorldEvent.java index e229772bd0..3439c01dad 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCWorldEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCWorldEvent.java @@ -1,13 +1,9 @@ -package com.laytonsmith.abstraction.events; - -import com.laytonsmith.abstraction.MCWorld; -import com.laytonsmith.core.events.BindableEvent; - -/** - * - * @author KingFisher - */ -public interface MCWorldEvent extends BindableEvent { - - public MCWorld getWorld(); -} \ No newline at end of file +package com.laytonsmith.abstraction.events; + +import com.laytonsmith.abstraction.MCWorld; +import com.laytonsmith.core.events.BindableEvent; + +public interface MCWorldEvent extends BindableEvent { + + MCWorld getWorld(); +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCWorldLoadEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCWorldLoadEvent.java index 090b1ebc07..cb343da8ce 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCWorldLoadEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCWorldLoadEvent.java @@ -1,8 +1,4 @@ -package com.laytonsmith.abstraction.events; - -/** - * - * @author KingFisher - */ -public interface MCWorldLoadEvent extends MCWorldEvent { -} \ No newline at end of file +package com.laytonsmith.abstraction.events; + +public interface MCWorldLoadEvent extends MCWorldEvent { +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCWorldSaveEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCWorldSaveEvent.java index ca7ec61ea4..991f1b2c3d 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCWorldSaveEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCWorldSaveEvent.java @@ -1,8 +1,4 @@ -package com.laytonsmith.abstraction.events; - -/** - * - * @author KingFisher - */ -public interface MCWorldSaveEvent extends MCWorldEvent { -} \ No newline at end of file +package com.laytonsmith.abstraction.events; + +public interface MCWorldSaveEvent extends MCWorldEvent { +} diff --git a/src/main/java/com/laytonsmith/abstraction/events/MCWorldUnloadEvent.java b/src/main/java/com/laytonsmith/abstraction/events/MCWorldUnloadEvent.java index 976815ac6a..dcbcb6b9b4 100644 --- a/src/main/java/com/laytonsmith/abstraction/events/MCWorldUnloadEvent.java +++ b/src/main/java/com/laytonsmith/abstraction/events/MCWorldUnloadEvent.java @@ -1,8 +1,4 @@ -package com.laytonsmith.abstraction.events; - -/** - * - * @author KingFisher - */ -public interface MCWorldUnloadEvent extends MCWorldEvent { -} \ No newline at end of file +package com.laytonsmith.abstraction.events; + +public interface MCWorldUnloadEvent extends MCWorldEvent { +} diff --git a/src/main/java/com/laytonsmith/abstraction/pluginmessages/MCMessenger.java b/src/main/java/com/laytonsmith/abstraction/pluginmessages/MCMessenger.java index f9d0e00020..b1350651c4 100644 --- a/src/main/java/com/laytonsmith/abstraction/pluginmessages/MCMessenger.java +++ b/src/main/java/com/laytonsmith/abstraction/pluginmessages/MCMessenger.java @@ -2,18 +2,15 @@ import java.util.Set; -/** - * - * @author Jason Unger - */ public interface MCMessenger { - public MCPluginMessageListenerRegistration registerIncomingPluginChannel(String channel); - public boolean isIncomingChannelRegistered(String channel); + MCPluginMessageListenerRegistration registerIncomingPluginChannel(String channel); - public void unregisterIncomingPluginChannel(String channel); + boolean isIncomingChannelRegistered(String channel); - public Set getIncomingChannels(); - - public void closeAllChannels(); + void unregisterIncomingPluginChannel(String channel); + + Set getIncomingChannels(); + + void closeAllChannels(); } diff --git a/src/main/java/com/laytonsmith/abstraction/pluginmessages/MCPluginMessageListenerRegistration.java b/src/main/java/com/laytonsmith/abstraction/pluginmessages/MCPluginMessageListenerRegistration.java index dd424b9ed9..4e408a8b91 100644 --- a/src/main/java/com/laytonsmith/abstraction/pluginmessages/MCPluginMessageListenerRegistration.java +++ b/src/main/java/com/laytonsmith/abstraction/pluginmessages/MCPluginMessageListenerRegistration.java @@ -1,9 +1,4 @@ package com.laytonsmith.abstraction.pluginmessages; -/** - * - * @author Jason Unger - */ public interface MCPluginMessageListenerRegistration { - } diff --git a/src/main/java/com/laytonsmith/annotations/DocumentLink.java b/src/main/java/com/laytonsmith/annotations/DocumentLink.java new file mode 100644 index 0000000000..68ac06843d --- /dev/null +++ b/src/main/java/com/laytonsmith/annotations/DocumentLink.java @@ -0,0 +1,28 @@ +package com.laytonsmith.annotations; + +import com.laytonsmith.core.functions.AbstractFunction; +import com.laytonsmith.core.functions.DocumentLinkProvider; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A function tagged with DocumentLink has a parameter that, if a constant string, links to a file that could + * or must be in the local file system. This is used by the language server to display a link to the file. + * + * This annotation can only be used if the function extends {@link AbstractFunction}, implements + * {@link DocumentLinkProvider}, and the given parameter locations are always the same. If the location of the file + * parameters can change based on the type of arguments, this cannot be used, and instead the method in + * DocumentLinkProvider must be overwritten. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface DocumentLink { + /** + * The location(s) of parameters which are links to local files. Zero indexed. If the argument index is not present, + * it is not an error, but no child will be returned. + * @return + */ + int[] value(); +} diff --git a/src/main/java/com/laytonsmith/annotations/shutdown.java b/src/main/java/com/laytonsmith/annotations/EventIdentifier.java similarity index 62% rename from src/main/java/com/laytonsmith/annotations/shutdown.java rename to src/main/java/com/laytonsmith/annotations/EventIdentifier.java index 20e4758604..1288131f3e 100644 --- a/src/main/java/com/laytonsmith/annotations/shutdown.java +++ b/src/main/java/com/laytonsmith/annotations/EventIdentifier.java @@ -1,17 +1,20 @@ package com.laytonsmith.annotations; +import com.laytonsmith.core.events.Driver; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * Methods with this annotation will be run on shutdown (or shutdown before a - * restart) + * Created by jb_aero on 3/22/2015. */ -@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) -@Deprecated // Use LifeCycle classes instead. -public @interface shutdown { - +@Retention(RetentionPolicy.RUNTIME) +public @interface EventIdentifier { + + String className(); + + Driver event(); } diff --git a/src/main/java/com/laytonsmith/annotations/ExposedElement.java b/src/main/java/com/laytonsmith/annotations/ExposedElement.java new file mode 100644 index 0000000000..c93c8c07db --- /dev/null +++ b/src/main/java/com/laytonsmith/annotations/ExposedElement.java @@ -0,0 +1,29 @@ +package com.laytonsmith.annotations; + +import com.laytonsmith.core.environments.Environment; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation should be tagged onto methods and fields that should be exposed to user code through native classes. + * In order for these + * to succeed, the containing class should ultimately extend Mixed, if a field, it must be a type that extends Mixed, + * if a method, the return type must extend Mixed, and all the parameters must extend mixed. + * The exception to the Mixed rule is that various types have special handling, and + * the system will transparently convert between java types and MethodScript types for you. Even still, you may mix uses + * of Mixed values and these types. + *

+ * Meanwhile, in the native methodscript core library, this must be a defined class. The class itself must be marked + * as native, and so must the method or field. The field may not be assigned a value in the declaration, and the method + * may not have a body. It is highly recommended that the + * + * The first two parameters in a method must be {@link Environment} and {@link Target}, but the rest of the parameters + * should correspond to the parameters defined in the MethodScript method. + */ +@Target({ElementType.METHOD, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExposedElement { + +} diff --git a/src/main/java/com/laytonsmith/annotations/MDynamicEnum.java b/src/main/java/com/laytonsmith/annotations/MDynamicEnum.java new file mode 100644 index 0000000000..9bec00d323 --- /dev/null +++ b/src/main/java/com/laytonsmith/annotations/MDynamicEnum.java @@ -0,0 +1,24 @@ +package com.laytonsmith.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Tagged onto "enum" classes to allow the reflection mechanism to list out all the values in the enum. It is important + * to note that the underlying real enum should not be tagged with the {@link MEnum} annotation. The type in + * MethodScript will be inherited from this annotation, and the values that are listed in the "values" method in this + * class will be used. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface MDynamicEnum { + + /** + * The name of the enum, in MethodScript. + * + * @return + */ + String value(); +} diff --git a/src/main/java/com/laytonsmith/annotations/MEnum.java b/src/main/java/com/laytonsmith/annotations/MEnum.java index c6e318f8e6..cb2479bf58 100644 --- a/src/main/java/com/laytonsmith/annotations/MEnum.java +++ b/src/main/java/com/laytonsmith/annotations/MEnum.java @@ -1,4 +1,3 @@ - package com.laytonsmith.annotations; import java.lang.annotation.ElementType; @@ -7,15 +6,17 @@ import java.lang.annotation.Target; /** - * Tagged onto enum classes to allow the reflection mechanism to list out all the values - * in the enum. This is semi-temporary. + * Tagged onto enum classes to allow the reflection mechanism to list out all the values in the enum. This is + * semi-temporary. */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface MEnum { + /** * The name of the enum, in MethodScript. - * @return + * + * @return */ String value(); } diff --git a/src/main/java/com/laytonsmith/annotations/MustUseOverride.java b/src/main/java/com/laytonsmith/annotations/MustUseOverride.java index a0165aab79..9bff805019 100644 --- a/src/main/java/com/laytonsmith/annotations/MustUseOverride.java +++ b/src/main/java/com/laytonsmith/annotations/MustUseOverride.java @@ -1,4 +1,3 @@ - package com.laytonsmith.annotations; import java.lang.annotation.ElementType; @@ -7,13 +6,9 @@ import java.lang.annotation.Target; /** - * An interface can be tagged with this to indicate - * that it requires implementing classes to tag - * the implemented methods with @Override. This is - * useful for if an interface has methods that are - * subject to removal, and leaving the methods in the - * subclasses would prove problematic, or would make - * them unused, and therefore a code smell. + * An interface can be tagged with this to indicate that it requires implementing classes to tag the implemented methods + * with @Override. This is useful for if an interface has methods that are subject to removal, and leaving the methods + * in the subclasses would prove problematic, or would make them unused, and therefore a code smell. */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.CLASS) diff --git a/src/main/java/com/laytonsmith/annotations/NonInheritImplements.java b/src/main/java/com/laytonsmith/annotations/NonInheritImplements.java new file mode 100644 index 0000000000..33bf6083dd --- /dev/null +++ b/src/main/java/com/laytonsmith/annotations/NonInheritImplements.java @@ -0,0 +1,90 @@ +package com.laytonsmith.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +/** + * A class that must implement the methods of the given interface, but whose subclasses are not necessarily required to + * implement them. The value must be an interface, or a compile error will be given. Note that classes that are tagged + * with this are not directly castable to the specified interface, but will have all the methods required of them at + * compile time. Further, it is also allowed for a subclass to be tagged with this, yet a superclass (which may or may + * not be tagged with it) to actually provide the implementation. It is also an error to tag a class which is already an + * instanceof this interface. + * + * A good use case for this mechanism is the case of serialization. Which it is useful to provide a default + * serialization method, not all subclasses may want to be serializable themselves. However, if the superclass defines + * itself as serializable, all subclasses would then be required to implement these methods, even if they do not + * want to be serializable. + * + * For convenience, this interface also provides a Cast method and an Instanceof method, which replace the Java cast + * mechanism and instanceof mechanism to be aware of the fact that the cast appears to not work. + * @author cailin + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface NonInheritImplements { + + /** + * The interface that this class implements + * @return + */ + Class value(); + + /** + * If the interface has generic types, these can be specified here, in order. + * @return + */ + Class[] parameterTypes() default {}; + + /** + * Provides helper methods for operations on a {@link NonInheritImplements} ecosystem. + */ + public static class Helper { + + // No constructing allowed! + private Helper() {} + + /** + * Casts a given object to the given type, assuming it actually is truly castable to the specified type. (Either + * because it implements the interface, or NonInheritImplements it.) Proxies are used as necessary to actually + * provide a first class interface. + * @param The return interface type + * @param castType The return interface type + * @param o The object to cast + * @return The original object, cast to the appropriate type + * @throws If the underlying object does not implement the correct type + */ + public static T Cast(Class castType, Object o) throws ClassCastException { + NonInheritImplements nii = o.getClass().getAnnotation(NonInheritImplements.class); + if(castType.isAssignableFrom(o.getClass())) { + return castType.cast(o); + } else if(nii != null) { + Class iface = nii.value(); + @SuppressWarnings("unchecked") + T ifaceProxy = (T) Proxy.newProxyInstance(o.getClass().getClassLoader(), new Class[]{iface}, (Object proxy, Method method, Object[] args) -> { + return o.getClass().getMethod(method.getName(), method.getParameterTypes()).invoke(o, args); + }); + return ifaceProxy; + } else { + throw new ClassCastException("The object cannot be cast to " + castType.getName()); + } + } + + /** + * Works similar to the java {@code o instanceof value} mechanism, but is aware of the NonInheritImplements + * mechanism. It also checks to see if the object is castable using the normal cast mechanism too, but in + * general should not replace the instanceof keyword for normal use. + * @param o + * @param value + * @return + */ + public static boolean Instanceof(Object o, Class value) { + return value.isAssignableFrom(o.getClass()) + || o.getClass().getAnnotation(NonInheritImplements.class) != null; + } + } +} diff --git a/src/main/java/com/laytonsmith/annotations/OperatorPreferred.java b/src/main/java/com/laytonsmith/annotations/OperatorPreferred.java new file mode 100644 index 0000000000..f8f9f81a32 --- /dev/null +++ b/src/main/java/com/laytonsmith/annotations/OperatorPreferred.java @@ -0,0 +1,20 @@ +package com.laytonsmith.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Functions that have operator or keyword support should be marked with the annotation, and in strict mode, if the + * function is used directly, it becomes a compiler warning (or error in UltraStrict mode). + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface OperatorPreferred { + /** + * The value is the operator that should be used in place of the function, which is used in the error message. + * @return + */ + String value(); +} diff --git a/src/main/java/com/laytonsmith/annotations/abstraction.java b/src/main/java/com/laytonsmith/annotations/abstraction.java index 4194329e9a..1ab053d644 100644 --- a/src/main/java/com/laytonsmith/annotations/abstraction.java +++ b/src/main/java/com/laytonsmith/annotations/abstraction.java @@ -1,5 +1,3 @@ - - package com.laytonsmith.annotations; import com.laytonsmith.abstraction.Implementation; @@ -9,13 +7,14 @@ import java.lang.annotation.Target; /** - * This tag is to denote that a class is a implementation of a specific abstraction interface. - * This isn't always needed however, only when a reverse lookup may need to be done should - * this be needed. - * + * This tag is to denote that a class is a implementation of a specific abstraction interface. This isn't always needed + * however, only when a reverse lookup may need to be done should this be needed. + * */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) +@SuppressWarnings("checkstyle:typename") // Fixing this violation might break dependents. public @interface abstraction { - Implementation.Type type(); + + Implementation.Type type(); } diff --git a/src/main/java/com/laytonsmith/annotations/abstractionenum.java b/src/main/java/com/laytonsmith/annotations/abstractionenum.java index 7ac9c9b1f0..c4371c682c 100644 --- a/src/main/java/com/laytonsmith/annotations/abstractionenum.java +++ b/src/main/java/com/laytonsmith/annotations/abstractionenum.java @@ -1,4 +1,3 @@ - package com.laytonsmith.annotations; import com.laytonsmith.abstraction.Implementation; @@ -8,32 +7,35 @@ import java.lang.annotation.Target; /** - * An abstraction enum marks an enum upon which there exists an abstraction connection. - * At startup, a check is done to see if there are more (or less) enums in the runtime's real layer - * vs the number that is defined for it. If so, a warning is issued, which should assist - * in detecting a missing implementation mapping. - * + * An abstraction enum marks an enum upon which there exists an abstraction connection. At startup, a check is done to + * see if there are more (or less) enums in the runtime's real layer vs the number that is defined for it. If so, a + * warning is issued, which should assist in detecting a missing implementation mapping. + * */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) +@SuppressWarnings("checkstyle:typename") // Fixing this violation might break dependents. public @interface abstractionenum { + /** * Returns the Implementation type this is valid for - * @return + * + * @return */ public Implementation.Type implementation(); /** - * The class of the abstraction layer enum with which this convertor maps between. - * For instance, MCAction.class - * @return + * The class of the abstraction layer enum with which this convertor maps between. For instance, MCAction.class + * + * @return */ public Class forAbstractEnum(); - + /** - * The class of the implementation layer enum with which this convertor maps between. - * For instance, Action.class (in bukkit) - * @return + * The class of the implementation layer enum with which this convertor maps between. For instance, Action.class (in + * bukkit) + * + * @return */ public Class forConcreteEnum(); } diff --git a/src/main/java/com/laytonsmith/annotations/api.java b/src/main/java/com/laytonsmith/annotations/api.java index 52ff1ba532..0bb5efd1bc 100644 --- a/src/main/java/com/laytonsmith/annotations/api.java +++ b/src/main/java/com/laytonsmith/annotations/api.java @@ -1,10 +1,6 @@ - - package com.laytonsmith.annotations; -import com.laytonsmith.core.PlatformResolver; import com.laytonsmith.core.environments.Environment; -import com.laytonsmith.core.functions.bash.BashPlatformResolver; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -15,94 +11,100 @@ /** * Marks a function as an API function, which includes it in the list of functions. - * + * */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) +@SuppressWarnings("checkstyle:typename") // Fixing this violation might break dependents. public @interface api { - - public enum Platforms{ - INTERPRETER_JAVA(null, "Java Interpreter"), - COMPILER_BASH(new BashPlatformResolver(), "Bash Compiler"); - private PlatformResolver resolver; - private String platformName; - /** - * Returns the platform specific resolver, which is able to override base functionality, - * which will be adjusted as needed. If the resolver is null, one does not exist, implying - * that the default is fine. - * @return - */ - public PlatformResolver getResolver(){ - return this.resolver; - } - public String platformName(){ - return this.platformName; - } - private Platforms(PlatformResolver resolver, String platformName){ - this.resolver = resolver; - this.platformName = platformName; - } - } - + + public enum Platforms { + INTERPRETER_JAVA("Java Interpreter"), + COMPILER_BASH("Bash Compiler"), + COMPILER_LLVM("LLVM Compiler"); + private final String platformName; + + public String platformName() { + return this.platformName; + } + + private Platforms(String platformName) { + this.platformName = platformName; + } + } + /** - * Returns the platform this is implemented for. The default is {@see api.Platforms#INTERPRETER_JAVA}. - * @return + * Returns the platform this is implemented for. The default is {@link api.Platforms#INTERPRETER_JAVA}. + * + * @see api.Platforms#INTERPRETER_JAVA + * @return */ - Platforms [] platform() default {api.Platforms.INTERPRETER_JAVA}; + Platforms[] platform() default {api.Platforms.INTERPRETER_JAVA}; + /** - * Returns the environments this api element uses. The default is an empty array, but note - * that GlobalEnv.class is implied for all elements, and it is not required to add that to this - * list. - * @return + * Returns the environments this api element relies on. The default is an empty array, + * but note that GlobalEnv.class is + * implied for all elements, and it is not required to add that to this list. This list + * is what determines if a compile error should be displayed or not, if this function is + * used in an unsupported environment. There is no other functionality implied by this, + * so it is not going to cause an error if missing, but it will cause errors that could + * have been caught at compile time, to be runtime errors instead. Therefore, it is + * still important to get this correct. + * + * @return */ - Class [] environments() default {}; + Class[] environments() default {}; + /** - * If this api element is enabled. The default is {@code true}, but you can temporarily - * disable an element by setting this to false. - * @return + * If this api element is enabled. The default is {@code true}, but you can temporarily disable an element by + * setting this to false. + * + * @return */ boolean enabled() default true; - - /** - * This is a list of valid classes that are valid to be tagged with this annotation. - */ - public static enum ValidClasses{ - FUNCTION(com.laytonsmith.core.functions.FunctionBase.class), - EVENT(com.laytonsmith.core.events.Event.class); - private static List classes = null; - Class classType; - private ValidClasses(Class c){ - classType = c; - } - - /** - * Returns a copy of the list of valid classes that may be tagged with - * the api annotation. - * @return - */ - public static List Classes(){ - if(classes == null){ - Class[] cc = new Class[ValidClasses.values().length]; - for(int i = 0; i < ValidClasses.values().length; i++){ - cc[i] = ValidClasses.values()[i].classType; - } - classes = Arrays.asList(cc); - } - return new ArrayList(classes); - } - - /** - * Returns true if the specified class extends a valid class. - * @param c - * @return - */ - public static boolean IsValid(Class c){ - for(Class cc : Classes()){ - if(cc.isAssignableFrom(c)){ - return true; - } - } - return false; - } - } + + /** + * This is a list of valid classes that are valid to be tagged with this annotation. + */ + public static enum ValidClasses { + FUNCTION(com.laytonsmith.core.functions.FunctionBase.class), + EVENT(com.laytonsmith.core.events.Event.class); + private static List classes = null; + Class classType; + + private ValidClasses(Class c) { + classType = c; + } + + /** + * Returns a copy of the list of valid classes that may be tagged with the api annotation. + * + * @return + */ + public static List Classes() { + if(classes == null) { + Class[] cc = new Class[ValidClasses.values().length]; + for(int i = 0; i < ValidClasses.values().length; i++) { + cc[i] = ValidClasses.values()[i].classType; + } + classes = Arrays.asList(cc); + } + return new ArrayList(classes); + } + + /** + * Returns true if the specified class extends a valid class. + * + * @param c + * @return + */ + public static boolean IsValid(Class c) { + for(Class cc : Classes()) { + if(cc.isAssignableFrom(c)) { + return true; + } + } + return false; + } + } } diff --git a/src/main/java/com/laytonsmith/annotations/breakable.java b/src/main/java/com/laytonsmith/annotations/breakable.java index 4b7d60fccb..5411a630e4 100644 --- a/src/main/java/com/laytonsmith/annotations/breakable.java +++ b/src/main/java/com/laytonsmith/annotations/breakable.java @@ -1,4 +1,3 @@ - package com.laytonsmith.annotations; import java.lang.annotation.ElementType; @@ -7,13 +6,13 @@ import java.lang.annotation.Target; /** - * A function that is breakable can be "broken" from using the break() function. - * Most functions are ambivalent to breaks travelling up them, but some specifically - * support them. This information is used by the compiler to detect if break is - * being used correctly in the user code. + * A function that is breakable can be "broken" from using the break() function. Most functions are ambivalent to breaks + * travelling up them, but some specifically support them. This information is used by the compiler to detect if break + * is being used correctly in the user code. */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) +@SuppressWarnings("checkstyle:typename") // Fixing this violation might break dependents. public @interface breakable { } diff --git a/src/main/java/com/laytonsmith/annotations/convert.java b/src/main/java/com/laytonsmith/annotations/convert.java index 151902cffc..8d0ba57810 100644 --- a/src/main/java/com/laytonsmith/annotations/convert.java +++ b/src/main/java/com/laytonsmith/annotations/convert.java @@ -1,5 +1,3 @@ - - package com.laytonsmith.annotations; import com.laytonsmith.abstraction.Implementation; @@ -9,13 +7,14 @@ import java.lang.annotation.Target; /** - * This annotation denotes that the marked class implements the noted - * server type's Convertor. Only one class should exist for each type, - * otherwise the behavior is undefined, but an error will be thrown. - * + * This annotation denotes that the marked class implements the noted server type's Convertor. Only one class should + * exist for each type, otherwise the behavior is undefined, but an error will be thrown. + * */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) +@SuppressWarnings("checkstyle:typename") // Fixing this violation might break dependents. public @interface convert { - Implementation.Type type(); + + Implementation.Type type(); } diff --git a/src/main/java/com/laytonsmith/annotations/core.java b/src/main/java/com/laytonsmith/annotations/core.java index 40889f8a37..460ffbfd36 100644 --- a/src/main/java/com/laytonsmith/annotations/core.java +++ b/src/main/java/com/laytonsmith/annotations/core.java @@ -1,4 +1,3 @@ - package com.laytonsmith.annotations; import java.lang.annotation.ElementType; @@ -7,19 +6,18 @@ import java.lang.annotation.Target; /** - * This is annotated on classes of functions, or individual functions to indicate that they - * are part of the core API. This annotation has both a practical meaning, and - * a legal meaning. Functions tagged with this annotation are unable to be overridden - * by external code, both for consistency reasons and security reasons. Additionally, - * any implementation of MethodScript must correctly implement all of the functions - * labelled as core in order to be considered a valid implementation. - * - * This also has a legal purpose. All functions tagged as part of the core fall under the - * special contribution license, and any modifications by third parties must be - * released of all copyrights. + * This is annotated on classes of functions, or individual functions to indicate that they are part of the core API. + * This annotation has both a practical meaning, and a legal meaning. Functions tagged with this annotation are unable + * to be overridden by external code, both for consistency reasons and security reasons. Additionally, any + * implementation of MethodScript must correctly implement all of the functions labelled as core in order to be + * considered a valid implementation. + * + * This also has a legal purpose. All functions tagged as part of the core fall under the special contribution license, + * and any modifications by third parties must be released of all copyrights. */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) +@SuppressWarnings("checkstyle:typename") // Fixing this violation might break dependents. public @interface core { } diff --git a/src/main/java/com/laytonsmith/annotations/datasource.java b/src/main/java/com/laytonsmith/annotations/datasource.java index df257e6068..fcec5c790b 100644 --- a/src/main/java/com/laytonsmith/annotations/datasource.java +++ b/src/main/java/com/laytonsmith/annotations/datasource.java @@ -7,14 +7,17 @@ /** * Used to tag data store implementations. - * + * */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) +@SuppressWarnings("checkstyle:typename") // Fixing this violation might break dependents. public @interface datasource { - /** - * Returns the protocol handler associated with this data source. - * @return - */ - String value(); + + /** + * Returns the protocol handler associated with this data source. + * + * @return + */ + String value(); } diff --git a/src/main/java/com/laytonsmith/annotations/event.java b/src/main/java/com/laytonsmith/annotations/event.java deleted file mode 100644 index b20f3619ff..0000000000 --- a/src/main/java/com/laytonsmith/annotations/event.java +++ /dev/null @@ -1,22 +0,0 @@ - -package com.laytonsmith.annotations; - -import com.laytonsmith.core.events.BindableEvent; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * External extension points with an event handler should tag the methods that receive - * an event. The event handlers will receive an abstracted event object. The method - * must take one parameter, an object that extends {@link BindableEvent}. If the event - * handler is an extension point that does not include abstracted objects, the extension - * must make other arrangements to trigger itself. See {@link startup}. - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -@Deprecated // Use @api class MyClass extends AbstractEvent instead! -public @interface event { - -} diff --git a/src/main/java/com/laytonsmith/annotations/hide.java b/src/main/java/com/laytonsmith/annotations/hide.java index d26009605b..9a81eb3b84 100644 --- a/src/main/java/com/laytonsmith/annotations/hide.java +++ b/src/main/java/com/laytonsmith/annotations/hide.java @@ -1,4 +1,3 @@ - package com.laytonsmith.annotations; import java.lang.annotation.ElementType; @@ -7,20 +6,21 @@ import java.lang.annotation.Target; /** - * If present, this annotation indicates that the class, method or field should - * be undocumented. This generally indicates that the item is likely going to be - * subject to incompatible changes or removal, or is otherwise unstable or untested. - * Items generally should not be hidden if they were ever exposed, but instead should - * be deprecated and still publicly exposed for one release cycle or more, then either - * hidden or removed. + * If present, this annotation indicates that the class, method or field should be undocumented. This generally + * indicates that the item is likely going to be subject to incompatible changes or removal, or is otherwise unstable or + * untested. Items generally should not be hidden if they were ever exposed, but instead should be deprecated and still + * publicly exposed for one release cycle or more, then either hidden or removed. */ @Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) +@SuppressWarnings("checkstyle:typename") // Fixing this violation might break dependents. public @interface hide { + /** - * The reason why this element is hidden. This is likely only meant as a type of comment on the element itself, - * but could potentially show up in general documentation in some cases. - * @return + * The reason why this element is hidden. This is likely only meant as a type of comment on the element itself, but + * could potentially show up in general documentation in some cases. + * + * @return */ String value(); } diff --git a/src/main/java/com/laytonsmith/annotations/mobject.java b/src/main/java/com/laytonsmith/annotations/mobject.java index b7fc8a5bd6..0a475ec466 100644 --- a/src/main/java/com/laytonsmith/annotations/mobject.java +++ b/src/main/java/com/laytonsmith/annotations/mobject.java @@ -1,4 +1,3 @@ - package com.laytonsmith.annotations; import java.lang.annotation.ElementType; @@ -7,14 +6,17 @@ import java.lang.annotation.Target; /** - * + * */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) +@SuppressWarnings("checkstyle:typename") // Fixing this violation might break dependents. public @interface mobject { + /** * The name of the object, as usable in source code. - * @return + * + * @return */ String value(); } diff --git a/src/main/java/com/laytonsmith/annotations/noboilerplate.java b/src/main/java/com/laytonsmith/annotations/noboilerplate.java index 545a4eb6b4..4f24a47b26 100644 --- a/src/main/java/com/laytonsmith/annotations/noboilerplate.java +++ b/src/main/java/com/laytonsmith/annotations/noboilerplate.java @@ -1,5 +1,3 @@ - - package com.laytonsmith.annotations; import java.lang.annotation.ElementType; @@ -8,15 +6,15 @@ import java.lang.annotation.Target; /** - * This marks functions that should not be tested by the boilerplate test mechanisms, - * for instance, the ones that running a blind test with could cause system problems, - * or other undesired behavior. This only prevents the function execution, the rest - * of the tests (documentation, etc) will be run, and this will not prevent specific - * tests from being run. - * + * This marks functions that should not be tested by the boilerplate test mechanisms, for instance, the ones that + * running a blind test with could cause system problems, or other undesired behavior. This only prevents the function + * execution, the rest of the tests (documentation, etc) will be run, and this will not prevent specific tests from + * being run. + * */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) +@SuppressWarnings("checkstyle:typename") // Fixing this violation might break dependents. public @interface noboilerplate { - + } diff --git a/src/main/java/com/laytonsmith/annotations/nofield.java b/src/main/java/com/laytonsmith/annotations/nofield.java index f78ffe7074..d0a4b48b93 100644 --- a/src/main/java/com/laytonsmith/annotations/nofield.java +++ b/src/main/java/com/laytonsmith/annotations/nofield.java @@ -7,10 +7,11 @@ /** * Used to mark a public field as a non-object field in the CObject chain. - * + * */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) +@SuppressWarnings("checkstyle:typename") // Fixing this violation might break dependents. public @interface nofield { - + } diff --git a/src/main/java/com/laytonsmith/annotations/nolinking.java b/src/main/java/com/laytonsmith/annotations/nolinking.java index dba60944fb..02a487ff60 100644 --- a/src/main/java/com/laytonsmith/annotations/nolinking.java +++ b/src/main/java/com/laytonsmith/annotations/nolinking.java @@ -1,4 +1,3 @@ - package com.laytonsmith.annotations; import java.lang.annotation.ElementType; @@ -7,12 +6,12 @@ import java.lang.annotation.Target; /** - * This annotation indicates that the compiler should not do linking to the - * arguments inside of this function. It is implied that the function itself - * will do its own linking, perhaps at a later time. + * This annotation indicates that the compiler should not do linking to the arguments inside of this function. It is + * implied that the function itself will do its own linking, perhaps at a later time. */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) +@SuppressWarnings("checkstyle:typename") // Fixing this violation might break dependents. public @interface nolinking { } diff --git a/src/main/java/com/laytonsmith/annotations/noprofile.java b/src/main/java/com/laytonsmith/annotations/noprofile.java index f8567c9ad4..f6e6d5354c 100644 --- a/src/main/java/com/laytonsmith/annotations/noprofile.java +++ b/src/main/java/com/laytonsmith/annotations/noprofile.java @@ -1,4 +1,3 @@ - package com.laytonsmith.annotations; import java.lang.annotation.ElementType; @@ -7,13 +6,13 @@ import java.lang.annotation.Target; /** - * Used to tag functions that should not be profile-able, when the profiler - * is set to granularity 5. Generally, functions should always be profilable, - * but in the event that isn't desirable, the class can be tagged with this + * Used to tag functions that should not be profile-able, when the profiler is set to granularity 5. Generally, + * functions should always be profilable, but in the event that isn't desirable, the class can be tagged with this * annotation. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) +@SuppressWarnings("checkstyle:typename") // Fixing this violation might break dependents. public @interface noprofile { - + } diff --git a/src/main/java/com/laytonsmith/annotations/seealso.java b/src/main/java/com/laytonsmith/annotations/seealso.java index ec4f60ea5e..cfc2e811ca 100644 --- a/src/main/java/com/laytonsmith/annotations/seealso.java +++ b/src/main/java/com/laytonsmith/annotations/seealso.java @@ -1,4 +1,3 @@ - package com.laytonsmith.annotations; import java.lang.annotation.ElementType; @@ -11,11 +10,14 @@ */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) +@SuppressWarnings("checkstyle:typename") // Fixing this violation might break dependents. public @interface seealso { + /** - * A list of classes that should be "seen also". These classes should be tagged with - * @api, and implement Function. - * @return + * A list of classes that should be "seen also". These classes should be tagged with @api, and implement + * Function. + * + * @return */ Class[] value(); } diff --git a/src/main/java/com/laytonsmith/annotations/startup.java b/src/main/java/com/laytonsmith/annotations/startup.java deleted file mode 100644 index 1a0d80a9b0..0000000000 --- a/src/main/java/com/laytonsmith/annotations/startup.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.laytonsmith.annotations; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Methods with this annotation are triggered at startup. Note that "startup" - * is relative. This may or may not be a restart or an initial start. See {@link shutdown}. - * The method annotated with this annotation should take 0 parameters, and return void. - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -@Deprecated // Use LifeCycle classes instead. -public @interface startup { - -} diff --git a/src/main/java/com/laytonsmith/annotations/taskhandler.java b/src/main/java/com/laytonsmith/annotations/taskhandler.java index 3b7db5dfc6..fcc60bb74e 100644 --- a/src/main/java/com/laytonsmith/annotations/taskhandler.java +++ b/src/main/java/com/laytonsmith/annotations/taskhandler.java @@ -1,23 +1,22 @@ package com.laytonsmith.annotations; -import com.laytonsmith.core.taskmanager.TaskType; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * This annotation should be tagged onto the various task handlers. - * They should also extend {@link TaskHandler} + * This annotation should be tagged onto the various task handlers. They should also extend {@link TaskHandler} */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) +@SuppressWarnings("checkstyle:typename") // Fixing this violation might break dependents. public @interface taskhandler { /** - * Returns a string list of task property names. The handler - * should also have a list of get* methods, where * represents - * each property name. + * Returns a string list of task property names. The handler should also have a list of get* methods, where * + * represents each property name. + * * @return */ String[] properties(); diff --git a/src/main/java/com/laytonsmith/annotations/typeof.java b/src/main/java/com/laytonsmith/annotations/typeof.java index d303e5269b..4de046072f 100644 --- a/src/main/java/com/laytonsmith/annotations/typeof.java +++ b/src/main/java/com/laytonsmith/annotations/typeof.java @@ -1,4 +1,3 @@ - package com.laytonsmith.annotations; import java.lang.annotation.ElementType; @@ -7,15 +6,17 @@ import java.lang.annotation.Target; /** - * Defines the typeof an object in MethodScript. Defined as an annotation, - * to force overriding and static values. + * Defines the typeof an object in MethodScript. Defined as an annotation, to force overriding and static values. */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) +@SuppressWarnings("checkstyle:typename") // Fixing this violation might break dependents. public @interface typeof { + /** * The name of this object, as defined in MethodScript. - * @return + * + * @return */ String value(); } diff --git a/src/main/java/com/laytonsmith/annotations/unbreakable.java b/src/main/java/com/laytonsmith/annotations/unbreakable.java index b35e64ff62..ff0cdb4745 100644 --- a/src/main/java/com/laytonsmith/annotations/unbreakable.java +++ b/src/main/java/com/laytonsmith/annotations/unbreakable.java @@ -1,4 +1,3 @@ - package com.laytonsmith.annotations; import java.lang.annotation.ElementType; @@ -7,13 +6,13 @@ import java.lang.annotation.Target; /** - * A function that is unbreakable can't be "broken" from using the break() function. - * Most functions are ambivalent to breaks travelling up them, but some specifically - * cause errors. This information is used by the compiler to detect if break is - * being used correctly in the user code. + * A function that is unbreakable can't be "broken" from using the break() function. Most functions are ambivalent to + * breaks travelling up them, but some specifically cause errors. This information is used by the compiler to detect if + * break is being used correctly in the user code. */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) +@SuppressWarnings("checkstyle:typename") // Fixing this violation might break dependents. public @interface unbreakable { } diff --git a/src/main/java/com/laytonsmith/commandhelper/BukkitDirtyRegisteredListener.java b/src/main/java/com/laytonsmith/commandhelper/BukkitDirtyRegisteredListener.java new file mode 100644 index 0000000000..3095eff0e3 --- /dev/null +++ b/src/main/java/com/laytonsmith/commandhelper/BukkitDirtyRegisteredListener.java @@ -0,0 +1,320 @@ +package com.laytonsmith.commandhelper; + +import com.laytonsmith.core.Prefs; +import com.laytonsmith.core.Static; +import com.laytonsmith.core.exceptions.ConfigRuntimeException; +import java.lang.reflect.Field; +import java.util.Comparator; +import java.util.EnumMap; +import java.util.Map; +import java.util.Queue; +import java.util.TreeSet; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.bukkit.event.Event; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.plugin.EventExecutor; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.RegisteredListener; + +/** + * + * + */ +public class BukkitDirtyRegisteredListener extends RegisteredListener { + + private final Listener listener; + private final EventPriority priority; + private final Plugin plugin; + private final EventExecutor executor; + private static final int QUEUE_CAPACITY = 20; + private static Queue cancelledEvents = new LinkedBlockingQueue(QUEUE_CAPACITY); + + public BukkitDirtyRegisteredListener(final Listener pluginListener, final EventExecutor eventExecutor, final EventPriority eventPriority, final Plugin registeredPlugin, + boolean ignoreCancelled) { + super(pluginListener, eventExecutor, eventPriority, registeredPlugin, ignoreCancelled); + listener = pluginListener; + priority = eventPriority; + plugin = registeredPlugin; + executor = eventExecutor; + } + + public static class DirtyEnumMap, V> extends EnumMap { + + public DirtyEnumMap(Class keyType) { + super(keyType); + } + + public DirtyEnumMap(EnumMap m) { + super(m); + } + + public DirtyEnumMap(Map m) { + super(m); + } + + @Override + public V put(K key, V value) { + if(!(value instanceof DirtyTreeSet) && value instanceof TreeSet) { + return super.put(key, (V) DirtyTreeSet.GenerateDirtyTreeSet((TreeSet) value)); + } else { + return super.put(key, value); + } + //return null; + } + } + + public static class DirtyTreeSet extends TreeSet { + + public static DirtyTreeSet GenerateDirtyTreeSet(TreeSet ts) { + DirtyTreeSet dts = new DirtyTreeSet(ts.comparator()); + for(Object o : ts) { + dts.add(o); + } + return dts; + } + + public DirtyTreeSet(Comparator comparator) { + super(comparator); + } + + @Override + public boolean add(Object e) { + if(!(e instanceof BukkitDirtyRegisteredListener) && e instanceof RegisteredListener) { + try { + return super.add(Generate((RegisteredListener) e)); + } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException ex) { + Logger.getLogger(BukkitDirtyRegisteredListener.class.getName()).log(Level.SEVERE, null, ex); + } + } else { + return super.add(e); + } + return false; + } + + } + + public static void Repopulate() throws NoSuchFieldException, ClassCastException, IllegalArgumentException, IllegalAccessException, NoSuchMethodException { + ConfigRuntimeException.DoWarning(null, "Play-dirty mode is currently disabled until further notice. Disable play-dirty in your preferences file" + + " to get rid of this message.", false); + //Go through the list of registered listeners, and inject our + //our own poisoned DirtyRegisteredListeners in instead +// SimplePluginManager pm = (SimplePluginManager) AliasCore.parent.getServer().getPluginManager(); +// Field fListener = SimplePluginManager.class.getDeclaredField("listeners"); +// //set it to public +// fListener.setAccessible(true); +// EnumMap> listeners = +// (EnumMap>) fListener.get(pm); +// +// if(listeners instanceof DirtyEnumMap) { +// return; //We don't need to bother with it, we've already injected our poisoned EnumMap, +// //so further additions will go through that instead. +// } +// +// //Remove final from the listeners, so we can modify it +// Field modifiersField = Field.class.getDeclaredField("modifiers"); +// modifiersField.setAccessible(true); +// modifiersField.setInt(fListener, fListener.getModifiers() & ~Modifier.FINAL); +// +// Map> newListeners = new DirtyEnumMap>(Event.Type.class); +// +// //We need the comparator, so we can create a new listener map +// Field fComparator = SimplePluginManager.class.getDeclaredField("comparer"); +// fComparator.setAccessible(true); +// Comparator comparator = (Comparator) fComparator.get(pm); +// +// //Ok, now we have the listeners, so lets loop through them, and shove them into our own newListener object, so that +// //we can replace the reference later, without modifying the existing variable, because it is currently being walked +// //through elsewhere in the code. +// +// boolean doReplace = false; +// +// Set>> entrySet = listeners.entrySet(); +// Iterator i = entrySet.iterator(); +// while(i.hasNext()) { +// final Map.Entry> mySet = (Map.Entry>) i.next(); +// Iterator k = mySet.getValue().iterator(); +// SortedSet rls = new DirtyTreeSet(comparator); +// newListeners.put(mySet.getKey(), rls); +// while(k.hasNext()) { +// final RegisteredListener rl = (RegisteredListener) k.next(); +// if(!(rl instanceof BukkitDirtyRegisteredListener)) { +// doReplace = true; +// } +// rls.add(BukkitDirtyRegisteredListener.Generate(rl)); +// } +// } +// +// if(doReplace) { +// //Only replace it if we've made changes +// fListener.set(pm, newListeners); +// } + + } + +// public static class MyEntry { +// +// public Type key; +// public DirtyRegisteredListener value; +// } + public static void setCancelled(Event superCancelledEvent) { + if(cancelledEvents.size() >= QUEUE_CAPACITY) { + cancelledEvents.poll(); + } + cancelledEvents.offer(superCancelledEvent); + } + + public static BukkitDirtyRegisteredListener Generate(RegisteredListener real) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException { + if(real instanceof BukkitDirtyRegisteredListener) { + return (BukkitDirtyRegisteredListener) real; + } + Field rListener = real.getClass().getDeclaredField("listener"); + rListener.setAccessible(true); + Listener nListener = (Listener) rListener.get(real); + + Field rPriority = real.getClass().getDeclaredField("priority"); + rPriority.setAccessible(true); + EventPriority nPriority = (EventPriority) rPriority.get(real); + + Field rPlugin = real.getClass().getDeclaredField("plugin"); + rPlugin.setAccessible(true); + Plugin nPlugin = (Plugin) rPlugin.get(real); + + Field rExecutor = real.getClass().getDeclaredField("executor"); + rExecutor.setAccessible(true); + EventExecutor nExecutor = (EventExecutor) rExecutor.get(real); + + Field rIgnoreCancelled = real.getClass().getDeclaredField("ignoreCancelled"); + rIgnoreCancelled.setAccessible(true); + boolean nIgnoreCancelled = rIgnoreCancelled.getBoolean(real); + + return new BukkitDirtyRegisteredListener(nListener, nExecutor, nPriority, nPlugin, nIgnoreCancelled); + } + + /** + * This is the magic method we need to override. When we call the event, if it is "super cancelled", then we don't + * run it. Cancelled events are still run if they aren't "super cancelled", which mirrors existing behavior. + * + * @param event + */ + @Override + public void callEvent(Event event) { +// if(Debug.EVENT_LOGGING && Debug.IsFiltered(plugin)) { +// Debug.DoLog(event.getType(), 1, "Bukkit Event received: " + event.getType().name()); +// } +// //If it isn't super cancelled, call it, even if it is cancelled +// if(!BukkitDirtyRegisteredListener.cancelledEvents.contains(event)) { +// if(Debug.EVENT_LOGGING && Debug.IsFiltered(plugin) +// && Debug.EVENT_LOGGING_FILTER.contains(event.getType())) { +// Debug.DoLog(event.getType(), 3, "\tEvent is not super cancelled, so triggering now"); +// } +// callEvent0(event); +// } else { +// //If it's a cancellable event, and this listener isn't Monitor priority, just return +// if(event instanceof Cancellable && this.priority != EventPriority.MONITOR) { +// if(Debug.EVENT_LOGGING && Debug.IsFiltered(plugin) +// && Debug.EVENT_LOGGING_FILTER.contains(event.getType())) { +// Debug.DoLog(event.getType(), 3, "\tEvent is being ignored, due to play-dirty mode rules"); +// } +// return; +// } else { +// if(Debug.EVENT_LOGGING && Debug.IsFiltered(plugin) +// && Debug.EVENT_LOGGING_FILTER.contains(event.getType())) { +// Debug.DoLog(event.getType(), 3, "\tEvent is super cancelled, but this listener is either monitor priority (Y/N:" +// + (this.priority == EventPriority.MONITOR ? "y" : "n") + " or it it is not cancellable (Y/N:" +// + (event instanceof Cancellable ? "n" : "y")); +// } +// callEvent0(event); +// } +// } + } + + private void callEvent0(Event event) { +// StopWatch stopWatch = null; +// if(Debug.EVENT_LOGGING && Debug.IsFiltered(plugin) +// && Debug.EVENT_LOGGING_FILTER.contains(event.getType())) { +// if(Debug.EVENT_LOGGING_LEVEL >= 1) { +// Debug.DoLog(event.getType(), 1, "\tEvent type: " + event.getType().name()); +// Debug.DoLog(event.getType(), 1, "\tCalled from plugin: " + this.plugin.getClass().getSimpleName()); +// } +// if(Debug.EVENT_LOGGING_LEVEL >= 2) { +// Debug.DoLog(event.getType(), 1, "\tListener Registered: " + this.listener.getClass().getCanonicalName()); +// Debug.DoLog(event.getType(), 2, "\tIs Cancellable? " + (event instanceof Cancellable ? "Y" : "N")); +// if(event instanceof Cancellable) { +// Debug.DoLog(event.getType(), 2, "\t\tIs Cancelled? " + (((Cancellable) event).isCancelled() ? "Y" : "N")); +// } +// } +// if(Debug.EVENT_LOGGING_LEVEL >= 3) { +// Debug.DoLog(event.getType(), 3, "\tEvent class: " + event.getClass().getCanonicalName()); +// } +// if(Debug.EVENT_LOGGING_LEVEL >= 4) { +// //Let's just dump the fields +// StringBuilder b = new StringBuilder("\n\tFields in this event:\n"); +// for(Field f : event.getClass().getSuperclass().getDeclaredFields()) { +// b.append("\t\t").append(f.getType().getSimpleName()).append(" ").append(f.getName()); +// f.setAccessible(true); +// try { +// Object o = f.get(event); +// b.append(" = (actual type: ").append(o.getClass().getSimpleName()).append(") ").append(o.toString()).append("\n"); +// } catch (IllegalArgumentException ex) { +// Logger.getLogger(BukkitDirtyRegisteredListener.class.getName()).log(Level.SEVERE, null, ex); +// } catch (IllegalAccessException ex) { +// Logger.getLogger(BukkitDirtyRegisteredListener.class.getName()).log(Level.SEVERE, null, ex); +// } +// } +// Debug.DoLog(event.getType(), 4, b.toString()); +// } +// if(Debug.EVENT_LOGGING_LEVEL == 5) { +// //dump ALL the things +// StringBuilder b = new StringBuilder("\n\tMethods in this event:\n"); +// for(Method m : event.getClass().getSuperclass().getDeclaredMethods()) { +// b.append("\t\t").append(m.getReturnType().getSimpleName()).append(" ").append(m.getName()).append("(").append(Static.strJoin(m.getParameterTypes(), ", ")).append(");\n"); +// } +// Debug.DoLog(event.getType(), 5, b.toString()); +// } +// } +// if((Debug.EVENT_LOGGING && Debug.IsFiltered(plugin) +// && Debug.EVENT_LOGGING_FILTER.contains(event.getType()) && Debug.EVENT_LOGGING_LEVEL >= 2) || Performance.PERFORMANCE_LOGGING) { +// stopWatch = new StopWatch( +// this.plugin.getClass().getSimpleName() + "."//Plugin name +// + this.listener.getClass().getCanonicalName().replaceAll("\\.", "/") + "." //File event is being called from +// + (event.getType() == Event.Type.CUSTOM_EVENT ? "CUSTOM_EVENT/" + event.getEventName() : event.getType().name()) //Event name +// ); +// } +// try { +// executor.execute(listener, event); +// } catch (EventException e){ +// Logger.getLogger(BukkitDirtyRegisteredListener.class.getName()).log(Level.SEVERE, e.getMessage(), e); +// } +// if(stopWatch != null) { +// stopWatch.stop(); +// if(Debug.EVENT_LOGGING) { +// Debug.DoLog(event.getType(), 2, "\t\t\tEvent completed in " + stopWatch.getElapsedTime() + " milliseconds"); +// } +// if(Performance.PERFORMANCE_LOGGING) { +// Performance.DoLog(stopWatch); +// } +// } +// if(Debug.EVENT_LOGGING && Debug.IsFiltered(plugin)) { +// Debug.DoLog(event.getType(), 1, "--------------------------------------------------------------\n"); +// } + } + + /** + * Sets up CommandHelper to play-dirty, if the user has specified as such + */ + public static void PlayDirty() { + if(Prefs.PlayDirty()) { + try { + //Set up our "proxy" + BukkitDirtyRegisteredListener.Repopulate(); + } catch (NoSuchMethodException ex) { + Logger.getLogger(Static.class.getName()).log(Level.SEVERE, null, ex); + } catch (NoSuchFieldException | ClassCastException | IllegalArgumentException | IllegalAccessException ex) { + Static.getLogger().log(Level.SEVERE, "Uh oh, play dirty mode isn't working.", ex); + } + } //else play nice :( + } +} diff --git a/src/main/java/com/laytonsmith/commandhelper/CommandHelperFileLocations.java b/src/main/java/com/laytonsmith/commandhelper/CommandHelperFileLocations.java index 420b8e86a4..c1fdb789bc 100644 --- a/src/main/java/com/laytonsmith/commandhelper/CommandHelperFileLocations.java +++ b/src/main/java/com/laytonsmith/commandhelper/CommandHelperFileLocations.java @@ -1,4 +1,3 @@ - package com.laytonsmith.commandhelper; import com.laytonsmith.core.MethodScriptFileLocations; @@ -8,26 +7,28 @@ * */ public class CommandHelperFileLocations extends MethodScriptFileLocations { - + private static CommandHelperFileLocations defaultInstance = null; - public static CommandHelperFileLocations getDefault(){ - if(defaultInstance == null){ + + public static CommandHelperFileLocations getDefault() { + if(defaultInstance == null) { setDefault(new CommandHelperFileLocations()); } return defaultInstance; } - - public static void setDefault(CommandHelperFileLocations provider){ + + public static void setDefault(CommandHelperFileLocations provider) { defaultInstance = provider; MethodScriptFileLocations.setDefault(defaultInstance); } /** * Returns the location of the upgrade log file. - * @return + * + * @return */ - public File getUpgradeLogFile(){ + public File getUpgradeLogFile() { return new File(getCacheDirectory(), "upgradeLog.json"); } - + } diff --git a/src/main/java/com/laytonsmith/commandhelper/CommandHelperInterpreterListener.java b/src/main/java/com/laytonsmith/commandhelper/CommandHelperInterpreterListener.java index 31f38a4838..519ebbbe6c 100644 --- a/src/main/java/com/laytonsmith/commandhelper/CommandHelperInterpreterListener.java +++ b/src/main/java/com/laytonsmith/commandhelper/CommandHelperInterpreterListener.java @@ -1,43 +1,41 @@ - - package com.laytonsmith.commandhelper; import com.laytonsmith.abstraction.MCPlayer; import com.laytonsmith.abstraction.StaticLayer; -import com.laytonsmith.abstraction.bukkit.BukkitMCPlayer; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCPlayer; import com.laytonsmith.abstraction.enums.MCChatColor; -import com.laytonsmith.core.CHLog; import com.laytonsmith.core.MethodScriptCompiler; -import com.laytonsmith.core.MethodScriptComplete; -import com.laytonsmith.core.MethodScriptFileLocations; import com.laytonsmith.core.ParseTree; import com.laytonsmith.core.Static; -import com.laytonsmith.core.constructs.Target; -import com.laytonsmith.core.constructs.Token; +import com.laytonsmith.core.compiler.CompilerEnvironment; +import com.laytonsmith.core.compiler.TokenStream; +import com.laytonsmith.core.compiler.analysis.StaticAnalysis; +import com.laytonsmith.core.constructs.IVariable; import com.laytonsmith.core.environments.CommandHelperEnvironment; import com.laytonsmith.core.environments.Environment; import com.laytonsmith.core.environments.GlobalEnv; +import com.laytonsmith.core.environments.RuntimeMode; +import com.laytonsmith.core.environments.StaticRuntimeEnv; import com.laytonsmith.core.exceptions.CancelCommandException; import com.laytonsmith.core.exceptions.ConfigCompileException; +import com.laytonsmith.core.exceptions.ConfigCompileGroupException; import com.laytonsmith.core.exceptions.ConfigRuntimeException; -import com.laytonsmith.core.Profiles; -import com.laytonsmith.core.taskmanager.TaskManager; +import com.laytonsmith.core.natives.interfaces.Mixed; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; +import org.bukkit.event.player.PlayerQuitEvent; + import java.io.File; -import java.io.IOException; import java.util.Collections; +import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; -import java.util.logging.Logger; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.player.AsyncPlayerChatEvent; -import org.bukkit.event.player.PlayerCommandPreprocessEvent; -import org.bukkit.event.player.PlayerQuitEvent; /** * @@ -45,153 +43,189 @@ */ public class CommandHelperInterpreterListener implements Listener { - private Set interpreterMode = Collections.synchronizedSet(new HashSet()); - private CommandHelperPlugin plugin; - Map multilineMode = new HashMap(); - - public boolean isInInterpreterMode(String player){ - return (interpreterMode.contains(player)); - } + private final Set interpreterMode = Collections.synchronizedSet(new HashSet<>()); + private final Map multilineMode = new HashMap<>(); + private final Map interpreterEnvs = new HashMap<>(); + private final Map interpreterSAs = new HashMap<>(); - public CommandHelperInterpreterListener(CommandHelperPlugin plugin){ - this.plugin = plugin; + public boolean isInInterpreterMode(String player) { + return (interpreterMode.contains(player)); } - @EventHandler(priority= EventPriority.LOWEST) - public void onPlayerChat(final AsyncPlayerChatEvent event) { - if (interpreterMode.contains(event.getPlayer().getName())) { - final MCPlayer p = new BukkitMCPlayer(event.getPlayer()); - event.setCancelled(true); - StaticLayer.SetFutureRunnable(null, 0, new Runnable() { + public CommandHelperInterpreterListener() { + } - @Override - public void run() { - textLine(p, event.getMessage()); - } - }); - } + @EventHandler(priority = EventPriority.LOWEST) + public void onPlayerChat(final AsyncPlayerChatEvent event) { + if(interpreterMode.contains(event.getPlayer().getName())) { + final MCPlayer p = new BukkitMCPlayer(event.getPlayer()); + event.setCancelled(true); + StaticLayer.SetFutureRunnable(null, 0, () -> textLine(p, event.getMessage())); + } - } + } - @EventHandler(priority= EventPriority.NORMAL) - public void onPlayerQuit(PlayerQuitEvent event) { - interpreterMode.remove(event.getPlayer().getName()); - multilineMode.remove(event.getPlayer().getName()); - } + @EventHandler(priority = EventPriority.NORMAL) + public void onPlayerQuit(PlayerQuitEvent event) { + interpreterMode.remove(event.getPlayer().getName()); + multilineMode.remove(event.getPlayer().getName()); + } - @EventHandler(priority= EventPriority.LOWEST) - public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) { - if(event.isCancelled()){ + @EventHandler(priority = EventPriority.LOWEST) + public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) { + if(event.isCancelled()) { return; } - if (interpreterMode.contains(event.getPlayer().getName())) { - MCPlayer p = new BukkitMCPlayer(event.getPlayer()); - textLine(p, event.getMessage()); - event.setCancelled(true); - } - } - - public void textLine(MCPlayer p, String line) { - if (line.equals("-")) { - //Exit interpreter mode - interpreterMode.remove(p.getName()); - Static.SendMessage(p, MCChatColor.YELLOW + "Now exiting interpreter mode"); - } else if (line.equals(">>>")) { - //Start multiline mode - if (multilineMode.containsKey(p.getName())) { - Static.SendMessage(p, MCChatColor.RED + "You are already in multiline mode!"); - } else { - multilineMode.put(p.getName(), ""); - Static.SendMessage(p, MCChatColor.YELLOW + "You are now in multiline mode. Type <<< on a line by itself to execute."); - Static.SendMessage(p, ":" + MCChatColor.GRAY + ">>>"); - } - } else if (line.equals("<<<")) { - //Execute multiline - Static.SendMessage(p, ":" + MCChatColor.GRAY + "<<<"); - String script = multilineMode.get(p.getName()); - multilineMode.remove(p.getName()); - try { - execute(script, p); - } catch (ConfigCompileException e) { - Static.SendMessage(p, MCChatColor.RED + e.getMessage() + ":" + e.getLineNum()); - } - } else { - if (multilineMode.containsKey(p.getName())) { - //Queue multiline - multilineMode.put(p.getName(), multilineMode.get(p.getName()) + line + "\n"); - Static.SendMessage(p, ":" + MCChatColor.GRAY + line); - } else { - try { - //Execute single line - execute(line, p); - } catch (ConfigCompileException ex) { - Static.SendMessage(p, MCChatColor.RED + ex.getMessage()); - } - } - } - } + if(interpreterMode.contains(event.getPlayer().getName())) { + MCPlayer p = new BukkitMCPlayer(event.getPlayer()); + textLine(p, event.getMessage()); + event.setCancelled(true); + } + } - public void reload() { - } + public void textLine(MCPlayer p, String line) { + switch(line) { + case "-": + //Exit interpreter mode + interpreterMode.remove(p.getName()); + interpreterEnvs.remove(p.getName()); + interpreterSAs.remove(p.getName()); + Static.SendMessage(p, MCChatColor.YELLOW + "Now exiting interpreter mode"); + break; + case ">>>": + //Start multiline mode + if(multilineMode.containsKey(p.getName())) { + Static.SendMessage(p, MCChatColor.RED + "You are already in multiline mode!"); + } else { + multilineMode.put(p.getName(), ""); + Static.SendMessage(p, MCChatColor.YELLOW + "You are now in multiline mode. Type <<< on a line by itself to execute."); + Static.SendMessage(p, ":" + MCChatColor.GRAY + ">>>"); + } + break; + case "<<<": + //Execute multiline + Static.SendMessage(p, ":" + MCChatColor.GRAY + "<<<"); + String script = multilineMode.get(p.getName()); + multilineMode.remove(p.getName()); + try { + execute(script, p); + } catch (ConfigCompileException e) { + Static.SendMessage(p, MCChatColor.RED + e.getMessage() + ":" + e.getLineNum()); + } catch (ConfigCompileGroupException ex) { + for(ConfigCompileException e : ex.getList()) { + Static.SendMessage(p, MCChatColor.RED + e.getMessage() + ":" + e.getLineNum()); + } + } + break; + case "~": + if(interpreterEnvs.containsKey(p.getName())) { + Environment env = interpreterEnvs.get(p.getName()); + Static.SendMessage(p, MCChatColor.GRAY + "Environment cleared."); + env.getEnv(GlobalEnv.class).GetProcs().clear(); + env.getEnv(GlobalEnv.class).GetVarList().clear(); + for(Thread t : env.getEnv(StaticRuntimeEnv.class).GetDaemonManager().getActiveThreads()) { + t.interrupt(); + } + env.getEnv(StaticRuntimeEnv.class).getExecutionQueue().stopAll(); + env.getEnv(StaticRuntimeEnv.class).getIncludeCache().clear(); + } + if(interpreterSAs.containsKey(p.getName())) { + interpreterSAs.remove(p.getName()); + } + break; + default: + if(multilineMode.containsKey(p.getName())) { + //Queue multiline + multilineMode.put(p.getName(), multilineMode.get(p.getName()) + line + "\n"); + Static.SendMessage(p, ":" + MCChatColor.GRAY + line); + } else { + try { + //Execute single line + execute(line, p); + } catch (ConfigCompileException ex) { + Static.SendMessage(p, MCChatColor.RED + ex.getMessage()); + } catch (ConfigCompileGroupException e) { + for(ConfigCompileException ex : e.getList()) { + Static.SendMessage(p, MCChatColor.RED + ex.getMessage()); + } + } + } + break; + } + } - public void execute(String script, final MCPlayer p) throws ConfigCompileException { - List stream = MethodScriptCompiler.lex(script, new File("Interpreter"), true); - ParseTree tree = MethodScriptCompiler.compile(stream); - interpreterMode.remove(p.getName()); - GlobalEnv gEnv; - try { - gEnv = new GlobalEnv(plugin.executionQueue, plugin.profiler, - plugin.persistenceNetwork, plugin.permissionsResolver, - CommandHelperFileLocations.getDefault().getConfigDirectory(), - new Profiles(MethodScriptFileLocations.getDefault().getSQLProfilesFile()), - new TaskManager()); - } catch (IOException ex) { - CHLog.GetLogger().e(CHLog.Tags.GENERAL, ex.getMessage(), Target.UNKNOWN); + /** + * Executes the given script as the given player in a new environment. Exceptions in script runtime are printed + * to the player. + * If the player is in interpreter mode, then this mode is removed until the script execution terminates. + * @param script - The script to execute. + * @param p - The player to execute the script as. + * @throws ConfigCompileException If compilation fails. + * @throws ConfigCompileGroupException Container for multiple {@link ConfigCompileException}s. + */ + public void execute(String script, final MCPlayer p) throws ConfigCompileException, ConfigCompileGroupException { + TokenStream stream = MethodScriptCompiler.lex(script, null, new File("Interpreter"), true); + Environment env = interpreterEnvs.computeIfAbsent(p.getName(), (player) -> { + StaticRuntimeEnv staticRuntimeEnv = Static.getAliasCore().getStaticRuntimeEnv(); + GlobalEnv gEnv = new GlobalEnv(CommandHelperFileLocations.getDefault().getConfigDirectory(), + EnumSet.of(RuntimeMode.EMBEDDED, RuntimeMode.INTERPRETER)); + gEnv.SetDynamicScriptingMode(true); + CommandHelperEnvironment cEnv = new CommandHelperEnvironment(); + cEnv.SetPlayer(p); + CompilerEnvironment compilerEnv = new CompilerEnvironment(); + compilerEnv.setLogCompilerWarnings(false); + Environment e = Environment.createEnvironment(gEnv, staticRuntimeEnv, cEnv, compilerEnv); + return e; + }); + ParseTree tree = MethodScriptCompiler.compile(stream, env, env.getEnvClasses(), interpreterSAs.computeIfAbsent(p.getName(), (player) -> { + StaticAnalysis sa = new StaticAnalysis(true); + sa.setLocalDisabled(true); + return sa; + })); + if(tree.getChildren().size() == 1 && tree.getChildAt(0).getData() instanceof IVariable ivar) { + Mixed i = env.getEnv(GlobalEnv.class).GetVarList() + .get(ivar.getVariableName(), ivar.getTarget(), env).ival(); + Static.SendMessage(p, ":" + MCChatColor.GREEN + i.val()); return; - } catch (Profiles.InvalidProfileException ex) { - CHLog.GetLogger().e(CHLog.Tags.GENERAL, ex.getMessage(), Target.UNKNOWN); + } + final boolean isInterpeterMode = interpreterMode.remove(p.getName()); + try { + env.getEnv(StaticRuntimeEnv.class).getIncludeCache().executeAutoIncludes(env, null); + MethodScriptCompiler.execute(tree, env, output -> { + output = output.trim(); + if(output.isEmpty()) { + Static.SendMessage(p, ":"); + } else { + if(output.startsWith("/")) { + //Run the command + Static.SendMessage(p, ":" + MCChatColor.YELLOW + output); + p.chat(output); + } else { + //output the results + Static.SendMessage(p, ":" + MCChatColor.GREEN + output); + } + } + if(isInterpeterMode) { + interpreterMode.add(p.getName()); + } else { + Static.SendMessage(p, MCChatColor.YELLOW + "No longer in interpreter mode."); + } + }, null); return; + } catch (CancelCommandException e) { + } catch (ConfigRuntimeException e) { + ConfigRuntimeException.HandleUncaughtException(e, env); + Static.SendMessage(p, MCChatColor.RED + e.toString()); + } catch (Exception e) { + Static.SendMessage(p, MCChatColor.RED + e.toString()); + Static.getLogger().log(Level.SEVERE, null, e); } - gEnv.SetDynamicScriptingMode(true); - CommandHelperEnvironment cEnv = new CommandHelperEnvironment(); - cEnv.SetPlayer(p); - Environment env = Environment.createEnvironment(gEnv, cEnv); - try { - MethodScriptCompiler.registerAutoIncludes(env, null); - MethodScriptCompiler.execute(tree, env, new MethodScriptComplete() { - - @Override - public void done(String output) { - output = output.trim(); - if (output.isEmpty()) { - Static.SendMessage(p, ":"); - } else { - if (output.startsWith("/")) { - //Run the command - Static.SendMessage(p, ":" + MCChatColor.YELLOW + output); - p.chat(output); - } else { - //output the results - Static.SendMessage(p, ":" + MCChatColor.GREEN + output); - } - } - interpreterMode.add(p.getName()); - } - }, null); - } catch (CancelCommandException e) { - interpreterMode.add(p.getName()); - } catch(ConfigRuntimeException e) { - ConfigRuntimeException.HandleUncaughtException(e, env); - Static.SendMessage(p, MCChatColor.RED + e.toString()); - interpreterMode.add(p.getName()); - } catch(Exception e){ - Static.SendMessage(p, MCChatColor.RED + e.toString()); - Logger.getLogger(CommandHelperInterpreterListener.class.getName()).log(Level.SEVERE, null, e); - interpreterMode.add(p.getName()); - } - } + if(isInterpeterMode) { + interpreterMode.add(p.getName()); + } + } - public void startInterpret(String playername) { - interpreterMode.add(playername); - } + public void startInterpret(String playername) { + interpreterMode.add(playername); + } } diff --git a/src/main/java/com/laytonsmith/commandhelper/CommandHelperListener.java b/src/main/java/com/laytonsmith/commandhelper/CommandHelperListener.java index 76aa4ba213..e441212cb5 100644 --- a/src/main/java/com/laytonsmith/commandhelper/CommandHelperListener.java +++ b/src/main/java/com/laytonsmith/commandhelper/CommandHelperListener.java @@ -18,37 +18,26 @@ */ package com.laytonsmith.commandhelper; -import com.laytonsmith.abstraction.Implementation; import com.laytonsmith.abstraction.MCPlayer; -import com.laytonsmith.abstraction.bukkit.BukkitMCPlayer; +import com.laytonsmith.abstraction.bukkit.entities.BukkitMCPlayer; import com.laytonsmith.abstraction.bukkit.events.BukkitPlayerEvents; import com.laytonsmith.abstraction.enums.MCChatColor; import com.laytonsmith.abstraction.events.MCPlayerCommandEvent; -import com.laytonsmith.core.AliasCore; -import com.laytonsmith.core.BukkitDirtyRegisteredListener; import com.laytonsmith.core.InternalException; import com.laytonsmith.core.Prefs; -import com.laytonsmith.core.Script; import com.laytonsmith.core.Static; -import com.laytonsmith.core.UserManager; -import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.events.Driver; import com.laytonsmith.core.events.EventUtils; import com.laytonsmith.core.exceptions.ConfigRuntimeException; import com.laytonsmith.persistence.DataSourceException; -import com.sk89q.worldguard.bukkit.WorldGuardPlayerListener; -import com.sk89q.worldguard.bukkit.WorldGuardPlugin; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; -import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerLoginEvent; -import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.logging.Level; /** * Event listener for Hey0's server mod. @@ -57,122 +46,74 @@ */ public class CommandHelperListener implements Listener { - /** - * Logger. - */ - private static final Logger logger = Logger.getLogger("Minecraft"); - - /** - * List of global aliases. - */ - private AliasCore ac; - private CommandHelperPlugin plugin; - - public CommandHelperListener(CommandHelperPlugin plugin) { - this.plugin = plugin; - } - - /** - * Load global aliases. - */ - public void loadGlobalAliases() { - ac = CommandHelperPlugin.getCore(); - } - - /** - * Find and run aliases for a player for a given command. - * - * @param command - * @return - */ - public boolean runAlias(String command, MCPlayer player) throws DataSourceException { - UserManager um = UserManager.GetUserManager(player.getName()); - List

" + successText + "

"; + os.append("HTTP/1.0 200 OK\r\n"); + os.append("Content-Length: " + content.length() + "\r\n"); + os.append("Connection: close\r\n"); + os.append("Content-Type: text/html\r\n"); + os.append("\r\n"); + os.append(content); + os.flush(); + ss.shutdownOutput(); + } + } + + ret.setObject(query.get("code")); + synchronized(ret) { + ret.notifyAll(); + } + } catch (IOException ex) { + ex.printStackTrace(System.err); + } + } + }, "oauth-callback-" + UUID.randomUUID()).start(); + return ret; + } + + public static String generateRequestURI(String authorizationLocation, String clientId, String scope, + String redirectUrl, Map extraHeaders) throws OAuthSystemException { + OAuthClientRequest req; + OAuthClientRequest.AuthenticationRequestBuilder requestBuilder = OAuthClientRequest + .authorizationLocation(authorizationLocation) + .setClientId(clientId) + .setScope(scope) + .setRedirectURI(redirectUrl); + for(String key : extraHeaders.keySet()) { + requestBuilder.setParameter(key, extraHeaders.get(key)); + } + requestBuilder.setParameter("response_type", "code"); + req = requestBuilder.buildQueryMessage(); + return req.getLocationUri(); + } + + @Override + public String getName() { + return "x_get_oauth_token"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{Integer.MAX_VALUE}; + } + + @Override + public String docs() { + return "void {}"; + } + + @Override + public Version since() { + return MSVersion.V3_3_2; + } + + } + + @api + public static class clear_oauth_tokens extends AbstractFunction { + + /** + * + * @param gEnv + * @param clientId If set, clears just the one client id, if null, clears all tokens. + */ + public static void execute(Environment env, String clientId) { + Mixed[] args = new Mixed[0]; + if(clientId != null) { + args = new Mixed[]{new CString(clientId, Target.UNKNOWN)}; + } + new clear_oauth_tokens().exec(Target.UNKNOWN, env, args); + } + + @Override + public Class[] thrown() { + return new Class[]{}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return null; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + PersistenceNetwork pn = environment.getEnv(StaticRuntimeEnv.class).GetPersistenceNetwork(); + String namespace = "oauth"; + if(args.length >= 1) { + namespace += "." + x_get_oauth_token.getFormattedClientId(args[0].val()); + } + DaemonManager dm = environment.getEnv(StaticRuntimeEnv.class).GetDaemonManager(); + try { + Map list = pn.getNamespace(namespace.split("\\.")); + for(String[] key : list.keySet()) { + pn.clearKey(dm, key); + } + } catch (DataSourceException | IOException ex) { + throw new CREIOException(ex.getMessage(), t, ex); + } catch (IllegalArgumentException ex) { + throw new CREFormatException(ex.getMessage(), t, ex); + } catch (ReadOnlyException ex) { + throw new CREReadOnlyException(ex.getMessage(), t, ex); + } + return CVoid.VOID; + } + + @Override + public String getName() { + return "clear_oauth_tokens"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{0, 1}; + } + + @Override + public String docs() { + return "void {[clientId]} Clears the oauth tokens (refresh token and access token) for the given client ID." + + " If the client ID is not specified, all tokens are deleted. This is useful if various oath tokens" + + " have been revoked, or you would specifically like to prevent caching of those tokens."; + } + + @Override + public Version since() { + return MSVersion.V3_3_2; + } + + } +} diff --git a/src/main/java/com/laytonsmith/core/functions/OS.java b/src/main/java/com/laytonsmith/core/functions/OS.java new file mode 100644 index 0000000000..ebed196878 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/functions/OS.java @@ -0,0 +1,75 @@ +package com.laytonsmith.core.functions; + +import com.laytonsmith.PureUtilities.Common.OSUtils; +import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.annotations.api; +import com.laytonsmith.core.MSVersion; +import com.laytonsmith.core.constructs.CInt; +import com.laytonsmith.core.constructs.Target; +import com.laytonsmith.core.environments.Environment; +import com.laytonsmith.core.exceptions.CRE.CREThrowable; +import com.laytonsmith.core.exceptions.CRE.CREUnsupportedOperationException; +import com.laytonsmith.core.exceptions.ConfigRuntimeException; +import com.laytonsmith.core.natives.interfaces.Mixed; + +/** + * + */ +public class OS { + public static String docs() { + return "Contains various methods for interacting with the Operating System. Some of the functions deal with" + + " OS specific mechanisms."; + } + + + @api + public static class get_pid extends AbstractFunction { + + @Override + public Class[] thrown() { + return new Class[]{CREUnsupportedOperationException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return null; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + try { + return new CInt(OSUtils.GetMyPid(), t); + } catch (UnsupportedOperationException ex) { + throw new CREUnsupportedOperationException(ex, t); + } + } + + @Override + public String getName() { + return "get_pid"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{0}; + } + + @Override + public String docs() { + return "int {} Returns the process id (pid) of the current process. In some implementations of Java, this" + + " cannot be relied on, and in those cases, an UnsupportedOperationException is thrown. In Java 9" + + " and above, this can generally be relied upon to work correctly, however."; + } + + @Override + public Version since() { + return MSVersion.V3_3_4; + } + + } +} diff --git a/src/main/java/com/laytonsmith/core/functions/ObjectManagement.java b/src/main/java/com/laytonsmith/core/functions/ObjectManagement.java new file mode 100644 index 0000000000..2a6a3c7d76 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/functions/ObjectManagement.java @@ -0,0 +1,609 @@ +package com.laytonsmith.core.functions; + +import com.laytonsmith.PureUtilities.Common.StringUtils; +import com.laytonsmith.PureUtilities.SmartComment; +import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.annotations.api; +import com.laytonsmith.annotations.hide; +import com.laytonsmith.core.ArgumentValidation; +import com.laytonsmith.core.natives.interfaces.Callable; +import com.laytonsmith.core.FullyQualifiedClassName; +import com.laytonsmith.core.MSVersion; +import com.laytonsmith.core.Optimizable; +import com.laytonsmith.core.ParseTree; +import com.laytonsmith.core.Script; +import com.laytonsmith.core.UnqualifiedClassName; +import com.laytonsmith.core.compiler.CompilerEnvironment; +import com.laytonsmith.core.compiler.FileOptions; +import com.laytonsmith.core.constructs.CArray; +import com.laytonsmith.core.constructs.CClassType; +import com.laytonsmith.core.constructs.CFunction; +import com.laytonsmith.core.constructs.CInt; +import com.laytonsmith.core.constructs.CNull; +import com.laytonsmith.core.constructs.CString; +import com.laytonsmith.core.constructs.CVoid; +import com.laytonsmith.core.constructs.NativeTypeList; +import com.laytonsmith.core.constructs.Target; +import com.laytonsmith.core.environments.Environment; +import com.laytonsmith.core.environments.GlobalEnv; +import com.laytonsmith.core.exceptions.CRE.CREClassDefinitionError; +import com.laytonsmith.core.exceptions.CRE.CREThrowable; +import com.laytonsmith.core.exceptions.ConfigCompileException; +import com.laytonsmith.core.exceptions.ConfigCompileGroupException; +import com.laytonsmith.core.exceptions.ConfigRuntimeException; +import com.laytonsmith.core.natives.interfaces.MAnnotation; +import com.laytonsmith.core.natives.interfaces.Mixed; +import com.laytonsmith.core.objects.AccessModifier; +import com.laytonsmith.core.objects.DuplicateObjectDefinitionException; +import com.laytonsmith.core.objects.ElementDefinition; +import com.laytonsmith.core.objects.ObjectDefinition; +import com.laytonsmith.core.objects.ObjectDefinitionNotFoundException; +import com.laytonsmith.core.objects.ObjectDefinitionTable; +import com.laytonsmith.core.objects.ObjectModifier; +import com.laytonsmith.core.objects.ObjectType; +import com.laytonsmith.core.objects.UserObject; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * + */ +public class ObjectManagement { + public static String docs() { + return "Provides functions for creating and using objects. None of these methods should normally be used," + + " all of them provide easier to use compiler support."; + } + + @api + @hide("Not ready for consumption by mortals yet.") + public static class dereference extends AbstractFunction { + + @Override + public Class[] thrown() { + return new Class[]{}; + } + + @Override + public boolean isRestricted() { + return false; + } + + @Override + public Boolean runAsync() { + return null; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public String getName() { + return "dereference"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{2}; + } + + @Override + public String docs() { + return "mixed {object, element} Dereferences a property on a value."; + } + + @Override + public Version since() { + return MSVersion.V0_0_0; + } + + } + + @api + @hide("Not meant for normal use") + public static class define_object extends AbstractFunction implements Optimizable { + + @Override + public Class[] thrown() { + return new Class[]{CREClassDefinitionError.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return null; + } + + @Override + public boolean useSpecialExec() { + return true; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + throw new Error(); + } + + /** + * We can't use parent in the execs function, because we are expected to work at compile time. But + * that also means we can't use dynamic elements, like array. Though that is ok, so long as the + * array is hardcoded. But it also means we have to manually compile the array. + * @param data + * @param t + * @return + */ + private Mixed evaluateArray(ParseTree data, Target t) { + if(data.getData() instanceof CNull) { + return CNull.NULL; + } + CArray n = new CArray(t); + if(!(data.getData() instanceof CFunction) || !data.getData().val().equals(DataHandling.array.NAME)) { + throw new CREClassDefinitionError("Expected array, but found " + data.getData() + " instead", t); + } + for(ParseTree child : data.getChildren()) { + if(child.isDynamic()) { + throw new CREClassDefinitionError("Dynamic elements may not be used in a class definition", t); + } + n.push(child.getData(), t); + } + return n; + } + + private CArray evaluateArrayNoNull(ParseTree data, String component, Target t) { + Mixed d = evaluateArray(data, t); + if(d instanceof CNull) { + throw new CREClassDefinitionError("Unexpected null value for " + component + ", expected an array", t); + } + return (CArray) d; + } + + private Mixed evaluateString(ParseTree data, Target t) { + if(data.getData() instanceof CNull) { + return CNull.NULL; + } + if(!(data.getData().isInstanceOf(CString.TYPE))) { + throw new CREClassDefinitionError("Expected a string, but found " + data.getData() + " instead", t); + } + return data.getData(); + } + private CString evaluateStringNoNull(ParseTree data, Target t) { + Mixed d = evaluateString(data, t); + if(d instanceof CNull) { + throw new CREClassDefinitionError("Expected a string, but found null instead", t); + } + return (CString) d; + } + + private Mixed evaluateMixed(ParseTree data, Target t) { + if(data.isDynamic()) { + // TODO: Since CClassType doesn't know about other classes yet, we can't allow hardcoding yet, as it's + // a chicken and egg problem. Eventually, when we get a two pass compiler, this can be re-allowed, but + // until then, we have to accept dynamic input. "Dynamic input" in this case is just the + // __to_class_reference__ function however. +// throw new CREClassDefinitionError("Expected a non-dynamic value, but " + data.getData() +// + " was found.", t); + if(!(data.getData() instanceof CFunction) || !data.getData().val().equals("__to_class_reference__")) { + throw new CREClassDefinitionError("Expected __to_class_reference__, but found " + data.getData() + + " instead", t); + } + return new __to_class_reference__().exec(t, null, + data.getChildren().stream() + .map((parseTree -> parseTree.getData())) + .collect(Collectors.toList()) + .toArray(new Mixed[1])); + } + return data.getData(); + } + + @Override + public Mixed execs(Target t, Environment env, Script parent, ParseTree... nodes) { + // 0 - Access Modifier + AccessModifier accessModifier = ArgumentValidation.getEnum(evaluateStringNoNull(nodes[0], t), + AccessModifier.class, t); + + // 1 - Object Modifiers + Set objectModifiers = evaluateArrayNoNull(nodes[1], "object modifiers", t).asList().stream() + .map((item) -> ArgumentValidation.getEnum(item, ObjectModifier.class, t)) + .collect(Collectors.toSet()); + + // 2 - Object Type + ObjectType type = ArgumentValidation.getEnum(evaluateStringNoNull(nodes[2], t), ObjectType.class, t); + + // 3 - Object Name + FullyQualifiedClassName name + = FullyQualifiedClassName.forFullyQualifiedClass(evaluateStringNoNull(nodes[3], t).val()); + + // 4 - Superclasses + Set superclasses = new HashSet<>(); + { + CArray su = evaluateArrayNoNull(nodes[4], "superclasses", t); + if(!type.canUseExtends() && !su.isEmpty()) { + throw new CREClassDefinitionError("An object definition of type " + type.name().toLowerCase() + + " may not extend" + + " another object type" + (type.canUseImplements() ? " (though it can implement" + + " other types)" : "") + ".", t); + } + for(Mixed m : su) { + if(m instanceof CClassType) { + superclasses.add(new UnqualifiedClassName(((CClassType) m).getFQCN())); + } else { + superclasses.add(new UnqualifiedClassName(m.val(), t)); + } + } + } + + if(type.extendsMixed() && superclasses.isEmpty()) { + superclasses.add(Mixed.TYPE.getFQCN().asUCN()); + } + + // 5 - Interfaces + Set interfaces = new HashSet<>(); + { + CArray su = evaluateArrayNoNull(nodes[5], "interfaces", t); + for(Mixed m : su) { + if(m instanceof CClassType) { + interfaces.add(new UnqualifiedClassName(((CClassType) m).getFQCN())); + } else { + interfaces.add(new UnqualifiedClassName(m.val(), t)); + } + } + + } + + // 6- Enum list + Mixed el = evaluateArray(nodes[6], t); + if(type != ObjectType.ENUM && el != CNull.NULL) { + throw new CREClassDefinitionError("Only enum types may define an enum list", t); + } else if(type == ObjectType.ENUM && el == CNull.NULL) { + throw new CREClassDefinitionError("Enum type was defined, but sent null as enum list. It may be an" + + " empty array, but cannot be null.", t); + } else { + // TODO + } + + // 7 - mapelement> + Map> elementDefinitions = new HashMap<>(); + // TODO + + // 8 - array + List annotations = new ArrayList<>(); + // TODO + + // 9 - containing class + // The containing class must have been created first, so we can just jump to using CClassType already. + CClassType containingClass; + if(nodes[9].getData() instanceof CNull) { + containingClass = null; + } else { + containingClass = ArgumentValidation.getClassType(evaluateMixed(nodes[9], t), t); + } + + // 10 - Class Comment + SmartComment classComment = null; + // TODO + + Class nativeClass = null; + if(objectModifiers.contains(ObjectModifier.NATIVE)) { + try { + // It must exist in the native type list + nativeClass = NativeTypeList.getNativeClass(name); + } catch (ClassNotFoundException ex) { + throw new CREClassDefinitionError(name + " was defined as a native class, but could not find" + + " the native class associated with it.", t); + } + } + + // 11 - Generic Parameter declarations + // TODO This should of course not be Object, but I need + // to create a new class first. + List genericDeclarations = new ArrayList<>(); + + // TODO Populate the native elements in the ElementDefinition + + // Native classes MUST define a constructor, they are not allowed to use the default + // constructor. They *may* define a native constructor, but they must explicitly do + // so. They also must directly call one of the native constructors in the non-native + // constructors, so we know that the native class will be properly defined. If a native + // class is unconstructable (i.e. static utility class) it must define a private constructor, + // and take care never to call it internally. + if(objectModifiers.contains(ObjectModifier.NATIVE)) { + if(elementDefinitions.get("").isEmpty()) { + throw new CREClassDefinitionError(name + " was defined as a native class, but did not define" + + " any constructors. Native classes do not get a default constructor, and so must" + + " explicitly define at least one. (It may have no arguments and point to an" + + " @ExposedProperty constructor in the native code, however.) At least one" + + " native constructor must be defined, and called during construction.", t); + } + // TODO check if the non-native constructors actually call one of the native ones + } + + ObjectDefinition def = new ObjectDefinition( + accessModifier, + objectModifiers, + type, + CClassType.defineClass(name), + superclasses, + interfaces, + containingClass, + t, + elementDefinitions, + annotations, + classComment, + genericDeclarations, + nativeClass); + if(env == null) { + throw new Error("Environment may not be null"); + } + ObjectDefinitionTable odt = env.getEnv(CompilerEnvironment.class).getObjectDefinitionTable(); + try { + odt.add(def, t); + } catch (DuplicateObjectDefinitionException ex) { + throw new CREClassDefinitionError("Class " + name + " already defined, cannot redefine!", t); + } + + if(env.getEnv(GlobalEnv.class).GetCustom("define_object.noQualifyClasses") == null) { + try { + // During the course of initial compilation, we do not qualify classes, because the class library + // does its initial pass, irregardless of what order the classes are defined, and then, after + // all the class libraries are loaded, the loaded classes are looped through and qualified in bulk. + // However, during the course of normal runtime, new classes are allowed to be defined, but they + // are defined and qualified at the same time, so they are ready for immediate use. The bulk compilation + // option is set only at first load, and then unset, so normal runtime will not have this flag set. + def.qualifyClasses(env); + } catch (ConfigCompileGroupException ex) { + List msgs = new ArrayList<>(); + for(ConfigCompileException e : ex.getList()) { + msgs.add(e.getMessage() + " - " + e.getTarget()); + } + throw new CREClassDefinitionError("One or more compile errors occurred while trying to compile " + + def.getName() + ":\n" + StringUtils.Join(msgs, "\n"), t); + } + } + + return CVoid.VOID; + } + + @Override + public ParseTree optimizeDynamic(Target t, Environment env, + Set> envs, + List children, FileOptions fileOptions) + throws ConfigCompileException, ConfigRuntimeException { + // Do the same thing as execs, but remove this call + execs(t, env, null, children.toArray(new ParseTree[children.size()])); + return REMOVE_ME; + } + + @Override + public String getName() { + return "define_object"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{12}; + } + + @Override + public String docs() { + return "void {AccessModifier accessModifier," + + " array objectModifiers," + + " ObjectType objectType," + + " string objectName," + + " array superclasses," + + " array interfaces," + + " ? enumList," + + " map elementList," + + " array annotations," + + " ClassType containingClass," + + " string classComment," + + " array genericParameters" + + "} Defines a new object. Not meant for normal use."; + } + + @Override + public Version since() { + return MSVersion.V3_3_4; + } + + @Override + public Set optimizationOptions() { + return EnumSet.of(OptimizationOption.OPTIMIZE_DYNAMIC); + } + + } + + @api + @hide("Normally one should use the new keyword") + public static class new_object extends AbstractFunction implements Optimizable { + + @Override + public Class[] thrown() { + return new Class[]{}; + } + + @Override + public boolean isRestricted() { + return false; + } + + @Override + public Boolean runAsync() { + return null; + } + + @Override + public boolean useSpecialExec() { + return true; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + throw new Error(); + } + + + // To speed up constructor identification, we modify the code at compile time to contain + // a reference to the constructor in the second parameter. However, there are some + // special codes we must support, those are defined here. + private static final int DEFAULT = -1; + private static final int UNDECIDEABLE = -2; + + @Override + public Mixed execs(final Target t, final Environment env, Script parent, ParseTree... args) + throws ConfigRuntimeException { + ObjectDefinitionTable odt = env.getEnv(CompilerEnvironment.class).getObjectDefinitionTable(); + CClassType clazz = ((CClassType) args[0].getData()); + ObjectDefinition od; + try { + od = odt.get(clazz.getFQCN()); + } catch (ObjectDefinitionNotFoundException ex) { + throw new CREClassDefinitionError(ex.getMessage(), t, ex); + } + int constructorId = (int) ((CInt) args[1].getData()).getInt(); + Callable constructor; + switch(constructorId) { + case DEFAULT: + // Guaranteed not to be a native class + constructor = null; + break; + case UNDECIDEABLE: + for(ElementDefinition ed : od.getElements().get("")) { + // TODO + } + constructor = null; // TODO REMOVE ME + break; + default: + // TODO + constructor = null; // TODO REMOVE ME + break; + } + // Construct the object! + // This is the native construction. + Mixed nativeObject = null; + if(od.isNative()) { + // TODO If this is a native object, we need to intercept the call to the native constructor, + // and grab the object generated there. + } + Mixed obj = new UserObject(t, parent, env, od, null); + // This is the MethodScript construction. + if(constructor != null) { + Mixed[] values = new Mixed[args.length - 1]; + values[0] = obj; + for(int i = 2; i < args.length; i++) { + values[i + 1] = parent.eval(args[i], env); + } + constructor.executeCallable(env, t, values); + } + return obj; + } + + @Override + public ParseTree optimizeDynamic(Target t, Environment env, + Set> envs, + List children, FileOptions fileOptions) + throws ConfigCompileException, ConfigRuntimeException { + ObjectDefinitionTable odt = env.getEnv(CompilerEnvironment.class).getObjectDefinitionTable(); + // The first parameter must be hardcoded, and be a class in the compiler environment. The rest of the + // parameters must match a constructor, but if most types are auto, we may only be able to count the + // parameters, rather than compare types. + if(children.get(0).isDynamic()) { + throw new ConfigCompileException("The first parameter to new_object must be hardcoded.", t); + } + FullyQualifiedClassName fqcn = FullyQualifiedClassName.forName(children.get(0).getData().val(), t, env); + ObjectDefinition od; + try { + od = odt.get(fqcn); + } catch (ObjectDefinitionNotFoundException ex) { + throw new ConfigCompileException("Could not find class with name " + fqcn + ". Are you missing" + + " a \"use\" statement?", t); + } + try { + children.set(0, new ParseTree(CClassType.get(fqcn), fileOptions)); + } catch (ClassNotFoundException ex) { + // This shouldn't happen, as we would have already thrown a CCE above if the class + // really didn't exist. + throw new Error(ex); + } + List constructors = od.getElements().get(""); + int id; + if(constructors == null || constructors.isEmpty()) { + // Default constructor + if(children.size() > 1) { + throw new ConfigCompileException("No suitable constructor found for " + fqcn + " only the default" + + " constructor is available.", t); + } + id = DEFAULT; + } else { + // Need to find the correct constructor from the list + int parameterCount = children.size() - 1; + for(ElementDefinition d : constructors) { + // TODO + } + id = UNDECIDEABLE; + } + children.add(1, new ParseTree(new CInt(id, t), fileOptions)); + return null; + } + + + + @Override + public String getName() { + return "new_object"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{Integer.MAX_VALUE}; + } + + @Override + public String docs() { + return " T {ClassType type, params...} Constructs a new object of the specified type. The type must" + + " be hardcoded."; + } + + @Override + public Version since() { + return MSVersion.V3_3_4; + } + + @Override + public Set optimizationOptions() { + return EnumSet.of(OptimizationOption.OPTIMIZE_DYNAMIC); + } + + } + + @api + @hide("This is only used internally to solve the chicken and egg problem") + public static class __to_class_reference__ extends DummyFunction { + + @Override + public Class[] thrown() { + return new Class[]{CREClassDefinitionError.class}; + } + + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + try { + return CClassType.get(FullyQualifiedClassName.forFullyQualifiedClass(args[0].val())); + } catch (ClassNotFoundException ex) { + throw new CREClassDefinitionError(ex.getMessage(), t, ex); + } + } + + } +} diff --git a/src/main/java/com/laytonsmith/core/functions/Performance.java b/src/main/java/com/laytonsmith/core/functions/Performance.java index 09671881c0..38adac62bd 100644 --- a/src/main/java/com/laytonsmith/core/functions/Performance.java +++ b/src/main/java/com/laytonsmith/core/functions/Performance.java @@ -1,17 +1,17 @@ - - package com.laytonsmith.core.functions; import com.laytonsmith.annotations.api; -import com.laytonsmith.core.CHVersion; +import com.laytonsmith.core.ArgumentValidation; +import com.laytonsmith.core.MSVersion; import com.laytonsmith.core.Prefs; import com.laytonsmith.core.Static; import com.laytonsmith.core.constructs.CVoid; -import com.laytonsmith.core.constructs.Construct; import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.environments.Environment; +import com.laytonsmith.core.exceptions.CRE.CRESecurityException; +import com.laytonsmith.core.exceptions.CRE.CREThrowable; import com.laytonsmith.core.exceptions.ConfigRuntimeException; -import com.laytonsmith.core.functions.Exceptions.ExceptionType; +import com.laytonsmith.core.natives.interfaces.Mixed; import java.io.File; import java.io.IOException; import java.util.logging.Level; @@ -20,72 +20,76 @@ /** * - + * */ public class Performance { - public static boolean PERFORMANCE_LOGGING = false; - public static String docs(){ - return "This class provides functions for hooking into CommandHelper's powerful Performance measuring. To use the functions, you must have" - + " allow-profiling option set to true in your preferences file."; - } - public static void DoLog(File root, StopWatch stopWatch) { - try { - Static.QuickAppend(Static.profilingLogFile(root), "start[" + stopWatch.getStartTime() + "] time[" + stopWatch.getElapsedTime() + "] " - + "tag[" + stopWatch.getTag() + "]\n"); - } catch (IOException ex) { - Logger.getLogger(Performance.class.getName()).log(Level.SEVERE, null, ex); - } - } - - @api public static class enable_performance_logging extends AbstractFunction{ + public static boolean performanceLogging = false; + + public static String docs() { + return "This class provides functions for hooking into CommandHelper's powerful Performance measuring. To use the functions, you must have" + + " allow-profiling option set to true in your preferences file."; + } + + public static void DoLog(File root, StopWatch stopWatch) { + try { + Static.QuickAppend(Static.profilingLogFile(root), "start[" + stopWatch.getStartTime() + "] time[" + stopWatch.getElapsedTime() + "] " + + "tag[" + stopWatch.getTag() + "]\n"); + } catch (IOException ex) { + Logger.getLogger(Performance.class.getName()).log(Level.SEVERE, null, ex); + } + } + + @api + public static class enable_performance_logging extends AbstractFunction { @Override - public String getName() { - return "enable_performance_logging"; - } + public String getName() { + return "enable_performance_logging"; + } @Override - public Integer[] numArgs() { - return new Integer[]{1}; - } + public Integer[] numArgs() { + return new Integer[]{1}; + } @Override - public String docs() { - return "void {boolean} Enables performance logging. The allow-profiling option must be set to true in your preferences file," - + " and play-dirty mode must be active. If allow-profiling is set to false, a SecurityException is thrown." - + " The debug filters are used by the performance logger, if you choose to filter through the events." - + " See the documenation" - + " for more details on performance logging."; - } + public String docs() { + return "void {boolean} Enables performance logging. The allow-profiling option must be set to true in your preferences file," + + " and play-dirty mode must be active. If allow-profiling is set to false, a SecurityException is thrown." + + " The debug filters are used by the performance logger, if you choose to filter through the events." + + " See the documentation" + + " for more details on performance logging."; + } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.SecurityException}; - } + public Class[] thrown() { + return new Class[]{CRESecurityException.class}; + } @Override - public boolean isRestricted() { - return true; - } + public boolean isRestricted() { + return true; + } + @Override - public CHVersion since() { - return CHVersion.V3_3_0; - } + public MSVersion since() { + return MSVersion.V3_3_0; + } @Override - public Boolean runAsync() { - return null; - } + public Boolean runAsync() { + return null; + } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - if(!Prefs.AllowProfiling()){ - throw new ConfigRuntimeException("allow-profiling is currently off, you must set it to true in your preferences.", ExceptionType.SecurityException, t); - } - PERFORMANCE_LOGGING = Static.getBoolean(args[0]); - return CVoid.VOID; - } - - } + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + if(!Prefs.AllowProfiling()) { + throw new CRESecurityException("allow-profiling is currently off, you must set it to true in your preferences.", t); + } + performanceLogging = ArgumentValidation.getBoolean(args[0], t); + return CVoid.VOID; + } + + } } diff --git a/src/main/java/com/laytonsmith/core/functions/Permissions.java b/src/main/java/com/laytonsmith/core/functions/Permissions.java index 406a9005c7..e90b3e6474 100644 --- a/src/main/java/com/laytonsmith/core/functions/Permissions.java +++ b/src/main/java/com/laytonsmith/core/functions/Permissions.java @@ -1,24 +1,23 @@ package com.laytonsmith.core.functions; -import com.laytonsmith.abstraction.MCBlockCommandSender; import com.laytonsmith.abstraction.MCCommandSender; -import com.laytonsmith.abstraction.MCConsoleCommandSender; import com.laytonsmith.abstraction.MCPlayer; import com.laytonsmith.annotations.api; -import com.laytonsmith.core.CHVersion; -import com.laytonsmith.core.PermissionsResolver; +import com.laytonsmith.core.MSVersion; import com.laytonsmith.core.Static; import com.laytonsmith.core.constructs.CBoolean; -import com.laytonsmith.core.constructs.Construct; import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.environments.CommandHelperEnvironment; import com.laytonsmith.core.environments.Environment; import com.laytonsmith.core.environments.GlobalEnv; +import com.laytonsmith.core.exceptions.CRE.CREInsufficientPermissionException; +import com.laytonsmith.core.exceptions.CRE.CREPlayerOfflineException; +import com.laytonsmith.core.exceptions.CRE.CREThrowable; import com.laytonsmith.core.exceptions.ConfigRuntimeException; -import com.laytonsmith.core.functions.Exceptions.ExceptionType; +import com.laytonsmith.core.natives.interfaces.Mixed; /** - * + * */ public class Permissions { @@ -27,7 +26,7 @@ public static String docs() { + " system in place, however, and so not all functions may be supported on a given system."; } - @api(environments={CommandHelperEnvironment.class, GlobalEnv.class}) + @api(environments = {CommandHelperEnvironment.class, GlobalEnv.class}) public static class has_permission extends AbstractFunction { @Override @@ -42,16 +41,17 @@ public Integer[] numArgs() { @Override public String docs() { - return "boolean {[player], permissionName} Using the built in permissions system, checks to see if the player has a particular permission." - + " This is simply passed through to the permissions system. This function does not throw a PlayerOfflineException, because" - + " it works with offline players, but that means that names must be an exact match. If you notice, this function isn't" - + " restricted. However, it IS restricted if the player attempts to check another player's permissions. If run from" - + " the console, will always return true."; + return "boolean {[player], permissionName} Using the built in permissions system," + + " checks to see if the player has a particular permission." + + " This is simply passed through to the permissions system." + + " This function is only restricted if the player attempts to check another player's permissions." + + " If run from the console or a CommandBlock, will always return true unless a value has been" + + " explicitly set for them."; } @Override - public Exceptions.ExceptionType[] thrown() { - return new Exceptions.ExceptionType[]{ExceptionType.InsufficientPermissionException, ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CREInsufficientPermissionException.class, CREPlayerOfflineException.class}; } @Override @@ -60,8 +60,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_0; + public MSVersion since() { + return MSVersion.V3_3_0; } @Override @@ -70,42 +70,38 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - String player = null; - String permission = null; - - if (args.length == 1) { - final MCCommandSender mcc = environment.getEnv(CommandHelperEnvironment.class).GetCommandSender(); - if (mcc instanceof MCConsoleCommandSender || mcc instanceof MCBlockCommandSender) { - // Console and CommandBlocks always have permission - return CBoolean.TRUE; - } + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { - MCPlayer mcp = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); - if(mcp == null){ - throw new ConfigRuntimeException("No player was specified", Exceptions.ExceptionType.PlayerOfflineException, t); - } - player = mcp.getName(); + MCCommandSender sender; + String permission; + + if(args.length == 1) { + sender = environment.getEnv(CommandHelperEnvironment.class).GetCommandSender(); permission = args[0].val(); } else { - player = args[0].val(); + sender = Static.GetCommandSender(args[0].val(), t); permission = args[1].val(); - } - if (environment.getEnv(CommandHelperEnvironment.class).GetPlayer() != null && !environment.getEnv(CommandHelperEnvironment.class).GetPlayer().getName().equals(player)) { - if (!Static.hasCHPermission(getName(), environment)) { - throw new ConfigRuntimeException("You do not have permission to use the " + getName() + " function.", - Exceptions.ExceptionType.InsufficientPermissionException, t); + MCPlayer mcp = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); + if(mcp != null && !mcp.getName().equals(args[0].val())) { + if(!Static.hasCHPermission(getName(), environment)) { + throw new CREInsufficientPermissionException("You do not have permission to use the " + getName() + " function.", t); + } } } - if (Static.getConsoleName().equals(player.toLowerCase()) || player.startsWith(Static.getBlockPrefix())) { - // Console and CommandBlocks always have permission + if(sender == null) { + return CBoolean.FALSE; // Return false for null command senders. + } + + if((Static.getConsoleName().equals(sender.getName().toLowerCase()) + || sender.getName().startsWith(Static.getBlockPrefix())) + && !sender.isPermissionSet(permission)) { + // Console and CommandBlocks always have permission unless specifically set otherwise return CBoolean.TRUE; } - PermissionsResolver perms = environment.getEnv(GlobalEnv.class).GetPermissionsResolver(); - return CBoolean.get(perms.hasPermission(player, permission)); + return CBoolean.get(sender.hasPermission(permission)); } } } diff --git a/src/main/java/com/laytonsmith/core/functions/Persistence.java b/src/main/java/com/laytonsmith/core/functions/Persistence.java index 82c349102e..1d3e7804e5 100644 --- a/src/main/java/com/laytonsmith/core/functions/Persistence.java +++ b/src/main/java/com/laytonsmith/core/functions/Persistence.java @@ -4,8 +4,9 @@ import com.laytonsmith.annotations.api; import com.laytonsmith.annotations.core; import com.laytonsmith.annotations.noboilerplate; -import com.laytonsmith.core.CHLog; -import com.laytonsmith.core.CHVersion; +import com.laytonsmith.annotations.seealso; +import com.laytonsmith.core.MSLog; +import com.laytonsmith.core.MSVersion; import com.laytonsmith.core.LogLevel; import com.laytonsmith.core.constructs.CArray; import com.laytonsmith.core.constructs.CBoolean; @@ -16,11 +17,16 @@ import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.environments.Environment; import com.laytonsmith.core.environments.GlobalEnv; +import com.laytonsmith.core.environments.StaticRuntimeEnv; +import com.laytonsmith.core.exceptions.CRE.CREFormatException; +import com.laytonsmith.core.exceptions.CRE.CREIOException; +import com.laytonsmith.core.exceptions.CRE.CREInsufficientArgumentsException; +import com.laytonsmith.core.exceptions.CRE.CREThrowable; import com.laytonsmith.core.exceptions.CancelCommandException; import com.laytonsmith.core.exceptions.ConfigCompileException; import com.laytonsmith.core.exceptions.ConfigRuntimeException; import com.laytonsmith.core.exceptions.MarshalException; -import com.laytonsmith.core.functions.Exceptions.ExceptionType; +import com.laytonsmith.core.natives.interfaces.Mixed; import com.laytonsmith.persistence.DataSourceException; import com.laytonsmith.persistence.PersistenceNetwork; import com.laytonsmith.persistence.ReadOnlyException; @@ -33,22 +39,23 @@ import java.util.logging.Logger; /** - * + * */ @core public class Persistence { public static String docs() { - return "Allows scripts to store data from execution to execution. See the guide on [[CommandHelper/Persistence|persistence]] for more information." - + " In all the functions, you may send multiple arguments for the key, which will automatically" - + " be concatenated with a period (the namespace separator). No magic happens here, you can" - + " put periods yourself, or combine manually namespaced values or automatically namespaced values" - + " with no side effects. All the functions in the Persistence API are threadsafe (though not necessarily" - + " process safe)."; + return "Allows scripts to store data from execution to execution. See the guide on" + + " [[Persistence_Network|persistence]] for more information. In all the functions, you may send" + + " multiple arguments for the key, which will automatically be concatenated with a period (the" + + " namespace separator). No magic happens here, you can put periods yourself, or combine manually" + + " namespaced values or automatically namespaced values with no side effects. All the functions in the" + + " Persistence API are threadsafe (though not necessarily process safe)."; } - @api(environments={GlobalEnv.class}) + @api(environments = {GlobalEnv.class}) @noboilerplate + @seealso({get_value.class, clear_value.class, com.laytonsmith.tools.docgen.templates.PersistenceNetwork.class}) public static class store_value extends AbstractFunction { @Override @@ -69,8 +76,8 @@ public String docs() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.FormatException, ExceptionType.IOException, ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CREFormatException.class, CREIOException.class, CREFormatException.class}; } @Override @@ -79,40 +86,40 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_0_2; + public MSVersion since() { + return MSVersion.V3_0_2; } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws CancelCommandException, ConfigRuntimeException { String key = GetNamespace(args, args.length - 1, getName(), t); String value = null; try { value = Construct.json_encode(args[args.length - 1], t); } catch (MarshalException e) { - throw new Exceptions.FormatException(e.getMessage(), t); + throw new CREFormatException(e.getMessage(), t); } char pc = '.'; - for (int i = 0; i < key.length(); i++) { + for(int i = 0; i < key.length(); i++) { Character c = key.charAt(i); - if (i != 0) { + if(i != 0) { pc = key.charAt(i - 1); } - if ((i == 0 || i == key.length() - 1 || pc == '.') && c == '.') { - throw new ConfigRuntimeException("Periods may only be used as seperators between namespaces.", ExceptionType.FormatException, t); + if((i == 0 || i == key.length() - 1 || pc == '.') && c == '.') { + throw new CREFormatException("Periods may only be used as seperators between namespaces.", t); } - if (c != '_' && c != '.' && !Character.isLetterOrDigit(c)) { - throw new ConfigRuntimeException("Param 1 in store_value must only contain letters, digits, underscores, or dots, (which denote namespaces).", - ExceptionType.FormatException, t); + if(c != '_' && c != '.' && !Character.isLetterOrDigit(c)) { + throw new CREFormatException("Param 1 in store_value must only contain letters, digits, underscores, or dots, (which denote namespaces).", t); } } - CHLog.GetLogger().Log(CHLog.Tags.PERSISTENCE, LogLevel.DEBUG, "Storing: " + key + " -> " + value, t); + MSLog.GetLogger().Log(MSLog.Tags.PERSISTENCE, LogLevel.DEBUG, "Storing: " + key + " -> " + value, t); try { - env.getEnv(GlobalEnv.class).GetPersistenceNetwork().set(env.getEnv(GlobalEnv.class).GetDaemonManager(), ("storage." + key).split("\\."), value); - } catch(IllegalArgumentException e){ - throw new ConfigRuntimeException(e.getMessage(), ExceptionType.FormatException, t); + env.getEnv(StaticRuntimeEnv.class).GetPersistenceNetwork().set( + env.getEnv(StaticRuntimeEnv.class).GetDaemonManager(), ("storage." + key).split("\\."), value); + } catch (IllegalArgumentException e) { + throw new CREFormatException(e.getMessage(), t); } catch (Exception ex) { - throw new ConfigRuntimeException(ex.getMessage(), ExceptionType.IOException, t, ex); + throw new CREIOException(ex.getMessage(), t, ex); } return CVoid.VOID; } @@ -129,8 +136,9 @@ public LogLevel profileAt() { } } - @api(environments={GlobalEnv.class}) + @api(environments = {GlobalEnv.class}) @noboilerplate + @seealso({store_value.class, get_values.class, has_value.class, com.laytonsmith.tools.docgen.templates.PersistenceNetwork.class}) public static class get_value extends AbstractFunction { @Override @@ -145,14 +153,14 @@ public Integer[] numArgs() { @Override public String docs() { - return "Mixed {[namespace, ...,] key} Returns a stored value stored with store_value. If the key doesn't exist in storage, null" + return "mixed {[namespace, ...,] key} Returns a stored value stored with store_value. If the key doesn't exist in storage, null" + " is returned. On a more detailed note: If the value stored in the persistence database is not actually a construct," + " then null is also returned."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.IOException, ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CREIOException.class, CREFormatException.class}; } @Override @@ -161,25 +169,26 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_0_2; + public MSVersion since() { + return MSVersion.V3_0_2; } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws CancelCommandException, ConfigRuntimeException { Object o; String namespace = GetNamespace(args, null, getName(), t); - CHLog.GetLogger().Log(CHLog.Tags.PERSISTENCE, LogLevel.DEBUG, "Getting value: " + namespace, t); + MSLog.GetLogger().Log(MSLog.Tags.PERSISTENCE, LogLevel.DEBUG, "Getting value: " + namespace, t); try { Object obj; try { - obj = env.getEnv(GlobalEnv.class).GetPersistenceNetwork().get(("storage." + namespace).split("\\.")); + obj = env.getEnv(StaticRuntimeEnv.class) + .GetPersistenceNetwork().get(("storage." + namespace).split("\\.")); } catch (DataSourceException ex) { - throw new ConfigRuntimeException(ex.getMessage(), ExceptionType.IOException, t, ex); - } catch(IllegalArgumentException e){ - throw new ConfigRuntimeException(e.getMessage(), ExceptionType.FormatException, t, e); + throw new CREIOException(ex.getMessage(), t, ex); + } catch (IllegalArgumentException e) { + throw new CREFormatException(e.getMessage(), t, e); } - if (obj == null) { + if(obj == null) { return CNull.NULL; } o = Construct.json_decode(obj.toString(), t); @@ -187,7 +196,7 @@ public Construct exec(Target t, Environment env, Construct... args) throws Cance throw ConfigRuntimeException.CreateUncatchableException(ex.getMessage(), t); } try { - return (Construct) o; + return (Mixed) o; } catch (ClassCastException e) { return CNull.NULL; } @@ -205,8 +214,9 @@ public LogLevel profileAt() { } } - @api(environments={GlobalEnv.class}) + @api(environments = {GlobalEnv.class}) @noboilerplate + @seealso({com.laytonsmith.tools.docgen.templates.PersistenceNetwork.class}) public static class get_values extends AbstractFunction { @Override @@ -231,8 +241,8 @@ public String docs() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.IOException, ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CREIOException.class, CREFormatException.class}; } @Override @@ -248,29 +258,30 @@ public Boolean runAsync() { @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ - new ExampleScript("Getting values", "store_value('x.top.a',true)\nstore_value('x.top.b',false)\nmsg(get_values('x'))"), - }; + new ExampleScript("Getting values", "store_value('x.top.a',true)\n" + + "store_value('x.top.b',false)\n" + + "msg(get_values('x'))", "{x.top.a: true, x.top.b: false}")}; } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - PersistenceNetwork p = environment.getEnv(GlobalEnv.class).GetPersistenceNetwork(); + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + PersistenceNetwork p = environment.getEnv(StaticRuntimeEnv.class).GetPersistenceNetwork(); List keyChain = new ArrayList(); keyChain.add("storage"); String namespace = GetNamespace(args, null, getName(), t); - CHLog.GetLogger().Log(CHLog.Tags.PERSISTENCE, LogLevel.DEBUG, "Getting all values from " + namespace, t); + MSLog.GetLogger().Log(MSLog.Tags.PERSISTENCE, LogLevel.DEBUG, "Getting all values from " + namespace, t); keyChain.addAll(Arrays.asList(namespace.split("\\."))); Map list; try { list = p.getNamespace(keyChain.toArray(new String[keyChain.size()])); } catch (DataSourceException ex) { - throw new ConfigRuntimeException(ex.getMessage(), ExceptionType.IOException, t, ex); - } catch(IllegalArgumentException e){ - throw new ConfigRuntimeException(e.getMessage(), ExceptionType.FormatException, t, e); + throw new CREIOException(ex.getMessage(), t, ex); + } catch (IllegalArgumentException e) { + throw new CREFormatException(e.getMessage(), t, e); } - CArray ca = new CArray(t); - CHLog.GetLogger().Log(CHLog.Tags.PERSISTENCE, LogLevel.DEBUG, list.size() + " value(s) are being returned", t); - for (String[] e : list.keySet()) { + CArray ca = CArray.GetAssociativeArray(t); + MSLog.GetLogger().Log(MSLog.Tags.PERSISTENCE, LogLevel.DEBUG, list.size() + " value(s) are being returned", t); + for(String[] e : list.keySet()) { try { String key = StringUtils.Join(e, ".").replaceFirst("storage\\.", ""); //Get that junk out of here ca.set(new CString(key, t), @@ -283,8 +294,8 @@ public Construct exec(Target t, Environment environment, Construct... args) thro } @Override - public CHVersion since() { - return CHVersion.V3_3_0; + public MSVersion since() { + return MSVersion.V3_3_0; } @Override @@ -293,8 +304,9 @@ public LogLevel profileAt() { } } - @api(environments={GlobalEnv.class}) + @api(environments = {GlobalEnv.class}) @noboilerplate + @seealso({get_value.class, com.laytonsmith.tools.docgen.templates.PersistenceNetwork.class}) public static class has_value extends AbstractFunction { @Override @@ -313,8 +325,8 @@ public String docs() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.IOException, ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CREIOException.class, CREFormatException.class}; } @Override @@ -323,8 +335,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_1_2; + public MSVersion since() { + return MSVersion.V3_1_2; } @Override @@ -333,13 +345,14 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { try { - return CBoolean.get(env.getEnv(GlobalEnv.class).GetPersistenceNetwork().hasKey(("storage." + GetNamespace(args, null, getName(), t)).split("\\."))); + return CBoolean.get(env.getEnv(StaticRuntimeEnv.class).GetPersistenceNetwork() + .hasKey(("storage." + GetNamespace(args, null, getName(), t)).split("\\."))); } catch (DataSourceException ex) { - throw new ConfigRuntimeException(ex.getMessage(), ExceptionType.IOException, t, ex); - } catch(IllegalArgumentException e){ - throw new ConfigRuntimeException(e.getMessage(), ExceptionType.FormatException, t, e); + throw new CREIOException(ex.getMessage(), t, ex); + } catch (IllegalArgumentException e) { + throw new CREFormatException(e.getMessage(), t, e); } } @@ -349,8 +362,9 @@ public LogLevel profileAt() { } } - @api(environments={GlobalEnv.class}) + @api(environments = {GlobalEnv.class}) @noboilerplate + @seealso({store_value.class, com.laytonsmith.tools.docgen.templates.PersistenceNetwork.class}) public static class clear_value extends AbstractFunction { @Override @@ -369,8 +383,8 @@ public String docs() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.IOException, ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CREIOException.class, CREFormatException.class}; } @Override @@ -379,8 +393,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_0; + public MSVersion since() { + return MSVersion.V3_3_0; } @Override @@ -389,19 +403,17 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { String namespace = GetNamespace(args, null, getName(), t); - CHLog.GetLogger().Log(CHLog.Tags.PERSISTENCE, LogLevel.DEBUG, "Clearing value: " + namespace, t); + MSLog.GetLogger().Log(MSLog.Tags.PERSISTENCE, LogLevel.DEBUG, "Clearing value: " + namespace, t); try { - environment.getEnv(GlobalEnv.class).GetPersistenceNetwork().clearKey(environment.getEnv(GlobalEnv.class).GetDaemonManager(), ("storage." + namespace).split("\\.")); - } catch (DataSourceException ex) { - throw new ConfigRuntimeException(ex.getMessage(), ExceptionType.IOException, t, ex); - } catch (ReadOnlyException ex) { - throw new ConfigRuntimeException(ex.getMessage(), ExceptionType.IOException, t, ex); - } catch (IOException ex) { - throw new ConfigRuntimeException(ex.getMessage(), ExceptionType.IOException, t, ex); - } catch(IllegalArgumentException e){ - throw new ConfigRuntimeException(e.getMessage(), ExceptionType.FormatException, t, e); + environment.getEnv(StaticRuntimeEnv.class).GetPersistenceNetwork().clearKey( + environment.getEnv(StaticRuntimeEnv.class).GetDaemonManager(), + ("storage." + namespace).split("\\.")); + } catch (DataSourceException | ReadOnlyException | IOException ex) { + throw new CREIOException(ex.getMessage(), t, ex); + } catch (IllegalArgumentException e) { + throw new CREFormatException(e.getMessage(), t, e); } return CVoid.VOID; } @@ -413,25 +425,24 @@ public LogLevel profileAt() { } /** - * Generates the namespace for this value, given an array of constructs. If - * the entire list of arguments isn't supposed to be part of the namespace, - * the value to be excluded may be specified. + * Generates the namespace for this value, given an array of constructs. If the entire list of arguments isn't + * supposed to be part of the namespace, the value to be excluded may be specified. * * @param args * @param exclude * @return */ - private static String GetNamespace(Construct[] args, Integer exclude, String name, Target t) { - if (exclude != null && args.length < 2 || exclude == null && args.length < 1) { - throw new ConfigRuntimeException(name + " was not provided with enough arguments. Check the documentation, and try again.", ExceptionType.InsufficientArgumentsException, t); + private static String GetNamespace(Mixed[] args, Integer exclude, String name, Target t) { + if(exclude != null && args.length < 2 || exclude == null && args.length < 1) { + throw new CREInsufficientArgumentsException(name + " was not provided with enough arguments. Check the documentation, and try again.", t); } boolean first = true; StringBuilder b = new StringBuilder(); - for (int i = 0; i < args.length; i++) { - if (exclude != null && exclude == i) { + for(int i = 0; i < args.length; i++) { + if(exclude != null && exclude == i) { continue; } - if (!first) { + if(!first) { b.append("."); } first = false; diff --git a/src/main/java/com/laytonsmith/core/functions/PlayerManagement.java b/src/main/java/com/laytonsmith/core/functions/PlayerManagement.java index 63288779bb..21acb4dd87 100644 --- a/src/main/java/com/laytonsmith/core/functions/PlayerManagement.java +++ b/src/main/java/com/laytonsmith/core/functions/PlayerManagement.java @@ -1,30 +1,55 @@ package com.laytonsmith.core.functions; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonIOException; import com.laytonsmith.PureUtilities.Common.StringUtils; +import com.laytonsmith.PureUtilities.Vector3D; import com.laytonsmith.PureUtilities.Version; import com.laytonsmith.abstraction.MCBlockCommandSender; import com.laytonsmith.abstraction.MCCommandSender; import com.laytonsmith.abstraction.MCConsoleCommandSender; +import com.laytonsmith.abstraction.MCEntity; +import com.laytonsmith.abstraction.MCHumanEntity; import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.MCLivingEntity; import com.laytonsmith.abstraction.MCLocation; +import com.laytonsmith.abstraction.MCNamespacedKey; import com.laytonsmith.abstraction.MCOfflinePlayer; import com.laytonsmith.abstraction.MCPlayer; +import com.laytonsmith.abstraction.MCPlayerInput; import com.laytonsmith.abstraction.MCServer; import com.laytonsmith.abstraction.MCWorld; +import com.laytonsmith.abstraction.MCWorldBorder; import com.laytonsmith.abstraction.StaticLayer; -import com.laytonsmith.abstraction.Velocity; import com.laytonsmith.abstraction.blocks.MCBlock; +import com.laytonsmith.abstraction.blocks.MCBlockData; +import com.laytonsmith.abstraction.blocks.MCMaterial; +import com.laytonsmith.abstraction.blocks.MCSign; +import com.laytonsmith.abstraction.blocks.MCSignText; +import com.laytonsmith.abstraction.entities.MCCommandMinecart; +import com.laytonsmith.abstraction.enums.MCDyeColor; +import com.laytonsmith.abstraction.enums.MCEntityType; +import com.laytonsmith.abstraction.enums.MCEquipmentSlot; import com.laytonsmith.abstraction.enums.MCGameMode; +import com.laytonsmith.abstraction.enums.MCPlayerStatistic; +import com.laytonsmith.abstraction.enums.MCPotionEffectType; +import com.laytonsmith.abstraction.enums.MCSound; +import com.laytonsmith.abstraction.enums.MCSoundCategory; +import com.laytonsmith.abstraction.enums.MCWeather; import com.laytonsmith.annotations.api; -import com.laytonsmith.annotations.hide; +import com.laytonsmith.annotations.seealso; import com.laytonsmith.commandhelper.CommandHelperPlugin; -import com.laytonsmith.core.CHLog; -import com.laytonsmith.core.CHVersion; +import com.laytonsmith.core.ArgumentValidation; +import com.laytonsmith.core.MSLog; +import com.laytonsmith.core.MSVersion; import com.laytonsmith.core.LogLevel; import com.laytonsmith.core.ObjectGenerator; import com.laytonsmith.core.Optimizable; import com.laytonsmith.core.ParseTree; import com.laytonsmith.core.Static; +import com.laytonsmith.core.compiler.CompilerEnvironment; +import com.laytonsmith.core.compiler.CompilerWarning; import com.laytonsmith.core.compiler.FileOptions; import com.laytonsmith.core.constructs.CArray; import com.laytonsmith.core.constructs.CBoolean; @@ -38,10 +63,24 @@ import com.laytonsmith.core.environments.CommandHelperEnvironment; import com.laytonsmith.core.environments.Environment; import com.laytonsmith.core.environments.GlobalEnv; -import com.laytonsmith.core.exceptions.CancelCommandException; +import com.laytonsmith.core.exceptions.CRE.CREBadEntityException; +import com.laytonsmith.core.exceptions.CRE.CRECastException; +import com.laytonsmith.core.exceptions.CRE.CREFormatException; +import com.laytonsmith.core.exceptions.CRE.CREIOException; +import com.laytonsmith.core.exceptions.CRE.CREIllegalArgumentException; +import com.laytonsmith.core.exceptions.CRE.CREInsufficientArgumentsException; +import com.laytonsmith.core.exceptions.CRE.CREInvalidWorldException; +import com.laytonsmith.core.exceptions.CRE.CRELengthException; +import com.laytonsmith.core.exceptions.CRE.CRENotFoundException; +import com.laytonsmith.core.exceptions.CRE.CRENullPointerException; +import com.laytonsmith.core.exceptions.CRE.CREPlayerOfflineException; +import com.laytonsmith.core.exceptions.CRE.CREPluginInternalException; +import com.laytonsmith.core.exceptions.CRE.CRERangeException; +import com.laytonsmith.core.exceptions.CRE.CREThrowable; import com.laytonsmith.core.exceptions.ConfigCompileException; import com.laytonsmith.core.exceptions.ConfigRuntimeException; -import com.laytonsmith.core.functions.Exceptions.ExceptionType; +import com.laytonsmith.core.natives.interfaces.Mixed; + import java.io.IOException; import java.util.ArrayList; import java.util.Collection; @@ -53,18 +92,17 @@ import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; +import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; -/** - * - */ public class PlayerManagement { public static String docs() { - return "This class of functions allow players to be managed"; + return "This class of functions allow players to be managed. Functions that accept an online player's name will" + + " also accept their UUID."; } @api(environments = {CommandHelperEnvironment.class}) @@ -81,24 +119,24 @@ public Integer[] numArgs() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { MCCommandSender p = env.getEnv(CommandHelperEnvironment.class).GetCommandSender(); - if (args.length == 1) { + if(args.length == 1) { p = Static.GetPlayer(args[0], t); } // assuming p is not null, just return the name set by the CommandSender // for player entities in CraftBukkit, this is the player's name, and // for the console it's "CONSOLE". For CommandBlocks it's "@" unless it has been renamed. - if (p == null) { + if(p == null) { return CNull.NULL; } else { String name = p.getName(); - if (p instanceof MCConsoleCommandSender || "CONSOLE".equals(name)) { + if(p instanceof MCConsoleCommandSender || "CONSOLE".equals(name)) { name = Static.getConsoleName(); } - if (p instanceof MCBlockCommandSender) { + if(p instanceof MCBlockCommandSender || p instanceof MCCommandMinecart) { name = Static.getBlockPrefix() + name; } return new CString(name, t); @@ -107,18 +145,21 @@ public Construct exec(Target t, Environment env, Construct... args) throws Cance @Override public String docs() { - return "string {[playerName]} Returns the full name of the partial Player name specified or the Player running the command otherwise." + return "string {[playerName] | [uuid]} Returns a player's name. If a string is specified, it will attempt" + + " to find a complete match for a partial name. If no string is specified, the current player is" + + " returned. UUIDs are also accepted for this and other functions that apply to online players." + " If the command is being run from the console, then the string '" + Static.getConsoleName() + "' is returned. If the command came from a CommandBlock, the block's name prefixed with " + Static.getBlockPrefix() + " is returned. If the command is coming from elsewhere," + " returns a string chosen by the sender of this command (or null)." - + " Note that most functions won't support console or block names (they'll throw a PlayerOfflineException)," - + " but you can use this to determine where a command is being run from."; + + " Note that most functions won't support console or block names" + + " (they'll throw a PlayerOfflineException), but you can use this to determine where a command is" + + " being run from."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class}; } @Override @@ -127,14 +168,84 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_0_1; + public MSVersion since() { + return MSVersion.V3_0_1; + } + + @Override + public Boolean runAsync() { + return false; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class puuid extends AbstractFunction { + + @Override + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CRENotFoundException.class}; + } + + @Override + public boolean isRestricted() { + return false; } @Override public Boolean runAsync() { return false; } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCOfflinePlayer pl = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); + boolean dashless = false; + if(args.length >= 1) { + try { + pl = Static.GetPlayer(args[0], t); + } catch (ConfigRuntimeException cre) { + pl = Static.GetUser(args[0], t); + } + } + if(args.length == 2) { + dashless = ArgumentValidation.getBoolean(args[1], t); + } + if(pl == null) { + throw new CREPlayerOfflineException("No matching player could be found.", t); + } + UUID uuid = pl.getUniqueID(); + if(uuid == null) { + throw new CRENotFoundException( + "Could not find the UUID of the player (are you running in cmdline mode?)", t); + } + String uuidStr = uuid.toString(); + return new CString(dashless ? uuidStr.replace("-", "") : uuidStr, t); + } + + @Override + public String getName() { + return "puuid"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{0, 1, 2}; + } + + @Override + public String docs() { + return "string {[player], [dashless]} Returns the UUID of the current player or the specified player." + + " If dashless is set to true, the returned UUID format will not have any dashes." + + " This will attempt to find an offline player, but if that also fails," + + " a PlayerOfflineException will be thrown. ---- It is not recommended to give this user input." + + " If the player is offline and hasn't visited the server recently (so that they're not in the" + + " user cache), the server may block the main thread with an HTTP request to Mojang's servers."; + } + + @Override + public Version since() { + return MSVersion.V3_3_1; + } } @api(environments = {CommandHelperEnvironment.class}) @@ -151,19 +262,21 @@ public Integer[] numArgs() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { CArray players = new CArray(t); - if (args.length == 0) { - for (MCPlayer player : Static.getServer().getOnlinePlayers()) { - players.push(new CString(player.getName(), t)); + if(args.length == 0) { + for(MCPlayer player : Static.getServer().getOnlinePlayers()) { + players.push(new CString(player.getName(), t), t); } } else { MCWorld world = Static.getServer().getWorld(args[0].val()); - if (world == null) { - throw new ConfigRuntimeException("Unknown world: " + args[0].val(), ExceptionType.InvalidWorldException, t); + if(world == null) { + throw new CREInvalidWorldException("Unknown world: " + args[0].val(), t); } - for (MCPlayer player : world.getPlayers()) { - players.push(new CString(player.getName(), t)); + for(MCPlayer player : world.getPlayers()) { + if(player.isOnline()) { + players.push(new CString(player.getName(), t), t); + } } } return players; @@ -171,12 +284,28 @@ public Construct exec(Target t, Environment env, Construct... args) throws Cance @Override public String docs() { - return "array {[world]} Returns an array of all the player names of all the online players on the server, if world is given only the name of the players in this world will be returned."; + return "array {[world]} Returns an array of all the player names of all the online players on the server." + + " If world is given only the name of the players in this world will be returned."; + } + + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("Demonstrates retrieving a list of all online players, in every dimension.", + "all_players()", + "{Dinnerbone, jeb_, kingbdogz}"), + new ExampleScript("Demonstrates retrieving a list of all online players, but only in the nether.", + "all_players('world_nether')", + "{kingbdogz}"), + new ExampleScript("Demonstrates attempting to retrieve players from a non-existent dimension.", + "all_players('does_not_exist')", + "(Throws InvalidWorldException: Unknown world: does_not_exist)"), + }; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException}; + public Class[] thrown() { + return new Class[]{CREInvalidWorldException.class}; } @Override @@ -185,8 +314,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_0_1; + public MSVersion since() { + return MSVersion.V3_0_1; } @Override @@ -209,7 +338,7 @@ public Integer[] numArgs() { } boolean inRadius(MCPlayer player, double dist, MCLocation loc) { - if (!(player.getWorld().equals(loc.getWorld()))) { + if(!(player.getWorld().equals(loc.getWorld()))) { return false; } @@ -226,40 +355,35 @@ boolean inRadius(MCPlayer player, double dist, MCLocation loc) { + (y1 - y2) * (y1 - y2) + (z1 - z2) * (z1 - z2)); - if (distance <= dist) { - return true; - } - - return false; + return distance <= dist; } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { Collection pa = Static.getServer().getOnlinePlayers(); MCPlayer p = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); MCLocation loc; double dist; - if (args.length == 1) { - dist = Static.getDouble(args[0], t); + if(args.length == 1) { + dist = ArgumentValidation.getDouble(args[0], t); Static.AssertPlayerNonNull(p, t); loc = p.getLocation(); } else { - if (!(args[0] instanceof CArray)) { - throw new ConfigRuntimeException("Expecting an array at parameter 1 of players_in_radius", - ExceptionType.CastException, t); + if(!(args[0].isInstanceOf(CArray.TYPE))) { + throw new CRECastException("Expecting an array at parameter 1 of players_in_radius", t); } loc = ObjectGenerator.GetGenerator().location(args[0], p != null ? p.getWorld() : null, t); - dist = Static.getDouble(args[1], t); + dist = ArgumentValidation.getDouble(args[1], t); } CArray sa = new CArray(t); - for (MCPlayer pa1 : pa) { - if (inRadius(pa1, dist, loc)) { - sa.push(new CString(pa1.getName(), t)); + for(MCPlayer pa1 : pa) { + if(inRadius(pa1, dist, loc)) { + sa.push(new CString(pa1.getName(), t), t); } } @@ -268,12 +392,13 @@ public Construct exec(Target t, Environment env, Construct... args) throws Cance @Override public String docs() { - return "array {[location array], distance} Returns an array of all the player names of all the online players within the given radius"; + return "array {[locationArray], distance} Returns an array of all the players within the given radius."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException, ExceptionType.FormatException, ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CRECastException.class, CREFormatException.class, + CREPlayerOfflineException.class, CRENotFoundException.class}; } @Override @@ -282,8 +407,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } @Override @@ -306,28 +431,34 @@ public Integer[] numArgs() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { MCPlayer p; - if (args.length == 0) { + if(args.length == 0) { p = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); Static.AssertPlayerNonNull(p, t); } else { p = Static.GetPlayer(args[0], t); } MCLocation location = p.getLocation(); + if(location == null) { + throw new CRENotFoundException( + "Could not find the location of the player (are you running in cmdline mode?)", t); + } location.setY(location.getY() - 1); return ObjectGenerator.GetGenerator().location(location); } @Override public String docs() { - return "array {[playerName]} Returns an array of x, y, z coords of the player specified, or the player running the command otherwise. Note that the y coordinate is" - + " in relation to the block the player is standing on. The array returned will also include the player's world."; + return "array {[playerName]} Returns a location array of the coordinates of the player specified," + + " or the player running the command if no player is specified." + + " Note that unlike entity_loc() the y coordinate will be for the block the player is standing on," + + " which is one meter lower. The array returned also includes the player's world, yaw and pitch."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CRENotFoundException.class}; } @Override @@ -336,8 +467,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_0_1; + public MSVersion since() { + return MSVersion.V3_0_1; } @Override @@ -361,15 +492,17 @@ public Integer[] numArgs() { @Override public String docs() { - return "boolean {[player], locationArray | [player], x, y, z} Sets the location of the player to the specified coordinates. If the coordinates" - + " are not valid, or the player was otherwise prevented from moving, false is returned, otherwise true. If player is omitted, " - + " the current player is used. Note that 1 is automatically added to the y component, which means that sending a player to" - + " x, y, z coordinates shown with F3 will work as expected, instead of getting them stuck inside the floor. "; + return "boolean {[player], locationArray | [player], x, y, z} Sets the location of the player to the" + + " specified coordinates. If the coordinates are not valid, or the player was otherwise prevented" + + " from teleporting, false is returned, otherwise true. On Paper 1.19.3+, passengers do not" + + " automatically prevent teleports. If player is omitted, the current player is used. Important:" + + " unlike {{function|set_entity_loc}}, 1.0 is automatically added to the y coordinate."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException, ExceptionType.LengthException, ExceptionType.PlayerOfflineException, ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CRECastException.class, CRELengthException.class, CREPlayerOfflineException.class, + CREFormatException.class, CREInvalidWorldException.class, CREIllegalArgumentException.class}; } @Override @@ -378,8 +511,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_1_0; + public MSVersion since() { + return MSVersion.V3_1_0; } @Override @@ -388,64 +521,62 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { - MCCommandSender p = env.getEnv(CommandHelperEnvironment.class).GetCommandSender(); - String MCPlayer = null; - double x; - double y; - double z; - MCPlayer m = null; - MCLocation l = null; - if (args.length == 1) { - if (args[0] instanceof CArray) { - CArray ca = (CArray) args[0]; - l = ObjectGenerator.GetGenerator().location(ca, (p instanceof MCPlayer ? ((MCPlayer) p).getWorld() : null), t); - x = Static.getNumber(ca.get(0, t), t); - y = Static.getNumber(ca.get(1, t), t); - z = Static.getNumber(ca.get(2, t), t); - if (p instanceof MCPlayer) { - m = ((MCPlayer) p); - } + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCPlayer p = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); + MCLocation l; + if(args.length <= 2) { + if(!(args[args.length - 1].isInstanceOf(CArray.TYPE))) { + throw new CRECastException("Expecting an array at parameter " + args.length + " of set_ploc", t); + } + CArray ca = (CArray) args[args.length - 1]; + if(args.length == 2) { + p = Static.GetPlayer(args[0], t); } else { - throw new ConfigRuntimeException("Expecting an array at parameter 1 of set_ploc", - ExceptionType.CastException, t); + Static.AssertPlayerNonNull(p, t); + } + + l = ObjectGenerator.GetGenerator().location(ca, p.getWorld(), t); + + // set yaw/pitch to current player values if not given + MCLocation ploc = p.getLocation(); + if(ca.isAssociative()) { + if(!(ca.containsKey("yaw"))) { + l.setYaw(ploc.getYaw()); + } + if(!(ca.containsKey("pitch"))) { + l.setPitch(ploc.getPitch()); + } + } else if(ca.size() < 5) { + l.setYaw(ploc.getYaw()); + l.setPitch(ploc.getPitch()); } - } else if (args.length == 2) { - if (args[1] instanceof CArray) { - CArray ca = (CArray) args[1]; - MCPlayer = args[0].val(); - l = ObjectGenerator.GetGenerator().location(ca, Static.GetPlayer(MCPlayer, t).getWorld(), t); - x = l.getX(); - y = l.getY(); - z = l.getZ(); + } else { + if(args.length == 4) { + p = Static.GetPlayer(args[0], t); } else { - throw new ConfigRuntimeException("Expecting parameter 2 to be an array in set_ploc", - ExceptionType.CastException, t); + Static.AssertPlayerNonNull(p, t); } - } else if (args.length == 3) { - if (p instanceof MCPlayer) { - m = (MCPlayer) p; + + double x = ArgumentValidation.getNumber(args[args.length - 3], t); + double y = ArgumentValidation.getNumber(args[args.length - 2], t); + double z = ArgumentValidation.getNumber(args[args.length - 1], t); + float yaw = 0; + float pitch = 0; + MCLocation ploc = p.getLocation(); + if(ploc != null) { + yaw = ploc.getYaw(); + pitch = ploc.getPitch(); } - x = Static.getNumber(args[0], t); - y = Static.getNumber(args[1], t); - z = Static.getNumber(args[2], t); - l = m.getLocation(); - } else { - MCPlayer = args[0].val(); - x = Static.getNumber(args[1], t); - y = Static.getNumber(args[2], t); - z = Static.getNumber(args[3], t); - l = StaticLayer.GetLocation(Static.GetPlayer(MCPlayer, t).getWorld(), x, y, z, 0, 0); - } - if (m == null && MCPlayer != null) { - m = Static.GetPlayer(MCPlayer, t); + l = StaticLayer.GetLocation(p.getWorld(), x, y, z, yaw, pitch); } - Static.AssertPlayerNonNull(m, t); - if (!l.getWorld().exists()) { - throw new ConfigRuntimeException("The world specified does not exist.", ExceptionType.InvalidWorldException, t); + + l.add(0, 1, 0); + try { + return CBoolean.get(p.teleport(l)); + } catch (IllegalArgumentException ex) { + throw new CREIllegalArgumentException(ex.getMessage(), t); } - return CBoolean.get(m.teleport(StaticLayer.GetLocation(l.getWorld(), x, y + 1, z, m.getLocation().getYaw(), m.getLocation().getPitch()))); } } @@ -464,17 +595,19 @@ public Integer[] numArgs() { @Override public String docs() { - return "array {[player], [array]} Returns an array with the (x, y, z, world) coordinates of the block the player has highlighted" - + " in their crosshairs. If player is omitted, the current player is used. If the block is too far, a" - + " RangeException is thrown. An array of ids to be considered transparent can be supplied, otherwise" - + " only air will be considered transparent. Providing an empty array will cause air to be considered" - + " a potential target, allowing a way to get the block containing the player's head."; + return "array {[player], [array]} Returns a location array with the coordinates of the block the player has" + + " highlighted in their crosshairs. If player is omitted, the current player is used." + + " If the block is too far, a RangeException is thrown. An array of block types to be considered" + + " transparent can be supplied, otherwise only air will be considered transparent." + + " Providing an empty array will cause air to be considered a potential target, allowing a way to" + + " get the block containing the player's head. On paper servers, this teleports passengers of" + + " the player as well."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException, ExceptionType.RangeException, - ExceptionType.FormatException, ExceptionType.CastException, ExceptionType.PluginInternalException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CRERangeException.class, + CREFormatException.class, CRECastException.class, CREPluginInternalException.class}; } @Override @@ -483,47 +616,62 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_0_2; + public MSVersion since() { + return MSVersion.V3_0_2; } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { MCPlayer p = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); - HashSet trans = null; - if (args.length == 1) { - if(args[0] instanceof CArray) { - CArray ta = (CArray) args[0]; - trans = new HashSet(); - for (int i=0; i < ta.size(); i++) { - trans.add(Static.getInt16(ta.get(i, t), t)); - } + HashSet trans = null; + int transparentIndex = -1; + if(args.length == 1) { + if(args[0].isInstanceOf(CArray.TYPE)) { + transparentIndex = 0; } else { p = Static.GetPlayer(args[0], t); } - } else if (args.length == 2) { + } else if(args.length == 2) { p = Static.GetPlayer(args[0], t); - if(args[1] instanceof CArray) { - CArray ta = (CArray) args[1]; - trans = new HashSet(); - for (int i=0; i < ta.size(); i++) { - trans.add(Static.getInt16(ta.get(i, t), t)); - } + if(args[1].isInstanceOf(CArray.TYPE)) { + transparentIndex = 1; } else { - throw new Exceptions.FormatException("An array was expected for argument 2 but received " + args[1], t); + throw new CREFormatException("An array was expected for argument 2 but received " + args[1], t); + } + } + if(transparentIndex >= 0) { + CArray ta = (CArray) args[transparentIndex]; + trans = new HashSet<>(); + for(Mixed mat : ta.asList()) { + MCMaterial material = StaticLayer.GetMaterial(mat.val()); + if(material != null) { + trans.add(StaticLayer.GetMaterial(mat.val())); + continue; + } + try { + material = StaticLayer.GetMaterialFromLegacy(ArgumentValidation.getInt16(mat, t), 0); + if(material != null) { + MSLog.GetLogger().w(MSLog.Tags.DEPRECATION, "The id \"" + mat.val() + "\" is deprecated." + + " Converted to \"" + material.getName() + "\"", t); + trans.add(material); + continue; + } + } catch (CRECastException ex) { + // ignore and throw a more specific message + } + throw new CREFormatException("Could not find a material by the name \"" + mat.val() + "\"", t); } } Static.AssertPlayerNonNull(p, t); MCBlock b; try { - b = p.getTargetBlock(trans, 10000, false); + b = p.getTargetBlock(trans, 512); } catch (IllegalStateException ise) { - throw new ConfigRuntimeException("The server's method of finding the target block has failed." - + " There is nothing that can be done about this except standing somewhere else.", - ExceptionType.PluginInternalException, t); + throw new CREPluginInternalException("The server's method of finding the target block has failed." + + " There is nothing that can be done about this except standing somewhere else.", t); } - if (b == null) { - throw new ConfigRuntimeException("No block in sight, or block too far", ExceptionType.RangeException, t); + if(b == null) { + throw new CRERangeException("No block in sight, or block too far", t); } else { return ObjectGenerator.GetGenerator().location(b.getLocation(), false); } @@ -537,23 +685,28 @@ public Boolean runAsync() { @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ - new ExampleScript("Demonstrates finding a non-air block", "msg(pcursor())", "{0: -127, 1: 75, 2: 798, 3: world, x: -127, y: 75, z: 798, world: world}"), - new ExampleScript("Demonstrates looking above the skyline", "msg(pcursor())", - "(Throws RangeException: No block in sight, or block too far)"), - new ExampleScript("Demonstrates getting your target while ignoring torches and bedrock", - "msg(pcursor(array(50, 7)))", "{0: -127, 1: 75, 2: 798, 3: world, x: -127, y: 75, z: 798, world: world}"), - new ExampleScript("Demonstrates getting Notch's target while ignoring air, water, and lava", - "msg(pcursor('Notch', array(0, 8, 9, 10, 11)))", "{0: -127, 1: 75, 2: 798, 3: world, x: -127, y: 75, z: 798, world: world}") + new ExampleScript("Demonstrates finding a non-air block", + "msg(pcursor())", + "{0: -127, 1: 75, 2: 798, 3: world, x: -127, y: 75, z: 798, world: world}"), + new ExampleScript("Demonstrates looking above the skyline", + "msg(pcursor())", + "(Throws RangeException: No block in sight, or block too far)"), + new ExampleScript("Demonstrates getting your target while ignoring torches and bedrock", + "msg(pcursor(array(50, 7)))", + "{0: -127, 1: 75, 2: 798, 3: world, x: -127, y: 75, z: 798, world: world}"), + new ExampleScript("Demonstrates getting Notch's target while ignoring air, water, and lava", + "msg(pcursor('Notch', array(0, 8, 9, 10, 11)))", + "{0: -127, 1: 75, 2: 798, 3: world, x: -127, y: 75, z: 798, world: world}") }; } } - @api + @api(environments = {CommandHelperEnvironment.class}) public static class ptarget_space extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CRERangeException.class}; } @Override @@ -567,13 +720,17 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCPlayer p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); - if(args.length > 0){ + if(args.length > 0) { p = Static.GetPlayer(args[0], t); } Static.AssertPlayerNonNull(p, t); - return ObjectGenerator.GetGenerator().location(p.getLastTwoTargetBlocks(null, 10000).get(0).getLocation(), false); + MCBlock b = p.getTargetSpace(512); + if(b == null) { + throw new CRERangeException("No block in sight, or block too far", t); + } + return ObjectGenerator.GetGenerator().location(b.getLocation(), false); } @Override @@ -588,13 +745,13 @@ public Integer[] numArgs() { @Override public String docs() { - return "{[player]} Returns the \"target space\" that the player is currently targetting. This is the \"space\" where" - + " if they placed a block (and were close enough), it would end up going."; + return "array {[player]} Returns a location array of the space that the player is currently looking at." + + " This is the space where if they placed a block (and were close enough), it would end up going."; } @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } } @@ -613,13 +770,13 @@ public Integer[] numArgs() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { MCCommandSender p = env.getEnv(CommandHelperEnvironment.class).GetCommandSender(); MCPlayer m = null; - if (args.length == 1) { + if(args.length == 1) { m = Static.GetPlayer(args[0], t); } else { - if (p instanceof MCPlayer) { + if(p instanceof MCPlayer) { m = (MCPlayer) p; } } @@ -630,12 +787,12 @@ public Construct exec(Target t, Environment env, Construct... args) throws Cance @Override public String docs() { - return "void {[playerName]} Kills the specified player, or the current player if it is omitted"; + return "void {[playerName]} Kills the specified player, or the current player if a name is omitted."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class}; } @Override @@ -644,8 +801,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_0_1; + public MSVersion since() { + return MSVersion.V3_0_1; } @Override @@ -654,30 +811,6 @@ public Boolean runAsync() { } } - @api(environments = {CommandHelperEnvironment.class}) - @hide("Deprecated in favor of pkill") - @Deprecated - public static class kill extends pkill implements Optimizable { - - @Override - public String getName() { - return "kill"; - } - - @Override - public Set optimizationOptions() { - return EnumSet.of(OptimizationOption.OPTIMIZE_DYNAMIC); - } - - @Override - public ParseTree optimizeDynamic(Target t, List children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { - CHLog.GetLogger().Log(CHLog.Tags.DEPRECATION, LogLevel.WARNING, "kill is deprecated, in favor of the more conventionally named pkill. Please change" - + " all usages of kill() to pkill() ", t); - return null; - } - - } - @api(environments = {CommandHelperEnvironment.class, GlobalEnv.class}) public static class pgroup extends AbstractFunction { @@ -692,36 +825,33 @@ public Integer[] numArgs() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { - MCCommandSender p = env.getEnv(CommandHelperEnvironment.class).GetCommandSender(); - MCPlayer m = null; - if (args.length == 0) { - if (p instanceof MCPlayer) { - m = (MCPlayer) p; - } + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCCommandSender sender; + if(args.length == 0) { + sender = env.getEnv(CommandHelperEnvironment.class).GetCommandSender(); } else { - m = Static.GetPlayer(args[0], t); + sender = Static.GetCommandSender(args[0].val(), t); } - Static.AssertPlayerNonNull(m, t); - - String[] sa = env.getEnv(GlobalEnv.class).GetPermissionsResolver().getGroups(m.getName()); - Construct[] ca = new Construct[sa.length]; - for (int i = 0; i < sa.length; i++) { - ca[i] = new CString(sa[i], t); + CArray ret = new CArray(t); + if(sender != null) { + for(String group : sender.getGroups()) { + ret.push(new CString(group, t), t); + } } - CArray a = new CArray(t, ca); - return a; + return ret; } @Override public String docs() { - return "array {[playerName]} Returns an array of the groups a player is in. If playerName is omitted, the current player is used."; + return "array {[playerName]} Returns an array of the groups a player is in. If playerName is omitted," + + " the current player is used. This relies on \"group.groupname\" permission nodes in your" + + " permissions plugin. Otherwise an extension is required to get the groups from the plugin."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class}; } @Override @@ -730,8 +860,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_0_1; + public MSVersion since() { + return MSVersion.V3_0_1; } @Override @@ -755,28 +885,41 @@ public Integer[] numArgs() { @Override public String docs() { - return "mixed {[pName], [value]} Returns various information about the player specified, or the current player if no argument was given. ---- " - + "If value is set, it should be an integer of one of the following indexes, and only that information for that index" - + " will be returned. Otherwise if value is not specified (or is -1), it returns an array of" - + " information with the following pieces of information in the specified index: " - + "
  • 0 - player's name; This will return the player's exact name, " - + " even if called with a partial match.
  • 1 - player's location; an array of the player's xyz coordinates
  • 2 - player's cursor; an array of the " - + "location of the player's cursor, or null if the block is out of sight.
  • 3 - player's IP; Returns the IP address of this player.
  • 4 - Display name; The name that is used when the" - + " player's name is displayed on screen typically.
  • 5 - player's health; Gets the current health of the player, which will be an int" - + " from 0-20.
  • 6 - Item in hand; The value returned by this will be similar to the value returned by get_block_at()
  • 7 - " - + "World name; Gets the name of the world this player is in.
  • 8 - Is Op; true or false if this player is an op.
  • 9 - player groups;" - + " An array of the permissions groups the player is in.
  • 10 - The player's hostname (or IP if a hostname can't be found)
  • " - + "
  • 11 - Is sneaking?
  • 12 - Host; The host the player connected to.
  • " - + "
  • 13 - Player's current entity id
  • 14 - Is player in a vehicle? Returns true or false.
  • " - + "
  • 15 - The slot number of the player's current hand.
  • " - + "
  • 16 - Is sleeping?
  • 17 - Is blocking?
  • 18 - Is flying?
  • 19 - Is sprinting?
  • " - + "
  • 20 - Player UUID" - + "
"; - } - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException, ExceptionType.RangeException, ExceptionType.CastException}; + return "mixed {[player], [value]} Returns various information about the player specified, or the current" + + " player if no argument was given. ----" + + " If value is set, it should be an integer of one of the following indexes, and only that" + + " information for that index will be returned. Otherwise if value is not specified (or is -1), it" + + " returns an array of values with the following pieces of information in the specified index:" + + "
    " + + "
  • 0 - Player's name; This will return the player's exact name," + + " even if called with a partial match.
  • " + + "
  • 1 - Player's location; a location array of the player's coordinates
  • " + + "
  • 2 - Player's cursor; a location array of the block the player is looking at," + + " or null if no block is in sight.
  • " + + "
  • 3 - Player's IP; Returns the IP address of this player.
  • " + + "
  • 4 - Display name; The name that is typically used when displayed on screen.
  • " + + "
  • 5 - Player's health; The current health of the player, which will be an int from 0-20.
  • " + + "
  • 6 - Item in hand; The type of item in their main hand.
  • " + + "
  • 7 - World name; Gets the name of the world this player is in.
  • " + + "
  • 8 - Is Op; true or false if this player is an op.
  • " + + "
  • 9 - Player groups; An array of the groups the player is in, by permission nodes.
  • " + + "
  • 10 - The player's hostname (or IP if a hostname can't be found)
  • " + + "
  • 11 - Is sneaking?
  • 12 - Host; The host the player connected to.
  • " + + "
  • 13 - Player UUID; (deprecated for index 20, but exists for backwards compatibility.)
  • " + + "
  • 14 - Is player in a vehicle? Returns true or false.
  • " + + "
  • 15 - Held Slot; The slot number of the player's current hand.
  • " + + "
  • 16 - Is sleeping?
  • " + + "
  • 17 - Is blocking?
  • " + + "
  • 18 - Is flying?
  • " + + "
  • 19 - Is sprinting?
  • " + + "
  • 20 - Player UUID; The unique identifier for this player's account as returned by puuid()." + + "
"; + } + + @Override + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CRERangeException.class, + CRECastException.class, CRENotFoundException.class}; } @Override @@ -785,8 +928,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_1_0; + public MSVersion since() { + return MSVersion.V3_1_0; } @Override @@ -795,40 +938,45 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { MCCommandSender m = env.getEnv(CommandHelperEnvironment.class).GetCommandSender(); - String player = ""; - int index = -1; - if (args.length == 0) { - player = (m instanceof MCPlayer ? ((MCPlayer) m).getName() : null); + String player; + int index; + if(args.length == 0) { + player = m instanceof MCPlayer ? m.getName() : null; index = -1; - } else if (args.length == 1) { + } else if(args.length == 1) { player = args[0].val(); index = -1; } else { player = args[0].val(); - index = Static.getInt32(args[1], t); + index = ArgumentValidation.getInt32(args[1], t); } - MCPlayer p = Static.GetPlayer(player, t); Static.AssertPlayerNonNull(p, t); int maxIndex = 20; - if (index < -1 || index > maxIndex) { - throw new ConfigRuntimeException(this.getName() + " expects the index to be between -1 and " + maxIndex, - ExceptionType.RangeException, t); + if(index < -1 || index > maxIndex) { + throw new CRERangeException(this.getName() + " expects the index to be between -1 and " + maxIndex, t); } - ArrayList retVals = new ArrayList(); - if (index == 0 || index == -1) { + ArrayList retVals = new ArrayList<>(); + if(index == 0 || index == -1) { //MCPlayer name retVals.add(new CString(p.getName(), t)); } - if (index == 1 || index == -1) { + if(index == 1 || index == -1) { //MCPlayer location - retVals.add(new CArray(t, new CDouble(p.getLocation().getX(), t), - new CDouble(p.getLocation().getY() - 1, t), new CDouble(p.getLocation().getZ(), t))); + MCLocation loc = p.getLocation(); + if(loc == null) { + throw new CRENotFoundException( + "Could not find the location of the player (are you running in cmdline mode?)", t); + } + retVals.add(new CArray(t, + new CDouble(loc.getX(), t), + new CDouble(loc.getY() - 1, t), + new CDouble(loc.getZ(), t))); } - if (index == 2 || index == -1) { + if(index == 2 || index == -1) { //MCPlayer cursor MCBlock b; try { @@ -836,13 +984,13 @@ public Construct exec(Target t, Environment env, Construct... args) throws Cance } catch (IllegalStateException ise) { b = null; } - if (b == null) { + if(b == null) { retVals.add(CNull.NULL); } else { retVals.add(new CArray(t, new CInt(b.getX(), t), new CInt(b.getY(), t), new CInt(b.getZ(), t))); } } - if (index == 3 || index == -1) { + if(index == 3 || index == -1) { //MCPlayer IP String add; try { @@ -853,48 +1001,36 @@ public Construct exec(Target t, Environment env, Construct... args) throws Cance retVals.add(new CString(add, t)); } - if (index == 4 || index == -1) { + if(index == 4 || index == -1) { //Display name retVals.add(new CString(p.getDisplayName(), t)); } - if (index == 5 || index == -1) { + if(index == 5 || index == -1) { //MCPlayer health retVals.add(new CDouble(p.getHealth(), t)); } - if (index == 6 || index == -1) { + if(index == 6 || index == -1) { //Item in hand - MCItemStack is = p.getItemInHand(); - int data; - if (is.getTypeId() < 256) { - if (is.getData() != null) { - data = is.getData().getData(); - } else { - data = 0; - } - } else { - data = is.getDurability(); - } - retVals.add(new CString(is.getTypeId() + ":" + data, t)); + MCItemStack is = p.getInventory().getItemInMainHand(); + retVals.add(new CString(is.getType().getName(), t)); } - if (index == 7 || index == -1) { + if(index == 7 || index == -1) { //World name retVals.add(new CString(p.getWorld().getName(), t)); } - if (index == 8 || index == -1) { + if(index == 8 || index == -1) { //Is op retVals.add(CBoolean.get(p.isOp())); } - if (index == 9 || index == -1) { + if(index == 9 || index == -1) { //MCPlayer groups - String[] sa = env.getEnv(GlobalEnv.class).GetPermissionsResolver().getGroups(p.getName()); - Construct[] ca = new Construct[sa.length]; - for (int i = 0; i < sa.length; i++) { - ca[i] = new CString(sa[i], t); + CArray a = new CArray(t); + for(String group : p.getGroups()) { + a.push(new CString(group, t), t); } - CArray a = new CArray(t, ca); retVals.add(a); } - if (index == 10 || index == -1) { + if(index == 10 || index == -1) { String hostname; try { hostname = p.getAddress().getAddress().getHostAddress(); @@ -902,48 +1038,48 @@ public Construct exec(Target t, Environment env, Construct... args) throws Cance hostname = ""; } - if (CommandHelperPlugin.hostnameLookupCache.containsKey(p.getName())) { + if(CommandHelperPlugin.hostnameLookupCache.containsKey(p.getName())) { hostname = CommandHelperPlugin.hostnameLookupCache.get(p.getName()); } retVals.add(new CString(hostname, t)); } - if (index == 11 || index == -1) { + if(index == 11 || index == -1) { retVals.add(CBoolean.get(p.isSneaking())); } - if (index == 12 || index == -1) { + if(index == 12 || index == -1) { retVals.add(new CString(p.getHost(), t)); } - if (index == 13 || index == -1) { - retVals.add(new CInt(p.getEntityId(), t)); + if(index == 13 || index == -1) { + retVals.add(new CString(p.getUniqueId().toString(), t)); } - if (index == 14 || index == -1) { + if(index == 14 || index == -1) { retVals.add(CBoolean.get(p.isInsideVehicle())); } - if (index == 15 || index == -1) { + if(index == 15 || index == -1) { retVals.add(new CInt(p.getInventory().getHeldItemSlot(), t)); } - if (index == 16 || index == -1) { + if(index == 16 || index == -1) { retVals.add(CBoolean.get(p.isSleeping())); } - if (index == 17 || index == -1) { + if(index == 17 || index == -1) { retVals.add(CBoolean.get(p.isBlocking())); } - if (index == 18 || index == -1) { + if(index == 18 || index == -1) { retVals.add(CBoolean.get(p.isFlying())); } - if (index == 19 || index == -1) { + if(index == 19 || index == -1) { retVals.add(CBoolean.get(p.isSprinting())); } - if(index == 20 || index == -1){ + if(index == 20 || index == -1) { retVals.add(new CString(p.getUniqueId().toString(), t)); } - if (retVals.size() == 1) { + if(retVals.size() == 1) { return retVals.get(0); } else { CArray ca = new CArray(t); - for (Construct c : retVals) { - ca.push(c); + for(Mixed c : retVals) { + ca.push(c, t); } return ca; } @@ -965,12 +1101,13 @@ public Integer[] numArgs() { @Override public String docs() { - return "string {[playerName]} Gets the world of the player specified, or the current player, if playerName isn't specified."; + return "string {[playerName]} Gets the world of the player specified," + + " or the current player if playerName isn't specified."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class}; } @Override @@ -979,21 +1116,21 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_1_0; + public MSVersion since() { + return MSVersion.V3_1_0; } @Override public Boolean runAsync() { - return true; + return false; } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { MCCommandSender p = env.getEnv(CommandHelperEnvironment.class).GetCommandSender(); MCPlayer m = null; - if (args.length == 0) { - if (p instanceof MCPlayer) { + if(args.length == 0) { + if(p instanceof MCPlayer) { m = (MCPlayer) p; } } else { @@ -1004,30 +1141,6 @@ public Construct exec(Target t, Environment env, Construct... args) throws Cance } } - @api - @hide("Deprecated in favor of pkick") - @Deprecated - public static class kick extends pkick implements Optimizable { - - @Override - public String getName() { - return "kick"; - } - - @Override - public Set optimizationOptions() { - return EnumSet.of(OptimizationOption.OPTIMIZE_DYNAMIC); - } - - @Override - public ParseTree optimizeDynamic(Target t, List children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { - CHLog.GetLogger().Log(CHLog.Tags.DEPRECATION, LogLevel.WARNING, "kick is deprecated, in favor of the more conventionally named pkick. Please change" - + " all usages of kick() to pkick() ", t); - return null; - } - - } - @api(environments = {CommandHelperEnvironment.class}) public static class pkick extends AbstractFunction { @@ -1043,13 +1156,14 @@ public Integer[] numArgs() { @Override public String docs() { - return "void {[playerName], [message]} Kicks the specified player, with an optional message. If no message is specified, " - + "\"You have been kicked\" is used. If no player is specified, the current player is used, with the default message."; + return "void {[playerName], [message]} Kicks the specified player with an optional message." + + " If no message is specified, \"You have been kicked\" is used." + + " If no player is specified, the current player is used with the default message."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class}; } @Override @@ -1058,8 +1172,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_1_0; + public MSVersion since() { + return MSVersion.V3_1_0; } @Override @@ -1068,19 +1182,19 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { MCCommandSender p = env.getEnv(CommandHelperEnvironment.class).GetCommandSender(); String message = "You have been kicked"; MCPlayer m = null; - if (args.length == 0) { - if (p instanceof MCPlayer) { + if(args.length == 0) { + if(p instanceof MCPlayer) { m = (MCPlayer) p; } } - if (args.length >= 1) { + if(args.length >= 1) { m = Static.GetPlayer(args[0], t); } - if (args.length >= 2) { + if(args.length >= 2) { message = args[1].val(); } MCPlayer ptok = m; @@ -1090,6 +1204,78 @@ public Construct exec(Target t, Environment env, Construct... args) throws Cance } } + @api(environments = {CommandHelperEnvironment.class}) + public static class display_name extends AbstractFunction { + + @Override + public String getName() { + return "display_name"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{0, 1}; + } + + @Override + public String docs() { + return "string {[player]} Returns the display name of the player."; + } + + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript( + "Demonstrates retrieving the display name for the player calling the function.", + "display_name()", + ":The King" + ), + new ExampleScript( + "Demonstrates retrieving the display name for the specified player.", + "display_name('kingbdogz')", + ":The King" + ), + new ExampleScript( + "Demonstrates attempting to retrieve the display name for a non-existent player.", + "display_name('does_not_exist')", + "(Throws PlayerOfflineException: The specified player (does_not_exist) is not online)" + ), + }; + } + + @Override + public Class[] thrown() { + return new Class[]{CRELengthException.class, CREPlayerOfflineException.class}; + } + + @Override + public boolean isRestricted() { + return false; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_2; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCPlayer m; + if(args.length == 0) { + m = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); + Static.AssertPlayerNonNull(m, t); + } else { + m = Static.GetPlayer(args[0].val(), t); + } + return new CString(m.getDisplayName(), t); + } + } + @api(environments = {CommandHelperEnvironment.class}) public static class set_display_name extends AbstractFunction { @@ -1105,14 +1291,35 @@ public Integer[] numArgs() { @Override public String docs() { - return "void {playerName, newDisplayName | newDisplayName} Sets a player's display name. If the second usage is used," - + " it sets the display name of the player running the command. See reset_display_name also. playerName, as well" - + " as all CommandHelper commands expect the player's real name, not their display name."; + return "void {[playerName], newDisplayName} Sets a player's display name. If the first name isn't provided," + + " it sets the display name of the player running the command. See reset_display_name() also." + + " All player functions expect the player's real name, not their display name."; + } + + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript( + "Demonstrates setting a display name for the player calling the function.", + "set_display_name('The King')", + "(The current player will now appear as 'The King' in chat)" + ), + new ExampleScript( + "Demonstrates setting a display name for the specified player.", + "set_display_name('kingbdogz', 'The King')", + "(The player named 'kingbdogz' will now appear as 'The King' in chat)" + ), + new ExampleScript( + "Demonstrates attempting to set the display name for a non-existent player.", + "set_display_name('does_not_exist', 'The King')", + "(Throws PlayerOfflineException: The specified player (does_not_exist) is not online)" + ), + }; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class}; } @Override @@ -1121,8 +1328,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_1_2; + public MSVersion since() { + return MSVersion.V3_1_2; } @Override @@ -1131,21 +1338,18 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { - MCCommandSender p = env.getEnv(CommandHelperEnvironment.class).GetCommandSender(); - MCPlayer MCPlayer = null; + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCPlayer player; String name; - if (args.length == 1) { - if (p instanceof MCPlayer) { - MCPlayer = (MCPlayer) p; - } + if(args.length == 1) { + player = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); + Static.AssertPlayerNonNull(player, t); name = args[0].val(); } else { - MCPlayer = Static.GetPlayer(args[0], t); + player = Static.GetPlayer(args[0], t); name = args[1].val(); } - Static.AssertPlayerNonNull(MCPlayer, t); - MCPlayer.setDisplayName(name); + player.setDisplayName(name); return CVoid.VOID; } } @@ -1165,13 +1369,34 @@ public Integer[] numArgs() { @Override public String docs() { - return "void {[playerName]} Resets a player's display name to their real name. If playerName isn't specified, defaults to the" - + " player running the command."; + return "void {[playerName]} Resets a player's display name to their real name." + + " If playerName isn't specified, defaults to the player running the command."; + } + + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript( + "Demonstrates resetting a display name for the player calling the function.", + "reset_display_name()", + "(The current player will have their display name removed and their real Minecraft username will be shown.)" + ), + new ExampleScript( + "Demonstrates resetting a display name for the specified player.", + "reset_display_name('kingbdogz')", + "(The player named 'kingbdogz' will have their display name removed and their real Minecraft username will be shown.)" + ), + new ExampleScript( + "Demonstrates attempting to reset the display name for a non-existent player. Note that you must use the player's real name, not their current display name.", + "reset_display_name('The King')", + "(Throws PlayerOfflineException: The specified player (The King) is not online)" + ), + }; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class}; } @Override @@ -1180,8 +1405,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_1_2; + public MSVersion since() { + return MSVersion.V3_1_2; } @Override @@ -1190,18 +1415,15 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { - MCCommandSender p = env.getEnv(CommandHelperEnvironment.class).GetCommandSender(); - MCPlayer MCPlayer = null; - if (args.length == 0) { - if (p instanceof MCPlayer) { - MCPlayer = (MCPlayer) p; - } + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCPlayer player; + if(args.length == 0) { + player = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); + Static.AssertPlayerNonNull(player, t); } else { - MCPlayer = Static.GetPlayer(args[0], t); + player = Static.GetPlayer(args[0], t); } - Static.AssertPlayerNonNull(MCPlayer, t); - MCPlayer.setDisplayName(MCPlayer.getName()); + player.setDisplayName(player.getName()); return CVoid.VOID; } } @@ -1221,21 +1443,30 @@ public Integer[] numArgs() { @Override public String docs() { - return "mixed {F | yaw, pitch | player, F | player, yaw, pitch | player | <none>} Sets the direction the player is facing. ---- When using the first variation, expects an integer 0-3, which will" - + " set the direction the player faces using their existing pitch (up and down) but sets their yaw (left and right) to one of the" - + " cardinal directions, as follows: 0 - West, 1 - South, 2 - East, 3 - North, which corresponds to the directions given by F when" - + " viewed with F3. In the second variation, specific yaw and pitches can be provided. If the player is not specified, the current player" - + " is used. If just the player is specified, that player's yaw and pitch are returned as an array, or if no arguments are given, the" - + " player running the command's yaw and pitch are returned as an array. The function returns void when setting the values. (Note that while this" - + " function looks like it has ambiguous arguments, players cannot be named numbers.) A note on numbers: The values returned by the getter will always be" - + " as such: pitch will always be a number between 90 and -90, with -90 being the player looking up, and 90 being the player looking down. Yaw will" - + " always be a number between 0 and 359.9~. When using it as a setter, pitch must be a number between -90 and 90, and yaw may be any number." - + " If the number given is not between 0 and 359.9~, it will be normalized first. 0 is dead west, 90 is north, etc."; + return "mixed {F | yaw, pitch | player, F | player, yaw, pitch | player | <none>}" + + " Gets or sets the direction the player is facing." + + " ---- When using the first variation, expects an integer 0-3, which will set the direction the" + + " player faces using their existing pitch (up and down) but sets their yaw (left and right) to" + + " one of the cardinal directions, as follows: 0 - South, 1 - West, 2 - North, 3 - East, which" + + " corresponds to the directions given by F when viewed with F3." + + " In the second variation, specific yaw and pitches can be provided." + + " If the player is not specified, the current player is used." + + " If just the player is specified, that player's yaw and pitch are returned as an array," + + " or if no arguments are given, the current player's yaw and pitch are returned as an array." + + " The function returns void when setting the values. (Note that while this" + + " function looks like it has ambiguous arguments, players cannot be named numbers.)" + + " A note on numbers: The values returned by the getter will always be as such:" + + " pitch will always be a number between 90 and -90, with -90 being the player looking up," + + " and 90 being the player looking down. Yaw will always be a number between 0 and 359.9~." + + " When setting the facing, pitch must be a number between -90 and 90, and yaw may be any number." + + " If the number given is not between 0 and 359.9~, it will be normalized first." + + " 0 is due south, 90 is west, etc."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException, ExceptionType.RangeException, ExceptionType.CastException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CRERangeException.class, + CRECastException.class, CRENotFoundException.class}; } @Override @@ -1244,8 +1475,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_1_3; + public MSVersion since() { + return MSVersion.V3_1_3; } @Override @@ -1254,30 +1485,29 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { MCCommandSender p = env.getEnv(CommandHelperEnvironment.class).GetCommandSender(); //Getter - if (args.length == 0 || args.length == 1) { + if(args.length == 0 || args.length == 1) { MCLocation l = null; - if (args.length == 0) { - if (p instanceof MCPlayer) { + if(args.length == 0) { + if(p instanceof MCPlayer) { l = ((MCPlayer) p).getLocation(); } - } else if (args.length == 1) { + } else { //if it's a number, we are setting F. Otherwise, it's a getter for the MCPlayer specified. - - if (!(args[0] instanceof CInt)) { + if(!(args[0].isInstanceOf(CInt.TYPE))) { MCPlayer p2 = Static.GetPlayer(args[0], t); l = p2.getLocation(); } } - if (l != null) { - float yaw = l.getYaw(); - float pitch = l.getPitch(); - //normalize yaw - if (yaw < 0) { - yaw = (((yaw) % 360) + 360); + if(l != null) { + // guarantee yaw in the 0 - 359.9~ range + float yaw = l.getYaw() % 360.0f; + if(yaw < 0.0f) { + yaw += 360.0f; } + float pitch = l.getPitch(); return new CArray(t, new CDouble(yaw, t), new CDouble(pitch, t)); } } @@ -1285,57 +1515,70 @@ public Construct exec(Target t, Environment env, Construct... args) throws Confi MCPlayer toSet = null; float yaw = 0; float pitch = 0; - if (args.length == 1) { + if(args.length == 1) { //We are setting F for this MCPlayer - if (p instanceof MCPlayer) { + if(p instanceof MCPlayer) { toSet = (MCPlayer) p; - pitch = toSet.getLocation().getPitch(); + MCLocation loc = toSet.getLocation(); + if(loc == null) { + throw new CRENotFoundException( + "Could not find the location of the given player (are you running in cmdline mode?)", t); + } + pitch = loc.getPitch(); } - int g = Static.getInt32(args[0], t); - if (g < 0 || g > 3) { - throw new ConfigRuntimeException("The F specifed must be from 0 to 3", - ExceptionType.RangeException, t); + int g = ArgumentValidation.getInt32(args[0], t); + if(g < 0 || g > 3) { + throw new CRERangeException("The F specifed must be from 0 to 3", t); } yaw = g * 90; - } else if (args.length == 2) { + } else if(args.length == 2) { //Either we are setting this MCPlayer's pitch and yaw, or we are setting the specified MCPlayer's F. //Check to see if args[0] is a number try { - Float.parseFloat(args[0].val()); + yaw = (float) ArgumentValidation.getNumber(args[0], t); + pitch = (float) ArgumentValidation.getNumber(args[1], t); //It's the yaw, pitch variation - if (p instanceof MCPlayer) { + if(p instanceof MCPlayer) { toSet = (MCPlayer) p; } - yaw = (float) Static.getNumber(args[0], t); - pitch = (float) Static.getNumber(args[1], t); - } catch (NumberFormatException e) { + } catch (CRECastException e) { //It's the MCPlayer, F variation toSet = Static.GetPlayer(args[0], t); pitch = toSet.getLocation().getPitch(); - int g = Static.getInt32(args[1], t); - if (g < 0 || g > 3) { - throw new ConfigRuntimeException("The F specifed must be from 0 to 3", - ExceptionType.RangeException, t); + int g = ArgumentValidation.getInt32(args[1], t); + if(g < 0 || g > 3) { + throw new CRERangeException("The F specifed must be from 0 to 3", t); } yaw = g * 90; } - } else if (args.length == 3) { + } else if(args.length == 3) { //It's the MCPlayer, yaw, pitch variation toSet = Static.GetPlayer(args[0], t); - yaw = (float) Static.getNumber(args[1], t); - pitch = (float) Static.getNumber(args[2], t); + yaw = (float) ArgumentValidation.getNumber(args[1], t); + pitch = (float) ArgumentValidation.getNumber(args[2], t); } //Error check our data - if (pitch > 90 || pitch < -90) { - throw new ConfigRuntimeException("pitch must be between -90 and 90", - ExceptionType.RangeException, t); + if(pitch > 90 || pitch < -90) { + throw new CRERangeException("pitch must be between -90 and 90", t); } Static.AssertPlayerNonNull(toSet, t); - MCLocation l = toSet.getLocation().clone(); + MCLocation l = toSet.getLocation(); + if(l == null) { + throw new CRENotFoundException( + "Could not find the location of the player (are you running in cmdline mode?)", t); + } + l = l.clone(); l.setPitch(pitch); l.setYaw(yaw); + MCEntity vehicle = null; + if(toSet.isInsideVehicle()) { + vehicle = toSet.getVehicle(); + } toSet.teleport(l); + if(vehicle != null) { + vehicle.setPassenger(toSet); + } return CVoid.VOID; } } @@ -1355,12 +1598,14 @@ public Integer[] numArgs() { @Override public String docs() { - return "string {[player]} Returns the player's game mode. It will be one of " + StringUtils.Join(MCGameMode.values(), ", ", ", or ") + "."; + return "string {[player]} Returns the player's game mode." + + " It will be one of " + StringUtils.Join(MCGameMode.values(), ", ", ", or ") + "."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, + CRENotFoundException.class}; } @Override @@ -1369,8 +1614,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_1_3; + public MSVersion since() { + return MSVersion.V3_1_3; } @Override @@ -1379,18 +1624,23 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { MCCommandSender p = env.getEnv(CommandHelperEnvironment.class).GetCommandSender(); MCPlayer m = null; - if (p instanceof MCPlayer) { + if(p instanceof MCPlayer) { m = (MCPlayer) p; } - if (args.length == 1) { + if(args.length == 1) { m = Static.GetPlayer(args[0], t); } Static.AssertPlayerNonNull(m, t); - String mode = m.getGameMode().name(); + MCGameMode gm = m.getGameMode(); + if(gm == null) { + throw new CRENotFoundException( + "Could not find the gamemode of the given player (are you running in cmdline mode?)", t); + } + String mode = gm.name(); return new CString(mode, t); } } @@ -1410,12 +1660,13 @@ public Integer[] numArgs() { @Override public String docs() { - return "void {[player], mode} Sets the player's game mode. mode must be one of: " + StringUtils.Join(MCGameMode.values(), ", ", ", or "); + return "void {[player], mode} Sets the player's game mode." + + " Mode must be one of: " + StringUtils.Join(MCGameMode.values(), ", ", ", or "); } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException, ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CREFormatException.class}; } @Override @@ -1424,8 +1675,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_1_3; + public MSVersion since() { + return MSVersion.V3_1_3; } @Override @@ -1434,15 +1685,15 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { MCCommandSender p = env.getEnv(CommandHelperEnvironment.class).GetCommandSender(); MCPlayer m = null; - String mode = ""; + String mode; MCGameMode gm; - if (p instanceof MCPlayer) { + if(p instanceof MCPlayer) { m = (MCPlayer) p; } - if (args.length == 2) { + if(args.length == 2) { m = Static.GetPlayer(args[0], t); mode = args[1].val(); } else { @@ -1452,7 +1703,7 @@ public Construct exec(Target t, Environment env, Construct... args) throws Confi try { gm = MCGameMode.valueOf(mode.toUpperCase()); } catch (IllegalArgumentException e) { - throw new ConfigRuntimeException("Mode must be either " + StringUtils.Join(MCGameMode.values(), ", ", ", or "), ExceptionType.FormatException, t); + throw new CREFormatException("Mode must be either " + StringUtils.Join(MCGameMode.values(), ", ", ", or "), t); } Static.AssertPlayerNonNull(m, t); m.setGameMode(gm); @@ -1475,13 +1726,12 @@ public Integer[] numArgs() { @Override public String docs() { - return "int {[player]} Gets the experience of a player within this level, as a percentage, from 0 to 99. (100 would be next level," - + " therefore, 0.)"; + return "int {[player]} Gets the experience of a player within this level, as a percentage, from 0 to 100."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException, ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CRECastException.class, CREPlayerOfflineException.class}; } @Override @@ -1490,8 +1740,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_1_3; + public MSVersion since() { + return MSVersion.V3_1_3; } @Override @@ -1500,17 +1750,17 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { MCCommandSender p = env.getEnv(CommandHelperEnvironment.class).GetCommandSender(); MCPlayer m = null; - if (p instanceof MCPlayer) { + if(p instanceof MCPlayer) { m = (MCPlayer) p; } - if (args.length == 1) { + if(args.length == 1) { m = Static.GetPlayer(args[0].val(), t); } Static.AssertPlayerNonNull(m, t); - return new CInt((int) (m.getExp() * 100), t); + return new CInt(java.lang.Math.round(m.getExp() * 100), t); } } @@ -1529,12 +1779,13 @@ public Integer[] numArgs() { @Override public String docs() { - return "void {[player], xp} Sets the experience of a player within the current level, as a percentage, from 0 to 100."; + return "void {[player], xp} Sets the experience of a player within the current level, as a percentage," + + " from 0 to 100. 100 will sometimes reset the experience to zero and add a level to the player."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException, ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CRECastException.class, CREPlayerOfflineException.class, CRERangeException.class}; } @Override @@ -1543,8 +1794,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_1_3; + public MSVersion since() { + return MSVersion.V3_1_3; } @Override @@ -1553,20 +1804,23 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { MCCommandSender p = env.getEnv(CommandHelperEnvironment.class).GetCommandSender(); MCPlayer m = null; - int xp = 0; - if (p instanceof MCPlayer) { + int xp; + if(p instanceof MCPlayer) { m = (MCPlayer) p; } - if (args.length == 2) { + if(args.length == 2) { m = Static.GetPlayer(args[0].val(), t); - xp = Static.getInt32(args[1], t); + xp = ArgumentValidation.getInt32(args[1], t); } else { - xp = Static.getInt32(args[0], t); + xp = ArgumentValidation.getInt32(args[0], t); } Static.AssertPlayerNonNull(m, t); + if(xp < 0 || xp > 100) { + throw new CRERangeException("Experience percentage must be from 0 to 100.", t); + } m.setExp(((float) xp) / 100.0F); return CVoid.VOID; } @@ -1587,12 +1841,12 @@ public Integer[] numArgs() { @Override public String docs() { - return "void {[player], exp} Gives the player the specified amount of xp."; + return "void {[player], exp} Gives the player the specified amount of experience."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException, ExceptionType.CastException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CRECastException.class}; } @Override @@ -1601,28 +1855,28 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_0; + public MSVersion since() { + return MSVersion.V3_3_0; } @Override public Boolean runAsync() { - return true; + return false; } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCCommandSender p = environment.getEnv(CommandHelperEnvironment.class).GetCommandSender(); MCPlayer m = null; - int xp = 0; - if (p instanceof MCPlayer) { + int xp; + if(p instanceof MCPlayer) { m = (MCPlayer) p; } - if (args.length == 2) { + if(args.length == 2) { m = Static.GetPlayer(args[0].val(), t); - xp = Static.getInt32(args[1], t); + xp = ArgumentValidation.getInt32(args[1], t); } else { - xp = Static.getInt32(args[0], t); + xp = ArgumentValidation.getInt32(args[0], t); } Static.AssertPlayerNonNull(m, t); m.giveExp(xp); @@ -1650,8 +1904,8 @@ public String docs() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException, ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CRECastException.class, CREPlayerOfflineException.class}; } @Override @@ -1660,8 +1914,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_1_3; + public MSVersion since() { + return MSVersion.V3_1_3; } @Override @@ -1670,13 +1924,13 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { MCCommandSender p = env.getEnv(CommandHelperEnvironment.class).GetCommandSender(); MCPlayer m = null; - if (p instanceof MCPlayer) { + if(p instanceof MCPlayer) { m = (MCPlayer) p; } - if (args.length == 1) { + if(args.length == 1) { m = Static.GetPlayer(args[0].val(), t); } Static.AssertPlayerNonNull(m, t); @@ -1703,8 +1957,8 @@ public String docs() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException, ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CRECastException.class, CREPlayerOfflineException.class}; } @Override @@ -1713,8 +1967,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_1_3; + public MSVersion since() { + return MSVersion.V3_1_3; } @Override @@ -1723,18 +1977,18 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { MCCommandSender p = env.getEnv(CommandHelperEnvironment.class).GetCommandSender(); MCPlayer m = null; - int level = 0; - if (p instanceof MCPlayer) { + int level; + if(p instanceof MCPlayer) { m = (MCPlayer) p; } - if (args.length == 2) { + if(args.length == 2) { m = Static.GetPlayer(args[0].val(), t); - level = Static.getInt32(args[1], t); + level = ArgumentValidation.getInt32(args[1], t); } else { - level = Static.getInt32(args[0], t); + level = ArgumentValidation.getInt32(args[0], t); } Static.AssertPlayerNonNull(m, t); m.setLevel(level); @@ -1761,8 +2015,8 @@ public String docs() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException, ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CRECastException.class, CREPlayerOfflineException.class}; } @Override @@ -1771,8 +2025,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_1_3; + public MSVersion since() { + return MSVersion.V3_1_3; } @Override @@ -1781,17 +2035,18 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { MCCommandSender p = env.getEnv(CommandHelperEnvironment.class).GetCommandSender(); MCPlayer m = null; - if (p instanceof MCPlayer) { + if(p instanceof MCPlayer) { m = (MCPlayer) p; } - if (args.length == 1) { + if(args.length == 1) { m = Static.GetPlayer(args[0].val(), t); } Static.AssertPlayerNonNull(m, t); - return new CInt(m.getTotalExperience(), t); + int texp = m.getExpAtLevel() + java.lang.Math.round(m.getExpToLevel() * m.getExp()); + return new CInt(texp, t); } } @@ -1810,12 +2065,12 @@ public Integer[] numArgs() { @Override public String docs() { - return "void {[player], xp} Sets the total experience of a player."; + return "void {[player], exp} Sets the total experience of a player."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException, ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CRECastException.class, CREPlayerOfflineException.class, CRERangeException.class}; } @Override @@ -1824,8 +2079,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_1_3; + public MSVersion since() { + return MSVersion.V3_1_3; } @Override @@ -1834,25 +2089,28 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { MCCommandSender p = env.getEnv(CommandHelperEnvironment.class).GetCommandSender(); MCPlayer m = null; - int xp = 0; - if (p instanceof MCPlayer) { + int xp; + if(p instanceof MCPlayer) { m = (MCPlayer) p; } - if (args.length == 2) { + if(args.length == 2) { m = Static.GetPlayer(args[0].val(), t); - xp = Static.getInt32(args[1], t); + xp = ArgumentValidation.getInt32(args[1], t); } else { - xp = Static.getInt32(args[0], t); + xp = ArgumentValidation.getInt32(args[0], t); + } + if(xp < 0) { + throw new CRERangeException("Experience can't be negative", t); } Static.AssertPlayerNonNull(m, t); - m.setTotalExperience(xp); -// m.setLevel(0); -// m.setExp(0); -// m.setTotalExperience(0); -// m.giveExp(xp); + int score = m.getTotalExperience(); + m.setLevel(0); + m.setExp(0); + m.giveExp(xp); + m.setTotalExperience(score); // reset experience score so that this function does not affect it return CVoid.VOID; } } @@ -1876,8 +2134,8 @@ public String docs() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class}; } @Override @@ -1886,8 +2144,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_1_3; + public MSVersion since() { + return MSVersion.V3_1_3; } @Override @@ -1896,13 +2154,13 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { MCCommandSender p = env.getEnv(CommandHelperEnvironment.class).GetCommandSender(); MCPlayer m = null; - if (p instanceof MCPlayer) { + if(p instanceof MCPlayer) { m = (MCPlayer) p; } - if (args.length == 1) { + if(args.length == 1) { m = Static.GetPlayer(args[0].val(), t); } Static.AssertPlayerNonNull(m, t); @@ -1929,8 +2187,8 @@ public String docs() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException, ExceptionType.CastException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CRECastException.class}; } @Override @@ -1939,8 +2197,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_1_3; + public MSVersion since() { + return MSVersion.V3_1_3; } @Override @@ -1949,18 +2207,18 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { MCCommandSender p = env.getEnv(CommandHelperEnvironment.class).GetCommandSender(); MCPlayer m = null; - int level = 0; - if (p instanceof MCPlayer) { + int level; + if(p instanceof MCPlayer) { m = (MCPlayer) p; } - if (args.length == 2) { + if(args.length == 2) { m = Static.GetPlayer(args[0].val(), t); - level = Static.getInt32(args[1], t); + level = ArgumentValidation.getInt32(args[1], t); } else { - level = Static.getInt32(args[0], t); + level = ArgumentValidation.getInt32(args[0], t); } Static.AssertPlayerNonNull(m, t); m.setFoodLevel(level); @@ -1978,25 +2236,29 @@ public String getName() { @Override public Integer[] numArgs() { - return new Integer[]{3, 4, 5}; + return new Integer[]{2, 3, 4, 5, 6, 7}; } @Override public String docs() { - return "boolean {player, potionID, strength, [seconds], [ambient]} Effect is 1-23. Seconds defaults to 30." - + " If the potionID is out of range, a RangeException is thrown, because out of range potion effects" - + " cause the client to crash, fairly hardcore. See http://www.minecraftwiki.net/wiki/Potion_effects" - + " for a complete list of potions that can be added. To remove an effect, set the seconds to 0." - + " Strength is the number of levels to add to the base power (effect level 1). Ambient takes a boolean" - + " of whether the particles should be less noticeable. The function returns true if the effect was" - + " added or removed as desired, and false if it wasn't (however, this currently only will happen if" - + " an effect is attempted to be removed, yet isn't already on the player)."; + return "boolean {player, potionEffect, [strength], [seconds], [ambient], [particles], [icon]}" + + " Adds one, or modifies an existing, potion effect on a mob." + + " The potionEffect can be " + StringUtils.Join(MCPotionEffectType.types(), ", ", ", or ", " or ") + + ". It also accepts an integer corresponding to the effect id listed on the Minecraft wiki." + + " Strength is an integer representing the power level of the effect, starting at 0." + + " Seconds defaults to 30.0. To remove an effect, set the seconds to 0." + + " If seconds is greater than 107374182 a RangeException is thrown." + + " Negative seconds makes the effect infinite. (or max in versions prior to 1.19.4)" + + " Ambient takes a boolean of whether the particles should be more transparent." + + " Particles takes a boolean of whether the particles should be visible at all." + + " Icon argument takes a boolean of whether the effect icon should be displayed." + + " The function returns whether or not the effect was modified."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException, ExceptionType.CastException, - ExceptionType.RangeException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CRECastException.class, + CRERangeException.class}; } @Override @@ -2005,8 +2267,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_0; + public MSVersion since() { + return MSVersion.V3_3_0; } @Override @@ -2015,45 +2277,72 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { MCPlayer m = Static.GetPlayer(args[0].val(), t); - int effect = Static.getInt32(args[1], t); - //To work around a bug in bukkit/vanilla, if the effect is invalid, throw an exception - //otherwise the client crashes, and requires deletion of - //player data to fix. - if (effect < 1 || effect > m.getMaxEffect()) { - throw new ConfigRuntimeException("Invalid effect ID recieved, must be from 1-" + m.getMaxEffect(), - ExceptionType.RangeException, t); + MCPotionEffectType type = null; + if(args[1].isInstanceOf(CString.TYPE)) { + try { + type = MCPotionEffectType.valueOf(args[1].val().toUpperCase()); + } catch (IllegalArgumentException ex) { + // maybe it's a number id + } + } + if(type == null) { + try { + type = MCPotionEffectType.getById(ArgumentValidation.getInt32(args[1], t)); + } catch (CRECastException | IllegalArgumentException ex) { + throw new CREFormatException("Invalid potion effect type: " + args[1].val(), t); + } } - int strength = Static.getInt32(args[2], t); - int seconds = 30; + int strength = 0; + double seconds = 30.0; boolean ambient = false; - if (args.length >= 4) { - seconds = Static.getInt32(args[3], t); - } - if (args.length == 5) { - ambient = Static.getBoolean(args[4]); + boolean particles = true; + boolean icon = true; + if(args.length >= 3) { + strength = ArgumentValidation.getInt32(args[2], t); + + if(args.length >= 4) { + seconds = ArgumentValidation.getDouble(args[3], t); + if(seconds * 20 > Integer.MAX_VALUE) { + throw new CRERangeException("Seconds cannot be greater than 107374182.0", t); + } + + if(args.length >= 5) { + ambient = ArgumentValidation.getBoolean(args[4], t); + + if(args.length >= 6) { + particles = ArgumentValidation.getBoolean(args[5], t); + + if(args.length == 7) { + icon = ArgumentValidation.getBoolean(args[6], t); + } + } + } + } } - Static.AssertPlayerNonNull(m, t); - if (seconds == 0) { - return CBoolean.get(m.removeEffect(effect)); + + if(seconds == 0.0) { + return CBoolean.get(m.removeEffect(type)); } else { - m.addEffect(effect, strength, seconds, ambient, t); - return CBoolean.TRUE; + return CBoolean.get(m.addEffect(type, strength, (int) (seconds * 20), ambient, particles, icon)); } } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ - new ExampleScript("Give player Notch nausea for 30 seconds", "set_peffect('Notch', 9, 30)", - "The player will experience a wobbly screen."), - new ExampleScript("Make player ArenaPlayer unable to jump for 10 minutes", "set_peffect('ArenaPlayer', 8, -16, 600)", - "From the player's perspective, they will not even leave the ground."), - new ExampleScript("Remove poison from yourself", "set_peffect(player(), 19, 1, 0)", - "You are now unpoisoned. Note, it does not matter what you set strength to here.") + new ExampleScript("Give player Notch nausea for 30 seconds", + "set_peffect('Notch', 'NAUSEA')", + "The player will experience a wobbly screen."), + new ExampleScript("Make player ArenaPlayer unable to jump for 10 minutes", + "set_peffect('ArenaPlayer', 'JUMP_BOOST', -16, 600)", + "From the player's perspective, they will not even leave the ground."), + new ExampleScript("Remove poison from yourself", + "set_peffect(player(), 'POISON', 1, 0)", + "You are now unpoisoned. Note, it does not matter what you set strength to here.") }; } } @@ -2062,8 +2351,8 @@ public ExampleScript[] examples() throws ConfigCompileException { public static class get_peffect extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class}; } @Override @@ -2077,9 +2366,9 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCPlayer p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); - if (args.length > 0) { + if(args.length > 0) { p = Static.GetPlayer(args[0], t); } Static.AssertPlayerNonNull(p, t); @@ -2098,73 +2387,230 @@ public Integer[] numArgs() { @Override public String docs() { - return "array {[player]} Returns an array of effects that are currently active on a given player." - + " The array will be full of playerEffect objects, which contain three fields, \"id\"," - + " \"strength\", \"seconds\" remaining, and whether the effect is \"ambient\"."; + return "array {[player]} Returns an array of potion effects that are currently active on a given player." + + " The array can contain potion effect objects, with the key defining the type of potion effect." + + " The arrays contain the following fields: \"id\"," + + " \"strength\", \"seconds\" remaining, whether the effect is \"ambient\", whether" + + " \"particles\" are enabled, and whether the \"icon\" is shown to the player."; } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } } @api(environments = {CommandHelperEnvironment.class}) - public static class set_phealth extends AbstractFunction { + public static class clear_peffects extends AbstractFunction { @Override - public String getName() { - return "set_phealth"; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CRELengthException.class}; } @Override - public Integer[] numArgs() { - return new Integer[]{1, 2}; + public boolean isRestricted() { + return true; } @Override - public String docs() { - return "void {[player], health} Sets the player's health. Health should be a double between 0 and their max health."; + public Boolean runAsync() { + return false; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException, ExceptionType.RangeException, ExceptionType.PlayerOfflineException}; + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCPlayer p; + if(args.length == 0) { + p = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); + Static.AssertPlayerNonNull(p, t); + } else { + p = Static.GetPlayer(args[0], t); + } + p.removeEffects(); + return CVoid.VOID; } @Override - public boolean isRestricted() { - return true; + public String getName() { + return "clear_peffects"; } @Override - public CHVersion since() { - return CHVersion.V3_2_0; + public Integer[] numArgs() { + return new Integer[]{0, 1}; } @Override - public Boolean runAsync() { - return false; + public String docs() { + return "void {[player]} Removes all potion effects from a player."; } @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { - MCCommandSender p = env.getEnv(CommandHelperEnvironment.class).GetCommandSender(); - MCPlayer m = null; - if (p instanceof MCPlayer) { - m = (MCPlayer) p; - } + public MSVersion since() { + return MSVersion.V3_3_2; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class psneaking extends AbstractFunction { + + @Override + public String getName() { + return "psneaking"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{0, 1}; + } + + @Override + public String docs() { + return "boolean {[player]} Returns whether or not the player is sneaking."; + } + + @Override + public Class[] thrown() { + return new Class[]{CRELengthException.class, CREPlayerOfflineException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_2; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCPlayer m; + if(args.length == 0) { + m = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); + Static.AssertPlayerNonNull(m, t); + } else { + m = Static.GetPlayer(args[0].val(), t); + } + return CBoolean.get(m.isSneaking()); + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class phealth extends AbstractFunction { + + @Override + public String getName() { + return "phealth"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{0, 1}; + } + + @Override + public String docs() { + return "double {[player]} Gets the player's health."; + } + + @Override + public Class[] thrown() { + return new Class[]{CRELengthException.class, CREPlayerOfflineException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_2; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCPlayer m; + if(args.length == 0) { + m = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); + Static.AssertPlayerNonNull(m, t); + } else { + m = Static.GetPlayer(args[0].val(), t); + } + return new CDouble(m.getHealth(), t); + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class set_phealth extends AbstractFunction { + + @Override + public String getName() { + return "set_phealth"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1, 2}; + } + + @Override + public String docs() { + return "void {[player], health} Sets the player's health." + + " Health should be a double between 0 and their max health, which is 20.0 by default."; + } + + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class, CRERangeException.class, CREPlayerOfflineException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public MSVersion since() { + return MSVersion.V3_2_0; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCCommandSender p = env.getEnv(CommandHelperEnvironment.class).GetCommandSender(); + MCPlayer m = null; + if(p instanceof MCPlayer) { + m = (MCPlayer) p; + } double health; - if (args.length == 2) { + if(args.length == 2) { m = Static.GetPlayer(args[0].val(), t); - health = Static.getDouble(args[1], t); + health = ArgumentValidation.getDouble(args[1], t); } else { - health = Static.getDouble(args[0], t); + health = ArgumentValidation.getDouble(args[0], t); } Static.AssertPlayerNonNull(m, t); - if (health < 0 || health > m.getMaxHealth()) { - throw new ConfigRuntimeException("Health must be between 0 and the player's max health (currently " - + m.getMaxHealth() + " for " + m.getName() + ").", ExceptionType.RangeException, t); + if(health < 0 || health > m.getMaxHealth()) { + throw new CRERangeException("Health must be between 0 and the player's max health (currently " + + m.getMaxHealth() + " for " + m.getName() + ").", t); } m.setHealth(health); return CVoid.VOID; @@ -2186,14 +2632,14 @@ public Integer[] numArgs() { @Override public String docs() { - return "boolean {player} Returns whether or not the specified player is online. Note" - + " that the name must match exactly, but it will not throw a PlayerOfflineException" + return "boolean {player} Returns whether or not the specified player is online." + + " Note that the name must match exactly, but it will not throw a PlayerOfflineException" + " if the player is not online, or if the player doesn't even exist."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{}; } @Override @@ -2202,8 +2648,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_0; + public MSVersion since() { + return MSVersion.V3_3_0; } @Override @@ -2212,26 +2658,18 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { - //We have to use this method here, because we might be in the midst - //of an event, in which the player is offline, but not really. It will - //throw an exception if the player doesn't exist - MCPlayer p = null; + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { try { - p = Static.GetPlayer(args[0], t); + //Static.GetPlayer() autocompletes names, which we don't want in this function, + //however we have to check if this is an injected player first. + MCPlayer p = Static.GetPlayer(args[0], t); + //Now we must check if the name was exact. Skip this if the argument is a UUID. + if(args[0].val().length() <= 16 && !p.getName().equalsIgnoreCase(args[0].val())) { + p = Static.getServer().getPlayerExact(args[0].val()); + } + return CBoolean.get(p != null); } catch (ConfigRuntimeException e) { //They aren't in the player list - } - //If the player we grabbed doesn't match exactly, we're referring to another player - //However, we had to check with Static.GetPlayer first, in case this is an injected player. - //Otherwise, we need to use the player returned from Static.GetPlayer, not the one returned - //from the server directly - if (p != null && !p.getName().equals(args[0].val())) { - MCOfflinePlayer player = Static.getServer().getOfflinePlayer(args[0].val()); - return CBoolean.get(player.isOnline()); - } else if (p != null) { - return CBoolean.get(p.isOnline()); - } else { return CBoolean.FALSE; } } @@ -2252,13 +2690,13 @@ public Integer[] numArgs() { @Override public String docs() { - return "boolean {player} Returns whether or not this player is whitelisted. Note that" - + " this will work with offline players, but the name must be exact."; + return "boolean {player} Returns whether or not this player is whitelisted." + + " This will work with offline players, but the name must be exact. ---- " + UUID_WARNING; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{}; } @Override @@ -2267,8 +2705,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_0; + public MSVersion since() { + return MSVersion.V3_3_0; } @Override @@ -2277,15 +2715,9 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { - MCOfflinePlayer pl = Static.getServer().getOfflinePlayer(args[0].val()); - boolean ret; - if (pl == null) { - ret = false; - } else { - ret = pl.isWhitelisted(); - } - return CBoolean.get(ret); + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCOfflinePlayer pl = Static.GetUser(args[0].val(), t); + return CBoolean.get(pl != null && pl.isWhitelisted()); } } @@ -2304,13 +2736,13 @@ public Integer[] numArgs() { @Override public String docs() { - return "void {player, isWhitelisted} Sets the whitelist flag of the specified player. Note that" - + " this will work with offline players, but the name must be exact."; + return "void {player, isWhitelisted} Sets the whitelist flag of the specified player." + + " This will work with offline players, but the name must be exact. ---- " + UUID_WARNING; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{CRENotFoundException.class}; } @Override @@ -2319,8 +2751,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_0; + public MSVersion since() { + return MSVersion.V3_3_0; } @Override @@ -2329,14 +2761,26 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { - MCOfflinePlayer pl = Static.getServer().getOfflinePlayer(args[0].val()); - boolean whitelist = Static.getBoolean(args[1]); + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCOfflinePlayer pl = Static.GetUser(args[0].val(), t); + boolean whitelist = ArgumentValidation.getBoolean(args[1], t); + if(pl == null) { + throw new CRENotFoundException( + this.getName() + " could not get the offline player (are you running in cmdline mode?)", t); + } pl.setWhitelisted(whitelist); return CVoid.VOID; } } + private static final String UUID_WARNING = " NOTICE: This function accepts UUIDs in place of player names," + + " however due to lack of API from Mojang, some server software is not able to" + + " correctly associate a UUID with a player if the player has not recently been online." + + " As such, it may not always be possible to ban or whitelist a player by UUID." + + " Servers known to have this problem are Bukkit and Spigot. Furthermore," + + " although this API functions, due to the limitations of the vanilla ban/whitelist" + + " system, it is recommended to use a 3rd party system or write your own."; + @api(environments = {CommandHelperEnvironment.class}) public static class pbanned extends AbstractFunction { @@ -2352,16 +2796,16 @@ public Integer[] numArgs() { @Override public String docs() { - return "boolean {player} Returns whether or not this player is banned. Note that" - + " this will work with offline players, but the name must be exact. At this" - + " time, this function only works with the vanilla ban system. If you use" - + " a third party ban system, you should instead run the command for that" - + " plugin instead."; + return "boolean {player} Returns whether or not this player is banned." + + " This will work with offline players, but the name must be exact." + + " At this time, this function only works with the vanilla ban system." + + " If you use a third party ban system, you should instead run the command for that" + + " plugin instead. ---- " + UUID_WARNING; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{CRENotFoundException.class}; } @Override @@ -2370,8 +2814,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_0; + public MSVersion since() { + return MSVersion.V3_3_0; } @Override @@ -2380,8 +2824,12 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { - MCOfflinePlayer pl = Static.getServer().getOfflinePlayer(args[0].val()); + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCOfflinePlayer pl = Static.GetUser(args[0].val(), t); + if(pl == null) { + throw new CRENotFoundException( + this.getName() + " could not get the offline player (are you running in cmdline mode?)", t); + } return CBoolean.get(pl.isBanned()); } } @@ -2396,21 +2844,23 @@ public String getName() { @Override public Integer[] numArgs() { - return new Integer[]{2}; + return new Integer[]{2, 3, 4}; } @Override public String docs() { - return "void {player, isBanned} Sets the ban flag of the specified player. Note that" - + " this will work with offline players, but the name must be exact. At this" - + " time, this function only works with the vanilla ban system. If you use" - + " a third party ban system, you should instead run the command for that" - + " plugin instead."; + return "void {player, isBanned, [reason], [source]} Sets the ban flag for the specified player." + + " This will work with offline players, but the name must be exact. When banning," + + " a reason message may be provided that the player will see when attempting to login." + + " An optional source may also be provided that indicates who or what banned the player." + + " At this time, this function only works with the vanilla ban system." + + " If you use a third party ban system, you should instead run the command for that" + + " plugin instead. ---- " + UUID_WARNING; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{CRENotFoundException.class}; } @Override @@ -2419,8 +2869,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_0; + public MSVersion since() { + return MSVersion.V3_3_0; } @Override @@ -2429,10 +2879,35 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { - MCOfflinePlayer pl = Static.getServer().getOfflinePlayer(args[0].val()); - boolean ban = Static.getBoolean(args[1]); - pl.setBanned(ban); + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + String target = args[0].val(); + boolean ban = ArgumentValidation.getBoolean(args[1], t); + String reason = ""; + String source = ""; + + if(target.length() > 16) { + MCOfflinePlayer pl = Static.GetUser(target, t); + if(pl == null) { + throw new CRENotFoundException( + this.getName() + " could not get the offline player (are you running in cmdline mode?)", t); + } + target = pl.getName(); + if(target == null) { + throw new CRENotFoundException(this.getName() + " could not get offline player's name", t); + } + } + + if(ban) { + if(args.length > 2) { + reason = Construct.nval(args[2]); + if(args.length == 4) { + source = Construct.nval(args[3]); + } + } + Static.getServer().banName(target, reason, source); + } else { + Static.getServer().unbanName(target); + } return CVoid.VOID; } } @@ -2452,12 +2927,13 @@ public Integer[] numArgs() { @Override public String docs() { - return "void {[player], speed} Sets players speed. The speed must be between -1 or 1"; + return "void {[player], speed} Sets a player's walk speed. The speed must be between -1.0 and 1.0." + + " The default player walk speed is 0.2."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException, ExceptionType.RangeException, ExceptionType.CastException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CRERangeException.class, CRECastException.class}; } @Override @@ -2466,8 +2942,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } @Override @@ -2476,23 +2952,23 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { MCCommandSender p = env.getEnv(CommandHelperEnvironment.class).GetCommandSender(); MCPlayer m = null; - double speed = 0; + double speed; - if (p instanceof MCPlayer) { + if(p instanceof MCPlayer) { m = (MCPlayer) p; } - if (args.length == 2) { + if(args.length == 2) { m = Static.GetPlayer(args[0], t); - speed = Static.getDouble(args[1], t); + speed = ArgumentValidation.getDouble(args[1], t); } else { - speed = Static.getDouble(args[0], t); + speed = ArgumentValidation.getDouble(args[0], t); } if(speed < -1 || speed > 1) { - throw new ConfigRuntimeException("Speed must be between -1 and 1", ExceptionType.RangeException, t); + throw new CRERangeException("Speed must be between -1 and 1", t); } Static.AssertPlayerNonNull(m, t); @@ -2516,12 +2992,12 @@ public Integer[] numArgs() { @Override public String docs() { - return "double {[player]} Gets the players speed. The speed must be between -1 or 1"; + return "double {[player]} Gets a player's walk speed. The speed will be between -1.0 and 1.0."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException, ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CREFormatException.class}; } @Override @@ -2530,8 +3006,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } @Override @@ -2540,21 +3016,21 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { MCCommandSender p = env.getEnv(CommandHelperEnvironment.class).GetCommandSender(); MCPlayer m = null; - if (p instanceof MCPlayer) { + if(p instanceof MCPlayer) { m = (MCPlayer) p; } - if (args.length == 1) { + if(args.length == 1) { m = Static.GetPlayer(args[0], t); } Static.AssertPlayerNonNull(m, t); - return new CDouble(((double) m.getWalkSpeed()), t); + return new CDouble(m.getWalkSpeed(), t); } } @@ -2573,12 +3049,13 @@ public Integer[] numArgs() { @Override public String docs() { - return "void {[player], speed} Sets players fly speed. The speed must be between -1 or 1"; + return "void {[player], speed} Sets a player's fly speed. The speed must be between -1.0 and 1.0." + + " The default player fly speed is 0.1."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException, ExceptionType.RangeException, ExceptionType.CastException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CRERangeException.class, CRECastException.class}; } @Override @@ -2587,8 +3064,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } @Override @@ -2597,23 +3074,23 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { MCCommandSender p = env.getEnv(CommandHelperEnvironment.class).GetCommandSender(); MCPlayer m = null; - double speed = 0; + double speed; - if (p instanceof MCPlayer) { + if(p instanceof MCPlayer) { m = (MCPlayer) p; } - if (args.length == 2) { + if(args.length == 2) { m = Static.GetPlayer(args[0], t); - speed = Static.getDouble(args[1], t); + speed = ArgumentValidation.getDouble(args[1], t); } else { - speed = Static.getDouble(args[0], t); + speed = ArgumentValidation.getDouble(args[0], t); } if(speed < -1 || speed > 1) { - throw new ConfigRuntimeException("Speed must be between -1 and 1", ExceptionType.RangeException, t); + throw new CRERangeException("Speed must be between -1 and 1", t); } Static.AssertPlayerNonNull(m, t); @@ -2637,12 +3114,12 @@ public Integer[] numArgs() { @Override public String docs() { - return "double {[player]} Gets the players speed. The speed must be between -1 or 1"; + return "double {[player]} Gets a player's fly speed. The speed will be between -1.0 and 1.0."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException, ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CREFormatException.class}; } @Override @@ -2651,8 +3128,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } @Override @@ -2661,21 +3138,21 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { MCCommandSender p = env.getEnv(CommandHelperEnvironment.class).GetCommandSender(); MCPlayer m = null; - if (p instanceof MCPlayer) { + if(p instanceof MCPlayer) { m = (MCPlayer) p; } - if (args.length == 1) { + if(args.length == 1) { m = Static.GetPlayer(args[0], t); } Static.AssertPlayerNonNull(m, t); - return new CDouble(((double) m.getFlySpeed()), t); + return new CDouble(m.getFlySpeed(), t); } } @@ -2694,13 +3171,12 @@ public Integer[] numArgs() { @Override public String docs() { - return "boolean {[player]} Returns whether or not the specified player (or the current" - + " player if not specified) is op"; + return "boolean {[player]} Returns whether or not the player is op."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class}; } @Override @@ -2709,8 +3185,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_0; + public MSVersion since() { + return MSVersion.V3_3_0; } @Override @@ -2719,9 +3195,9 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCPlayer m = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); - if (args.length == 1) { + if(args.length == 1) { m = Static.GetPlayer(args[0].val(), t); } Static.AssertPlayerNonNull(m, t); @@ -2748,8 +3224,8 @@ public String docs() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException, ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CREFormatException.class}; } @Override @@ -2758,8 +3234,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_0; + public MSVersion since() { + return MSVersion.V3_3_0; } @Override @@ -2768,17 +3244,17 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCPlayer m = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); MCLocation l; - if (args.length == 1) { + if(args.length == 1) { l = ObjectGenerator.GetGenerator().location(args[0], null, t); } else { m = Static.GetPlayer(args[0].val(), t); l = ObjectGenerator.GetGenerator().location(args[1], null, t); } - if (m == null) { - throw new ConfigRuntimeException("That player is not online", ExceptionType.PlayerOfflineException, t); + if(m == null) { + throw new CREPlayerOfflineException("That player is not online", t); } Static.AssertPlayerNonNull(m, t); MCLocation old = m.getCompassTarget(); @@ -2802,12 +3278,12 @@ public Integer[] numArgs() { @Override public String docs() { - return "array {[player]} Gets the compass target of the specified player"; + return "array {[player]} Gets the compass target location for the specified player."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class}; } @Override @@ -2816,8 +3292,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_0; + public MSVersion since() { + return MSVersion.V3_3_0; } @Override @@ -2826,9 +3302,9 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCPlayer m = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); - if (args.length == 1) { + if(args.length == 1) { m = Static.GetPlayer(args[0].val(), t); } Static.AssertPlayerNonNull(m, t); @@ -2851,14 +3327,13 @@ public Integer[] numArgs() { @Override public String docs() { - return "int {[player]} Returns the number of ticks remaining that this player will" - + " be on fire for. If the player is not on fire, 0 is returned, which incidentally" - + " is false."; + return "int {[player]} Returns the number of ticks remaining that this player will be on fire for." + + " If the player is not on fire, 0 is returned, which evaluates as false."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class}; } @Override @@ -2867,8 +3342,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_0; + public MSVersion since() { + return MSVersion.V3_3_0; } @Override @@ -2877,9 +3352,9 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCPlayer p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); - if (args.length == 1) { + if(args.length == 1) { p = Static.GetPlayer(args[0], t); } Static.AssertPlayerNonNull(p, t); @@ -2903,13 +3378,13 @@ public Integer[] numArgs() { @Override public String docs() { - return "void {[player], ticks} Sets the player on fire for the specified number of" - + " ticks. If a boolean is given for ticks, false is 0, and true is 20."; + return "void {[player], ticks} Sets the player on fire for the specified number of ticks." + + " If a boolean is given for ticks, false is 0, and true is 20."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException, ExceptionType.CastException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CRECastException.class}; } @Override @@ -2918,8 +3393,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_0; + public MSVersion since() { + return MSVersion.V3_3_0; } @Override @@ -2928,23 +3403,23 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCPlayer p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); - Construct ticks; - if (args.length == 2) { + Mixed ticks; + if(args.length == 2) { p = Static.GetPlayer(args[0], t); ticks = args[1]; } else { ticks = args[0]; } int tick = 0; - if (ticks instanceof CBoolean) { + if(ticks.isInstanceOf(CBoolean.TYPE)) { boolean value = ((CBoolean) ticks).getBoolean(); - if (value) { + if(value) { tick = 20; } } else { - tick = Static.getInt32(ticks, t); + tick = ArgumentValidation.getInt32(ticks, t); } Static.AssertPlayerNonNull(p, t); p.setRemainingFireTicks(tick); @@ -2967,12 +3442,12 @@ public Integer[] numArgs() { @Override public String docs() { - return "boolean {[player]} Returns whether or not the player has the ability to fly"; + return "boolean {[player]} Returns whether or not the player has the ability to fly."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class}; } @Override @@ -2986,9 +3461,9 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCPlayer p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); - if (args.length == 1) { + if(args.length == 1) { p = Static.GetPlayer(args[0], t); } Static.AssertPlayerNonNull(p, t); @@ -2996,44 +3471,13 @@ public Construct exec(Target t, Environment environment, Construct... args) thro } @Override - public CHVersion since() { - return CHVersion.V3_3_1; - } - } - - @api(environments = {CommandHelperEnvironment.class}) - @Deprecated - public static class pset_flight extends set_pflight implements Optimizable { - - @Override - public String getName() { - return "pset_flight"; - } - - @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - return super.exec(t, environment, args); - } - - @Override - public String docs() { - return super.docs() + " DEPRECATED(use set_pflight instead)"; - } - - @Override - public Set optimizationOptions() { - return EnumSet.of(OptimizationOption.OPTIMIZE_DYNAMIC); - } - - @Override - public ParseTree optimizeDynamic(Target t, List children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { - CHLog.GetLogger().Log(CHLog.Tags.COMPILER, LogLevel.WARNING, "Use of pset_flight is deprecated, change it to set_pflight before the next release", t); - return null; + public MSVersion since() { + return MSVersion.V3_3_1; } - } @api(environments = {CommandHelperEnvironment.class}) + @seealso({set_pflying.class}) public static class set_pflight extends AbstractFunction { @Override @@ -3048,12 +3492,12 @@ public Integer[] numArgs() { @Override public String docs() { - return "void {[player], flight} Sets whether or not this player is allowed to fly"; + return "void {[player], flight} Sets whether or not this player is allowed to fly."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class}; } @Override @@ -3067,14 +3511,14 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCPlayer p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); boolean flight; - if (args.length == 1) { - flight = Static.getBoolean(args[0]); + if(args.length == 1) { + flight = ArgumentValidation.getBoolean(args[0], t); } else { p = Static.GetPlayer(args[0], t); - flight = Static.getBoolean(args[1]); + flight = ArgumentValidation.getBoolean(args[1], t); } Static.AssertPlayerNonNull(p, t); p.setAllowFlight(flight); @@ -3082,58 +3526,26 @@ public Construct exec(Target t, Environment environment, Construct... args) thro } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } } - private static final SortedMap TimeLookup = new TreeMap(); + private static final SortedMap TIME_LOOKUP = new TreeMap<>(); static { Properties p = new Properties(); try { p.load(Minecraft.class.getResourceAsStream("/time_names.txt")); Enumeration e = p.propertyNames(); - while (e.hasMoreElements()) { + while(e.hasMoreElements()) { String name = e.nextElement().toString(); - TimeLookup.put(name, new CString(p.getProperty(name).toString(), Target.UNKNOWN)); + TIME_LOOKUP.put(name, new CString(p.getProperty(name), Target.UNKNOWN)); } } catch (IOException ex) { Logger.getLogger(World.class.getName()).log(Level.SEVERE, null, ex); } } - @api(environments = {CommandHelperEnvironment.class}) - @Deprecated - public static class pset_time extends set_ptime implements Optimizable { - - @Override - public String getName() { - return "pset_time"; - } - - @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - return super.exec(t, environment, args); - } - - @Override - public String docs() { - return super.docs() + " DEPRECATED(use set_ptime instead)"; - } - - @Override - public Set optimizationOptions() { - return EnumSet.of(Optimizable.OptimizationOption.OPTIMIZE_DYNAMIC); - } - - @Override - public ParseTree optimizeDynamic(Target t, List children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { - CHLog.GetLogger().Log(CHLog.Tags.COMPILER, LogLevel.WARNING, "Use of pset_time is deprecated, change it to set_ptime before the next release", t); - return null; - } - - } - @api(environments = {CommandHelperEnvironment.class}) public static class set_ptime extends AbstractFunction { @@ -3151,21 +3563,21 @@ public Integer[] numArgs() { public String docs() { StringBuilder doc = new StringBuilder(); doc.append("void {[player], time, [relative]} Sets the time of a given player. Relative defaults to false," - + " but if true, the time will be an offset and the player's time will still progress with the world." + + " but if true, the time will be an offset and the player's time will still progress." + " Otherwise it will be locked and should be a number from 0 to 24000, else it is modulo scaled." + " Alternatively, common time notation (9:30pm, 4:00 am) is acceptable," + " and convenient english mappings also exist:"); doc.append("
    "); - for (String key : TimeLookup.keySet()) { - doc.append("
  • ").append(key).append(" = ").append(TimeLookup.get(key)).append("
  • "); + for(String key : TIME_LOOKUP.keySet()) { + doc.append("
  • ").append(key).append(" = ").append(TIME_LOOKUP.get(key)).append("
  • "); } doc.append("
"); return doc.toString(); } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException, ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CREFormatException.class}; } @Override @@ -3174,8 +3586,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } @Override @@ -3184,45 +3596,45 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCPlayer p = null; boolean relative = false; - if (environment.getEnv(CommandHelperEnvironment.class).GetPlayer() != null) { + if(environment.getEnv(CommandHelperEnvironment.class).GetPlayer() != null) { p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); } - if (args.length >= 2) { + if(args.length >= 2) { p = Static.GetPlayer(args[0], t); } - if (args.length == 3) { - relative = Static.getBoolean(args[2]); + if(args.length == 3) { + relative = ArgumentValidation.getBoolean(args[2], t); } Static.AssertPlayerNonNull(p, t); long time = 0; String stime = (args.length == 1 ? args[0] : args[1]).val().toLowerCase(); - if (TimeLookup.containsKey(stime.replaceAll("[^a-z]", ""))) { - stime = TimeLookup.get(stime.replaceAll("[^a-z]", "")).val(); + if(TIME_LOOKUP.containsKey(stime.replaceAll("[^a-z]", ""))) { + stime = TIME_LOOKUP.get(stime.replaceAll("[^a-z]", "")).val(); } - if (stime.matches("^([\\d]+)[:.]([\\d]+)[ ]*?(?:([pa])\\.*m\\.*){0,1}$")) { + if(stime.matches("^([\\d]+)[:.]([\\d]+)[ ]*?(?:([pa])\\.*m\\.*){0,1}$")) { Pattern pa = Pattern.compile("^([\\d]+)[:.]([\\d]+)[ ]*?(?:([pa])\\.*m\\.*){0,1}$"); Matcher m = pa.matcher(stime); m.find(); int hour = Integer.parseInt(m.group(1)); int minute = Integer.parseInt(m.group(2)); String offset = "a"; - if (m.group(3) != null) { + if(m.group(3) != null) { offset = m.group(3); } - if (offset.equals("p")) { + if(offset.equals("p")) { hour += 12; } - if (hour == 24) { + if(hour == 24) { hour = 0; } - if (hour > 24) { - throw new ConfigRuntimeException("Invalid time provided", ExceptionType.FormatException, t); + if(hour > 24) { + throw new CREFormatException("Invalid time provided", t); } - if (minute > 59) { - throw new ConfigRuntimeException("Invalid time provided", ExceptionType.FormatException, t); + if(minute > 59) { + throw new CREFormatException("Invalid time provided", t); } hour -= 6; hour = hour % 24; @@ -3233,7 +3645,7 @@ public Construct exec(Target t, Environment environment, Construct... args) thro try { Long.valueOf(stime); } catch (NumberFormatException e) { - throw new ConfigRuntimeException("Invalid time provided", ExceptionType.FormatException, t); + throw new CREFormatException("Invalid time provided", t); } time = Long.parseLong(stime); p.setPlayerTime(time, relative); @@ -3256,13 +3668,12 @@ public Integer[] numArgs() { @Override public String docs() { - return "int {[player]} Returns the time of the specified player, as an integer from" - + " 0 to 24000-1"; + return "int {[player]} Returns the time of the specified player, as an integer from 0 to 24000-1"; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class}; } @Override @@ -3271,8 +3682,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } @Override @@ -3281,12 +3692,12 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCPlayer p = null; - if (environment.getEnv(CommandHelperEnvironment.class).GetPlayer() != null) { + if(environment.getEnv(CommandHelperEnvironment.class).GetPlayer() != null) { p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); } - if (args.length == 1) { + if(args.length == 1) { p = Static.GetPlayer(args[0], t); } Static.AssertPlayerNonNull(p, t); @@ -3309,12 +3720,12 @@ public Integer[] numArgs() { @Override public String docs() { - return "void {[player]} Resets the time of the player to the time of the world."; + return "void {[player]} Resets the visible time for the player to the time of the world."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException, ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CREInvalidWorldException.class, CREPlayerOfflineException.class}; } @Override @@ -3323,8 +3734,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } @Override @@ -3333,12 +3744,12 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCPlayer p = null; - if (environment.getEnv(CommandHelperEnvironment.class).GetPlayer() != null) { + if(environment.getEnv(CommandHelperEnvironment.class).GetPlayer() != null) { p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); } - if (args.length == 1) { + if(args.length == 1) { p = Static.GetPlayer(args[0], t); } Static.AssertPlayerNonNull(p, t); @@ -3348,11 +3759,11 @@ public Construct exec(Target t, Environment environment, Construct... args) thro } @api(environments = {CommandHelperEnvironment.class}) - public static class set_list_name extends AbstractFunction { + public static class phas_storm extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException, ExceptionType.LengthException, ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class}; } @Override @@ -3366,56 +3777,43 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCPlayer m = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); - String listName; - if (args.length == 2) { + if(args.length == 1) { m = Static.GetPlayer(args[0], t); - listName = args[1].nval(); - } else { - listName = args[0].nval(); - } - - if (listName.length() > 16) { - throw new ConfigRuntimeException("set_list_name([player,] name) expects name to be 16 characters or less", Exceptions.ExceptionType.LengthException, t); } - Static.AssertPlayerNonNull(m, t); - m.setPlayerListName(listName); - return CVoid.VOID; + return CBoolean.get(m.getPlayerWeather() == MCWeather.DOWNFALL); } @Override public String getName() { - return "set_list_name"; + return "phas_storm"; } @Override public Integer[] numArgs() { - return new Integer[]{1, 2}; + return new Integer[]{0, 1}; } @Override public String docs() { - return "void {[player], [listName]} Sets the player's list name." - + " The name cannot be longer than 16 characters, but colors are supported." - + " Setting the name to null resets it. If the name specified is already taken," - + " a FormatException is thrown, and if the length of the name is greater than 16" - + " characters, a LengthException is thrown."; + return "boolean {[player]} Returns true if the given player is experiencing a storm, as set by" + + " set_pstorm(). (ignores world weather)"; } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public Version since() { + return MSVersion.V3_3_1; } } @api(environments = {CommandHelperEnvironment.class}) - public static class get_list_name extends AbstractFunction { + public static class set_pstorm extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class}; } @Override @@ -3429,99 +3827,109 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCPlayer m = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); - if (args.length == 1) { + int offset = 0; + if(args.length == 2) { m = Static.GetPlayer(args[0], t); + offset = 1; + } + Static.AssertPlayerNonNull(m, t); + if(args[offset] instanceof CNull) { + m.resetPlayerWeather(); + } else if(ArgumentValidation.getBoolean(args[offset], t)) { + m.setPlayerWeather(MCWeather.DOWNFALL); + } else { + m.setPlayerWeather(MCWeather.CLEAR); } - return new CString(m.getPlayerListName(), t); + return CVoid.VOID; } @Override public String getName() { - return "get_list_name"; + return "set_pstorm"; } @Override public Integer[] numArgs() { - return new Integer[]{0, 1}; + return new Integer[]{1, 2}; } @Override public String docs() { - return "string {[player]} Returns the list name of the specified player, or the current player if none specified."; + return "void {[player], downFall} Sets the weather for the given player only. If downFall is true, the" + + " player will experience a storm. If downFall is null, it will reset the player's visible weather" + + " to that which the player's world is experiencing."; } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public Version since() { + return MSVersion.V3_3_1; } } @api(environments = {CommandHelperEnvironment.class}) - @hide("TODO: I'm not sure why this is hidden.") - public static class pvelocity extends AbstractFunction { + public static class set_list_name extends AbstractFunction { @Override - public String getName() { - return "pvelocity"; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CRELengthException.class}; } @Override - public Integer[] numArgs() { - return new Integer[]{0, 1}; + public boolean isRestricted() { + return true; } @Override - public String docs() { - return "array {[player]} Returns an associative array that represents the player's velocity." - + " The array contains the following items: magnitude, x, y, z. These represent a" - + " 3 dimensional Vector. The important part is x, y, z, however, the magnitude is provided" - + " for you as a convenience. (It should equal sqrt(x ** 2 + y ** 2 + z ** 2))"; + public Boolean runAsync() { + return false; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException}; + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCPlayer m; + String listName; + if(args.length == 2) { + m = Static.GetPlayer(args[0], t); + listName = Construct.nval(args[1]); + } else { + m = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); + Static.AssertPlayerNonNull(m, t); + listName = Construct.nval(args[0]); + } + m.setPlayerListName(listName); + return CVoid.VOID; } @Override - public boolean isRestricted() { - return true; + public String getName() { + return "set_list_name"; } @Override - public Boolean runAsync() { - return false; + public Integer[] numArgs() { + return new Integer[]{1, 2}; } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - MCPlayer p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); - if (args.length == 1) { - p = Static.GetPlayer(args[0], t); - } - CArray vector = CArray.GetAssociativeArray(t); - Velocity velocity = p.getVelocity(); - vector.set("magnitude", new CDouble(velocity.magnitude, t), t); - vector.set("x", new CDouble(velocity.x, t), t); - vector.set("y", new CDouble(velocity.y, t), t); - vector.set("z", new CDouble(velocity.z, t), t); - return vector; + public String docs() { + return "void {[player], [listName]} Sets the player's list name." + + " Colors are supported and setting the name to null resets it."; } @Override - public CHVersion since() { - return CHVersion.V3_3_0; + public MSVersion since() { + return MSVersion.V3_3_1; } } @api(environments = {CommandHelperEnvironment.class}) - public static class set_pvelocity extends AbstractFunction { + public static class get_list_name extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException, ExceptionType.PlayerOfflineException, ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class}; } @Override @@ -3535,52 +3943,158 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCPlayer p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); - double x; - double y; - double z; - switch (args.length) { - case 1: - case 2: { - int offset = 0; - if (args.length == 2) { - offset = 1; - p = Static.GetPlayer(args[0], t); - } - if (args[offset] instanceof CArray) { - MCLocation l = ObjectGenerator.GetGenerator().location(args[offset], p.getWorld(), t); - x = l.getX(); - y = l.getY(); - z = l.getZ(); - } else { - throw new ConfigRuntimeException("Expecting an array, but \"" + args[offset].val() + "\" was given.", ExceptionType.CastException, t); - } - break; - } - case 3: - case 4: { - int offset = 0; - if (args.length == 4) { - offset = 1; + if(args.length == 1) { + p = Static.GetPlayer(args[0], t); + } + Static.AssertPlayerNonNull(p, t); + return new CString(p.getPlayerListName(), t); + } + + @Override + public String getName() { + return "get_list_name"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{0, 1}; + } + + @Override + public String docs() { + return "string {[player]} Returns the name of the player that's display on the player list."; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_1; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class pvelocity extends AbstractFunction { + + @Override + public String getName() { + return "pvelocity"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{0, 1}; + } + + @Override + public String docs() { + return "array {[player]} Returns an associative array that represents the player's velocity." + + " The array contains the following items: magnitude, x, y, z. These represent a" + + " 3 dimensional Vector. The important part is x, y, z, however, the magnitude is provided" + + " for you as a convenience. (It should equal sqrt(x ** 2 + y ** 2 + z ** 2))"; + } + + @Override + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CRENotFoundException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCPlayer p; + if(args.length == 1) { + p = Static.GetPlayer(args[0], t); + } else { + p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); + Static.AssertPlayerNonNull(p, t); + } + Vector3D velocity = p.getVelocity(); + if(velocity == null) { + throw new CRENotFoundException( + "The players velocity could not be found (Are you running in cmdline mode?)", t); + } + CArray vector = ObjectGenerator.GetGenerator().vector(velocity, t); + vector.set("magnitude", new CDouble(velocity.length(), t), t); + return vector; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_0; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class set_pvelocity extends AbstractFunction { + + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class, CREPlayerOfflineException.class, CREFormatException.class, + CREIllegalArgumentException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCPlayer p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); + Vector3D v; + int offset = 0; + switch(args.length) { + case 1: + case 2: { + if(args.length == 2) { + offset = 1; + p = Static.GetPlayer(args[0], t); + } + v = ObjectGenerator.GetGenerator().vector(args[offset], t); + break; + } + case 3: + case 4: { + if(args.length == 4) { + offset = 1; p = Static.GetPlayer(args[0], t); } - x = Static.getDouble(args[offset], t); - y = Static.getDouble(args[offset + 1], t); - z = Static.getDouble(args[offset + 2], t); + double x = ArgumentValidation.getDouble(args[offset], t); + double y = ArgumentValidation.getDouble(args[offset + 1], t); + double z = ArgumentValidation.getDouble(args[offset + 2], t); + v = new Vector3D(x, y, z); break; } default: throw new RuntimeException(); } - Velocity v = new Velocity(x, y, z); - if (v.magnitude > 10) { - CHLog.GetLogger().Log(CHLog.Tags.GENERAL, LogLevel.WARNING, + if(v.length() > 10) { + MSLog.GetLogger().Log(MSLog.Tags.GENERAL, LogLevel.WARNING, "The call to " + getName() + " has been cancelled, because the magnitude was greater than 10." - + " (It was " + v.magnitude + ")", t); + + " (It was " + v.length() + ")", t); return CBoolean.FALSE; } - p.setVelocity(v); + Static.AssertPlayerNonNull(p, t); + try { + p.setVelocity(v); + } catch (IllegalArgumentException ex) { + throw new CREIllegalArgumentException(ex.getMessage(), t); + } return CBoolean.TRUE; } @@ -3600,13 +4114,12 @@ public String docs() { + " associative array with x, y, and z keys defined (if magnitude is set, it is ignored)." + " If the vector's magnitude is greater than 10, the command is cancelled, because the" + " server won't allow the player to move faster than that. A warning is issued, and false" - + " is returned if this" - + " happens, otherwise, true is returned."; + + " is returned if this happens, otherwise, true is returned."; } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } } @@ -3614,8 +4127,8 @@ public CHVersion since() { public static class psend_sign_text extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.FormatException, ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CREFormatException.class, CREPlayerOfflineException.class, CRECastException.class}; } @Override @@ -3629,33 +4142,108 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCPlayer p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); int offset = 0; - if (args.length == 3 || args.length == 6) { + if(args.length == 3 || args.length == 6) { p = Static.GetPlayer(args[0], t); offset = 1; } + Static.AssertPlayerNonNull(p, t); MCLocation loc = ObjectGenerator.GetGenerator().location(args[offset], p.getWorld(), t); - - String[] lines = new String[4]; + MCBlock block = loc.getBlock(); + if(!block.isSign()) { + return CVoid.VOID; + } + MCSign sign = block.getSign(); + String[] lines = sign.getLines(); if(args.length == 2 || args.length == 3) { - //Lines are in an array - CArray lineArray = Static.getArray(args[1 + offset], t); - if(lineArray.size() != 4) { - throw new ConfigRuntimeException("Line array must have 4 elements.", ExceptionType.CastException, t); + CArray signArray = ArgumentValidation.getArray(args[1 + offset], t); + if(signArray.isAssociative()) { + if(signArray.containsKey("signtext")) { + Mixed possibleLines = signArray.get("signtext", t); + if(possibleLines.isInstanceOf(CArray.TYPE)) { + CArray frontLines = (CArray) possibleLines; + if(frontLines.size() > 4) { + throw new CREFormatException("Sign text array cannot have more than 4 elements.", t); + } + for(int i = 0; i < frontLines.size(); i++) { + if(!(frontLines.get(i, t) instanceof CNull)) { + sign.setLine(i, frontLines.get(i, t).val()); + } + } + } else { + throw new CREFormatException("Expected array for sign text", t); + } + } + if(signArray.containsKey("glowing")) { + sign.setGlowingText(ArgumentValidation.getBooleanObject(signArray.get("glowing", t), t)); + } + if(signArray.containsKey("color")) { + Mixed dye = signArray.get("color", t); + if(!(dye instanceof CNull)) { + try { + sign.setDyeColor(MCDyeColor.valueOf(dye.val())); + } catch (IllegalArgumentException ex) { + throw new CREFormatException("Invalid color for sign text", t); + } + } + } + MCSignText backText = sign.getBackText(); + if(backText != null) { + if(signArray.containsKey("backtext")) { + Mixed possibleLines = signArray.get("backtext", t); + if(possibleLines.isInstanceOf(CArray.TYPE)) { + CArray backLines = (CArray) possibleLines; + if(backLines.size() > 4) { + throw new CREFormatException("Sign back text array cannot have more than 4 elements.", t); + } + for(int i = 0; i < backLines.size(); i++) { + if(!(backLines.get(i, t) instanceof CNull)) { + backText.setLine(i, backLines.get(i, t).val()); + } + } + } else { + throw new CREFormatException("Expected array for sign back text", t); + } + } + if(signArray.containsKey("backglowing")) { + backText.setGlowingText(ArgumentValidation.getBooleanObject(signArray.get("backglowing", t), t)); + } + if(signArray.containsKey("backcolor")) { + Mixed dye = signArray.get("backcolor", t); + if(!(dye instanceof CNull)) { + try { + backText.setDyeColor(MCDyeColor.valueOf(dye.val())); + } catch (IllegalArgumentException ex) { + throw new CREFormatException("Invalid color for sign back text", t); + } + } + } + } + p.sendSignTextChange(sign); + return CVoid.VOID; + } else { + // Lines are in an array + if(signArray.size() > 4) { + throw new CREFormatException("Sign array cannot have more than 4 elements.", t); + } + for(int i = 0; i < signArray.size(); i++) { + Mixed line = signArray.get(i, t); + if(!(line instanceof CNull)) { + lines[i] = line.val(); + } + } } - lines[0] = lineArray.get(0, t).val(); - lines[1] = lineArray.get(1, t).val(); - lines[2] = lineArray.get(2, t).val(); - lines[3] = lineArray.get(3, t).val(); } else { - //Lines are in different arguments - lines[0] = args[1 + offset].val(); - lines[1] = args[2 + offset].val(); - lines[2] = args[3 + offset].val(); - lines[3] = args[4 + offset].val(); + // Lines are in different arguments + for(int i = 0; i < 4; i++) { + Mixed line = args[i + 1 + offset]; + if(!(line instanceof CNull)) { + lines[i] = line.val(); + } + } } p.sendSignTextChange(loc, lines); @@ -3674,22 +4262,27 @@ public Integer[] numArgs() { @Override public String docs() { - return "void {[player], locationArray, 1, 2, 3, 4 | [player], locationArray, lineArray} Changes a signs' text, but only temporarily, and only for the specified player." - + " This can be used to \"fake\" sign text for a player. LineArray, if used, must have 4 elements."; + return "void {[player], locationArray, 1, 2, 3, 4 | [player], locationArray, array}" + + " Changes a sign's text only for the specified player. This will not change the sign in the world." + + " If a normal array is used it cannot have more than 4 elements." + + " If a line is null, the existing line will still be displayed." + + " An associative array of sign data may be sent instead. This is the same as sign item meta," + + " and can include the keys: 'signtext', 'color', 'glowing' (MC 1.17.1+)," + + " 'backtext' (MC 1.20.1+), 'backcolor' (MC 1.20.1+), and 'backglowing' (MC 1.20.1+)."; } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } } @api(environments = {CommandHelperEnvironment.class}) - public static class psend_block_change extends AbstractFunction { + public static class psend_block_change extends AbstractFunction implements Optimizable { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.FormatException, ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CREFormatException.class, CREPlayerOfflineException.class}; } @Override @@ -3703,16 +4296,36 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCPlayer p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); int offset = 0; - if (args.length == 3) { + if(args.length == 3) { p = Static.GetPlayer(args[0], t); offset = 1; } - MCLocation loc = ObjectGenerator.GetGenerator().location(args[0 + offset], p.getWorld(), t); - MCItemStack item = Static.ParseItemNotation(getName(), args[1 + offset].val(), 1, t); - p.sendBlockChange(loc, item.getType().getType(), (byte) item.getData().getData()); + Static.AssertPlayerNonNull(p, t); + MCLocation loc = ObjectGenerator.GetGenerator().location(args[offset], p.getWorld(), t); + Mixed cdata = args[1 + offset]; + MCBlockData data; + if(cdata instanceof CNull) { + data = loc.getBlock().getBlockData(); + } else { + try { + data = Static.getServer().createBlockData(cdata.val().toLowerCase()); + } catch (IllegalArgumentException ex) { + String value = cdata.val(); + if(value.contains(":") && value.length() <= 6) { + data = Static.ParseItemNotation(getName(), cdata.val(), 1, t).getType().createBlockData(); + } else { + throw new CREFormatException("Invalid block format: " + value, t); + } + } + if(!data.getMaterial().isBlock()) { + throw new CREIllegalArgumentException("The value \"" + cdata.val() + + "\" is not a valid block material.", t); + } + } + p.sendBlockChange(loc, data); return CVoid.VOID; } @@ -3728,13 +4341,120 @@ public Integer[] numArgs() { @Override public String docs() { - return "void {[player], locationArray, itemID} Changes a block, but only temporarily, and only for the specified player." - + " This can be used to \"fake\" blocks for a player. ItemID is in the 1[:1] data format."; + return "void {[player], locationArray, block} Changes a block temporarily for the specified player." + + " This can be used to \"fake\" blocks for a player. These illusory blocks will disappear when" + + " the client updates them, most often by clicking on them or reloading the chunks." + + " Sending null will reset the block to its existing state in the world." + + " A block type or blockdata format is supported. (see set_blockdata_string())"; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_1; + } + + @Override + public ParseTree optimizeDynamic(Target t, Environment env, + Set> envs, + List children, FileOptions fileOptions) + throws ConfigCompileException, ConfigRuntimeException { + if(children.size() < 2) { + return null; + } + String value = children.get(children.size() - 1).getData().val(); + if(value.contains(":") && value.length() <= 6) { // longest valid item format without being blockdata string + env.getEnv(CompilerEnvironment.class).addCompilerWarning(fileOptions, + new CompilerWarning("The 1:1 format is deprecated in " + getName(), t, null)); + } + return null; + } + + @Override + public Set optimizationOptions() { + return EnumSet.of(Optimizable.OptimizationOption.OPTIMIZE_DYNAMIC); + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class psend_block_damage extends AbstractFunction { + + @Override + public String getName() { + return "psend_block_damage"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{2, 3, 4}; + } + + @Override + public String docs() { + return "void {[player], locationArray, progress, [entity]} Sends the player fake block damage progress." + + " Progress is a percentage from 0.0 to 1.0, with 0.0 clearing any block damage." + + " Alternatively you can specify the discrete damage state as an integer from 0 to 10." + + " If a source entity UUID is specified, it will be as if that entity is damaging the block," + + " otherwise the player will be used. If given null, damage will disregard source entity." + + " If that entity damages a different block, the previous block's damage will be cleared." + + " If unchanged, the damage will clear on its own after 20 seconds." + + " On versions prior to Spigot 1.19.4 or Paper 1.19.2, the source entity will always be the" + + " player this block damage is being sent to."; + } + + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class, CREFormatException.class, CREInvalidWorldException.class, + CRELengthException.class, CREPlayerOfflineException.class, CRERangeException.class}; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCPlayer p; + MCLocation location; + float progress; + MCEntity entity; + int argOffset = 0; + if(args.length == 2 || args.length == 3 && args[0] instanceof CArray) { + p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); + Static.AssertPlayerNonNull(p, t); + } else { + p = Static.GetPlayer(args[0], t); + argOffset = 1; + } + location = ObjectGenerator.GetGenerator().location(args[argOffset], p.getWorld(), t); + Mixed progressArg = args[1 + argOffset]; + if(progressArg instanceof CInt) { + progress = ArgumentValidation.getInt(progressArg, t) / 10.0F; + } else { + progress = (float) ArgumentValidation.getDouble(progressArg, t); + } + if(progress < 0.0 || progress > 1.0) { + throw new CRERangeException("Block damage progress must be 0.0 to 1.0 (or 0 - 10).", t); + } + if(args.length < 3 + argOffset) { + entity = p; + } else if(args[2 + argOffset] instanceof CNull) { + entity = null; + } else { + entity = Static.getEntity(args[2 + argOffset], t); + } + p.sendBlockDamage(location, progress, entity); + return CVoid.VOID; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_5; + } + + @Override + public boolean isRestricted() { + return true; } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public Boolean runAsync() { + return false; } } @@ -3742,8 +4462,8 @@ public CHVersion since() { public static class phunger extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class}; } @Override @@ -3757,12 +4477,13 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCPlayer p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); - if (args.length == 1) { + if(args.length == 1) { p = Static.GetPlayer(args[0], t); } - return new CInt(p.getHunger(), t); + Static.AssertPlayerNonNull(p, t); + return new CInt(p.getFoodLevel(), t); } @Override @@ -3777,12 +4498,12 @@ public Integer[] numArgs() { @Override public String docs() { - return "int {[player]} Returns the player's hunger level"; + return "int {[player]} Returns the player's hunger level."; } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } } @@ -3790,8 +4511,8 @@ public CHVersion since() { public static class set_phunger extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.RangeException, ExceptionType.PlayerOfflineException, ExceptionType.CastException}; + public Class[] thrown() { + return new Class[]{CRERangeException.class, CREPlayerOfflineException.class, CRECastException.class}; } @Override @@ -3805,15 +4526,16 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCPlayer p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); - int hunger, hungerIndex = 0; - if (args.length == 2) { + int hungerIndex = 0; + if(args.length == 2) { p = Static.GetPlayer(args[0], t); hungerIndex = 1; } - hunger = Static.getInt32(args[hungerIndex], t); - p.setHunger(hunger); + Static.AssertPlayerNonNull(p, t); + int hunger = ArgumentValidation.getInt32(args[hungerIndex], t); + p.setFoodLevel(hunger); return CVoid.VOID; } @@ -3829,12 +4551,12 @@ public Integer[] numArgs() { @Override public String docs() { - return "void {[player], hunger} Sets a player's hunger level"; + return "void {[player], hunger} Sets a player's hunger level, where 0 is empty and 20 is full."; } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } } @@ -3842,8 +4564,8 @@ public CHVersion since() { public static class psaturation extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.RangeException, ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CRERangeException.class, CREPlayerOfflineException.class}; } @Override @@ -3857,9 +4579,9 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCPlayer p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); - if (args.length == 1) { + if(args.length == 1) { p = Static.GetPlayer(args[0], t); } Static.AssertPlayerNonNull(p, t); @@ -3878,12 +4600,12 @@ public Integer[] numArgs() { @Override public String docs() { - return "double {[player]} Returns the player's saturation level"; + return "double {[player]} Returns the player's food saturation level."; } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } } @@ -3891,8 +4613,8 @@ public CHVersion since() { public static class set_psaturation extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.RangeException, ExceptionType.PlayerOfflineException, ExceptionType.CastException}; + public Class[] thrown() { + return new Class[]{CRERangeException.class, CREPlayerOfflineException.class, CRECastException.class}; } @Override @@ -3906,15 +4628,16 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCPlayer p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); float saturation; int saturationIndex = 0; - if (args.length == 2) { + if(args.length == 2) { p = Static.GetPlayer(args[0], t); saturationIndex = 1; } - saturation = (float) Static.getDouble(args[saturationIndex], t); + Static.AssertPlayerNonNull(p, t); + saturation = (float) ArgumentValidation.getDouble(args[saturationIndex], t); p.setSaturation(saturation); return CVoid.VOID; } @@ -3931,12 +4654,14 @@ public Integer[] numArgs() { @Override public String docs() { - return "void {[player], saturation} "; + return "void {[player], saturation} Set's the player's food saturation level." + + " If this is above 0.0 and the player's health is below max, the player will experience fast" + + " health regeneration."; } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } } @@ -3955,28 +4680,36 @@ public Integer[] numArgs() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { MCOfflinePlayer player = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); - if (args.length == 1) { - player = Static.getServer().getOfflinePlayer(args[0].val()); + if(args.length == 1) { + player = Static.GetUser(args[0].val(), t); + } else if(player == null) { + throw new CREInsufficientArgumentsException(this.getName() + " requires a player as first argument when ran from console", t); + } + if(player == null) { + throw new CRENotFoundException( + this.getName() + " failed to get an offline player (are you running in cmdline mode?)", t); } MCLocation loc = player.getBedSpawnLocation(); - if (loc == null) { + if(loc == null) { return CNull.NULL; } else { - return ObjectGenerator.GetGenerator().location(loc, false); + return ObjectGenerator.GetGenerator().location(loc); } } @Override public String docs() { - return "array {[playerName]} Returns an array of x, y, z, coords of the bed of the player specified, or the player running the command otherwise." - + "The array returned will also include the bed's world in index 3 of the array. This is set when a player sleeps or by set_pbed_location."; + return "array {[playerName]} Returns a location array of the bed block the player last slept in." + + " The player will normally respawn next to this bed if they die." + + " However, this respawn location can be forcibly be set by plugins or commands to any location," + + " like when using set_pbed_location()."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{CREInsufficientArgumentsException.class, CRENotFoundException.class}; } @Override @@ -3985,8 +4718,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } @Override @@ -4005,18 +4738,21 @@ public String getName() { @Override public Integer[] numArgs() { - return new Integer[]{1, 2, 3, 4}; + return new Integer[]{1, 2, 3, 4, 5}; } @Override public String docs() { - return "boolean {[player], locationArray | [player], x, y, z} Sets the location of the bed of the player to the specified coordinates." - + " If player is omitted, the current player is used."; + return "void {[player], locationArray, [forced] | [player], x, y, z, [forced]} Sets the respawn location" + + " of a player. If player is omitted, the current player is used. The specified location should be" + + " the block below the respawn location. If forced is false, it will respawn the player next to" + + " that location only if a bed found is found there. (forced defaults to true)"; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException, ExceptionType.LengthException, ExceptionType.PlayerOfflineException, ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CRECastException.class, CRELengthException.class, CREPlayerOfflineException.class, + CREFormatException.class, CRENullPointerException.class}; } @Override @@ -4025,8 +4761,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } @Override @@ -4035,69 +4771,90 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { MCCommandSender p = env.getEnv(CommandHelperEnvironment.class).GetCommandSender(); String pname = null; - double x; - double y; - double z; MCPlayer m = null; - MCLocation l = null; - if (args.length == 1) { - if (args[0] instanceof CArray) { - CArray ca = (CArray) args[0]; - l = ObjectGenerator.GetGenerator().location(ca, (p instanceof MCPlayer ? ((MCPlayer) p).getWorld() : null), t); - x = Static.getNumber(ca.get(0, t), t); - y = Static.getNumber(ca.get(1, t), t); - z = Static.getNumber(ca.get(2, t), t); - if (p instanceof MCPlayer) { + MCLocation l; + int locationIndex; + boolean forced = true; + + if(args.length == 1) { + if(args[0].isInstanceOf(CArray.TYPE)) { + if(p instanceof MCPlayer) { m = ((MCPlayer) p); } - + locationIndex = 0; + } else { + throw new CRECastException("Expecting an array in set_pbed_location", t); + } + } else if(args.length == 2) { + if(args[1].isInstanceOf(CArray.TYPE)) { + pname = args[0].val(); + locationIndex = 1; + } else if(args[0].isInstanceOf(CArray.TYPE)) { + if(p instanceof MCPlayer) { + m = ((MCPlayer) p); + } + locationIndex = 0; + forced = ArgumentValidation.getBoolean(args[1], t); } else { - throw new ConfigRuntimeException("Expecting an array at parameter 1 of set_pbed_location", - ExceptionType.CastException, t); + throw new CRECastException("Expecting an array in set_pbed_location", t); } - } else if (args.length == 2) { - if (args[1] instanceof CArray) { - CArray ca = (CArray) args[1]; + } else if(args.length == 3) { + if(args[1].isInstanceOf(CArray.TYPE)) { pname = args[0].val(); - l = ObjectGenerator.GetGenerator().location(ca, Static.GetPlayer(pname, t).getWorld(), t); - x = l.getX(); - y = l.getY(); - z = l.getZ(); + locationIndex = 1; + forced = ArgumentValidation.getBoolean(args[2], t); } else { - throw new ConfigRuntimeException("Expecting parameter 2 to be an array in set_pbed_location", - ExceptionType.CastException, t); + if(p instanceof MCPlayer) { + m = (MCPlayer) p; + } + locationIndex = 0; } - } else if (args.length == 3) { - if (p instanceof MCPlayer) { - m = (MCPlayer) p; + } else if(args.length == 4) { + try { + m = Static.GetPlayer(args[0], t); + locationIndex = 1; + } catch (ConfigRuntimeException e) { + if(p instanceof MCPlayer) { + m = (MCPlayer) p; + } + locationIndex = 0; + forced = ArgumentValidation.getBoolean(args[3], t); } - x = Static.getNumber(args[0], t); - y = Static.getNumber(args[1], t); - z = Static.getNumber(args[2], t); - l = m.getLocation(); } else { m = Static.GetPlayer(args[0], t); - x = Static.getNumber(args[1], t); - y = Static.getNumber(args[2], t); - z = Static.getNumber(args[3], t); - l = m.getLocation(); + locationIndex = 1; + forced = ArgumentValidation.getBoolean(args[4], t); } - if (m == null && pname != null) { + + if(m == null && pname != null) { m = Static.GetPlayer(pname, t); } Static.AssertPlayerNonNull(m, t); - if (!l.getWorld().exists()) { - throw new ConfigRuntimeException("The world specified does not exist.", ExceptionType.InvalidWorldException, t); - }; - m.setBedSpawnLocation(StaticLayer.GetLocation(l.getWorld(), x, y + 1, z, m.getLocation().getYaw(), m.getLocation().getPitch())); + + if(args[locationIndex].isInstanceOf(CArray.TYPE)) { + CArray ca = (CArray) args[locationIndex]; + l = ObjectGenerator.GetGenerator().location(ca, m.getWorld(), t); + l.add(0, 1, 0); // someone decided to match ploc() here + } else { + l = m.getLocation(); + if(l == null) { + throw new CRENullPointerException( + "The given player has a null location (are you running from cmdline mode?)", t); + } + l.setX(ArgumentValidation.getNumber(args[locationIndex], t)); + l.setY(ArgumentValidation.getNumber(args[locationIndex + 1], t) + 1); + l.setZ(ArgumentValidation.getNumber(args[locationIndex + 2], t)); + } + + m.setBedSpawnLocation(l, forced); return CVoid.VOID; } } - @api(environments={CommandHelperEnvironment.class}) + @api(environments = {CommandHelperEnvironment.class}) public static class pvehicle extends AbstractFunction { @Override @@ -4112,12 +4869,13 @@ public Integer[] numArgs() { @Override public String docs() { - return "mixed {[player]} Returns ID of vehicle which player is in or null if player is outside the vehicle"; - } + return "string {[player]} Returns the UUID of the vehicle which the player is riding," + + " or null if player is not riding a vehicle."; + } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class}; } @Override @@ -4126,8 +4884,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } @Override @@ -4136,22 +4894,22 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCPlayer p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); - if (args.length == 1) { + if(args.length == 1) { p = Static.GetPlayer(args[0].val(), t); } Static.AssertPlayerNonNull(p, t); - if (p.isInsideVehicle() == false) { + if(!p.isInsideVehicle()) { return CNull.NULL; } - return new CInt(p.getVehicle().getEntityId(), t); + return new CString(p.getVehicle().getUniqueId().toString(), t); } } - @api(environments={CommandHelperEnvironment.class}) + @api(environments = {CommandHelperEnvironment.class}) public static class pvehicle_leave extends AbstractFunction { @Override @@ -4166,13 +4924,14 @@ public Integer[] numArgs() { @Override public String docs() { - return "boolean {[player]} Leave vehicle by player or return false if player is outside the vehicle"; + return "boolean {[player]} Forces a player to leave their vehicle." + + " This returns false if the player is not riding a vehicle."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException}; - } + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class}; + } @Override public boolean isRestricted() { @@ -4180,8 +4939,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } @Override @@ -4190,9 +4949,9 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCPlayer p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); - if (args.length == 1) {; + if(args.length == 1) { p = Static.GetPlayer(args[0].val(), t); } Static.AssertPlayerNonNull(p, t); @@ -4201,12 +4960,12 @@ public Construct exec(Target t, Environment environment, Construct... args) thro } } - @api(environments={CommandHelperEnvironment.class}) + @api(environments = {CommandHelperEnvironment.class}) public static class get_offline_players extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{}; } @Override @@ -4220,12 +4979,17 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, - Construct... args) throws ConfigRuntimeException { - MCServer s = environment.getEnv(CommandHelperEnvironment.class).GetCommandSender().getServer(); + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCServer s = Static.getServer(); CArray ret = new CArray(t); - for (MCOfflinePlayer offp : s.getOfflinePlayers()) { - ret.push(new CString(offp.getName(), t)); + // This causes the function to return an empty array for a fake/null server. + if(s != null) { + MCOfflinePlayer[] players = s.getOfflinePlayers(); + if(players != null) { + for(MCOfflinePlayer offp : players) { + ret.push(new CString(offp.getName(), t), t); + } + } } return ret; } @@ -4246,25 +5010,25 @@ public String docs() { } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ - new ExampleScript("Simple usage", "get_offline_players()", "{Bill, Bob, Joe, Fred}") + new ExampleScript("Simple usage", "get_offline_players()", "{Bill, Bob, Joe, Fred}") }; } } - @api(environments={CommandHelperEnvironment.class}) + @api(environments = {CommandHelperEnvironment.class}) public static class phas_played extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{}; } @Override @@ -4278,11 +5042,9 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, - Construct... args) throws ConfigRuntimeException { - MCServer s = environment.getEnv(CommandHelperEnvironment.class).GetCommandSender().getServer(); - MCOfflinePlayer offp = s.getOfflinePlayer(args[0].val()); - return CBoolean.get(offp.hasPlayedBefore()); + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCOfflinePlayer offp = Static.GetUser(args[0].val(), t); + return CBoolean.get(offp != null && offp.hasPlayedBefore()); } @Override @@ -4298,30 +5060,32 @@ public Integer[] numArgs() { @Override public String docs() { return "boolean {player} Returns whether the given player has ever been on this server." - + " This will not throw a PlayerOfflineException, so the name must be exact."; + + " The player argument can be a UUID or a name. But if given a name, it must be exact."; } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ - new ExampleScript("Demonstrates a player that has played", "phas_played('Notch')", ":true"), - new ExampleScript("Demonstrates a player that has not played", "phas_played('Herobrine')", ":false") + new ExampleScript("Demonstrates a player that has played", + "phas_played('Notch')", ":true"), + new ExampleScript("Demonstrates a player that has not played", + "phas_played('Herobrine')", ":false") }; } } - @api(environments={CommandHelperEnvironment.class}) + @api(environments = {CommandHelperEnvironment.class}) public static class pfirst_played extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{}; } @Override @@ -4335,16 +5099,18 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, - Construct... args) throws ConfigRuntimeException { - MCCommandSender cs = environment.getEnv(CommandHelperEnvironment.class).GetCommandSender(); - MCOfflinePlayer op = null; - if (args.length == 1) { - op = cs.getServer().getOfflinePlayer(args[0].val()); + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCOfflinePlayer op; + if(args.length == 1) { + op = Static.GetUser(args[0].val(), t); } else { - op = cs.getServer().getOfflinePlayer(cs.getName()); + op = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); + if(op == null) { + throw new CREInsufficientArgumentsException(this.getName() + + " requires a player argument when ran from a non-player", t); + } } - return new CInt(op.getFirstPlayed(), t); + return new CInt((op == null ? 0 : op.getFirstPlayed()), t); // Return 0 for fake/null command senders. } @Override @@ -4354,36 +5120,39 @@ public String getName() { @Override public Integer[] numArgs() { - return new Integer[]{0,1}; + return new Integer[]{0, 1}; } @Override public String docs() { - return "int {[player]} Returns the time this player first logged onto this server, or 0 if they never have." - + " This will not throw a PlayerOfflineException, so the name must be exact."; + return "int {[player]} Returns the unix time stamp, in milliseconds, that this player first logged onto" + + " this server, or 0 if they never have." + + " The player argument can be a UUID or a name. But if given a name, it must be exact."; } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ - new ExampleScript("Demonstrates a player that has played", "pfirst_played('Notch')", ":13558362167593"), - new ExampleScript("Demonstrates a player that has not played", "pfirst_played('Herobrine')", ":0") + new ExampleScript("Demonstrates a player that has played", + "pfirst_played('Notch')", "13558362167593"), + new ExampleScript("Demonstrates a player that has not played", + "pfirst_played('Herobrine')", "0") }; } } - @api(environments={CommandHelperEnvironment.class}) + @api(environments = {CommandHelperEnvironment.class}) public static class plast_played extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{}; } @Override @@ -4397,16 +5166,18 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, - Construct... args) throws ConfigRuntimeException { - MCCommandSender cs = environment.getEnv(CommandHelperEnvironment.class).GetCommandSender(); - MCOfflinePlayer op = null; - if (args.length == 1) { - op = cs.getServer().getOfflinePlayer(args[0].val()); + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCOfflinePlayer op; + if(args.length == 1) { + op = Static.GetUser(args[0].val(), t); } else { - op = cs.getServer().getOfflinePlayer(cs.getName()); + op = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); + if(op == null) { + throw new CREInsufficientArgumentsException(this.getName() + + " requires a player argument when ran from a non-player", t); + } } - return new CInt(op.getLastPlayed(), t); + return new CInt((op == null ? 0 : op.getLastPlayed()), t); // Return 0 for fake/null command senders. } @Override @@ -4416,35 +5187,38 @@ public String getName() { @Override public Integer[] numArgs() { - return new Integer[]{0,1}; + return new Integer[]{0, 1}; } @Override public String docs() { - return "int {[player]} Returns the time this player was last seen on this server, or 0 if they never were." - + " This will not throw a PlayerOfflineException, so the name must be exact."; + return "int {[player]} Returns the unix time stamp, in milliseconds, that this player was last seen on this" + + " server, or 0 if they never were." + + " The player argument can be a UUID or a name. But if given a name, it must be exact."; } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ - new ExampleScript("Demonstrates a player that has played", "plast_played('Notch')", ":13558362167593"), - new ExampleScript("Demonstrates a player that has not played", "plast_played('Herobrine')", ":0") + new ExampleScript("Demonstrates a player that has played", + "plast_played('Notch')", "13558362167593"), + new ExampleScript("Demonstrates a player that has not played", + "plast_played('Herobrine')", "0") }; } } - @api - public static class get_player_from_entity_id extends AbstractFunction { + @api(environments = {CommandHelperEnvironment.class}) + public static class plast_known_name extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException}; + public Class[] thrown() { + return new Class[]{CRELengthException.class}; } @Override @@ -4458,18 +5232,21 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - int id = Static.getInt32(args[0], t); - try { - return new CString(((MCPlayer) Static.getLivingEntity(id, t)).getName(), t); - } catch (Exception exception) { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCOfflinePlayer p = Static.GetUser(args[0].val(), t); + if(p == null) { return CNull.NULL; } + String name = p.getName(); + if(name == null) { + return CNull.NULL; + } + return new CString(name, t); } @Override public String getName() { - return "get_player_from_entity_id"; + return "plast_known_name"; } @Override @@ -4479,13 +5256,2305 @@ public Integer[] numArgs() { @Override public String docs() { - return "string {entityID} Given an entity ID that represents a player, returns that player's name, or" - + " null if the entity ID isn't a player's entity ID."; + return "string {uuid} Returns the name for this player the last time they were on the server." + + " Return null if the player has never been on the server. This is not guaranteed to be their" + + " current player name. This is read directly from the player data file for offline players."; } @Override - public Version since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_2; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class save_players extends AbstractFunction { + + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + Static.getServer().savePlayers(); + return CVoid.VOID; + } + + @Override + public String getName() { + return "save_players"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{0}; + } + + @Override + public String docs() { + return "void {} Saves current players to disk."; + } + + @Override + public Version since() { + return MSVersion.V3_3_1; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + @seealso({set_pflight.class, pflying.class}) + public static class set_pflying extends AbstractFunction { + + @Override + public String getName() { + return "set_pflying"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1, 2}; + } + + @Override + public String docs() { + return "void {[player], flying} Sets the flying state for the player." + + "Requires player to have the ability to fly, which is set with set_pflight()."; + } + + @Override + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CREIllegalArgumentException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCPlayer p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); + boolean flight; + if(args.length == 1) { + flight = ArgumentValidation.getBoolean(args[0], t); + } else { + p = Static.GetPlayer(args[0], t); + flight = ArgumentValidation.getBoolean(args[1], t); + } + Static.AssertPlayerNonNull(p, t); + if(!p.getAllowFlight()) { + throw new CREIllegalArgumentException("Player must have the ability to fly. Set with set_pflight()", t); + } + // This is needed in order for the player to enter flight mode whilst standing on the ground. + if(flight + && p.isOnGround()) { + Vector3D v = p.getVelocity(); + // 0.08 was chosen as it does not change the player's position, whereas higher values do. + Vector3D newV = v.add(new Vector3D(v.X(), v.Y() + 0.08, v.Z())); + p.setVelocity(newV); + } + p.setFlying(flight); + // We only want to set whether the player is flying; not whether the player can fly. + p.setAllowFlight(true); + return CVoid.VOID; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_1; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + @seealso({set_pflying.class}) + public static class pflying extends AbstractFunction { + + @Override + public String getName() { + return "pflying"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{0, 1}; + } + + @Override + public String docs() { + return "boolean {[player]} Gets the flying state for the player."; + } + + @Override + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CRELengthException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCPlayer p; + if(args.length == 1) { + p = Static.GetPlayer(args[0], t); + } else { + p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); + Static.AssertPlayerNonNull(p, t); + } + return CBoolean.get(p.isFlying()); + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_4; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class pspectator_target extends AbstractFunction { + + @Override + public String getName() { + return "pspectator_target"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{0, 1}; + } + + @Override + public String docs() { + return "string {[player]} Gets the entity UUID that a spectator is viewing. If the player isn't spectating" + + " from an entity, null is returned. If the player isn't in spectator mode, an" + + " IllegalArgumentException is thrown."; + } + + @Override + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CREIllegalArgumentException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCPlayer p; + if(args.length == 0) { + p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); + } else { + p = Static.GetPlayer(args[0], t); + } + Static.AssertPlayerNonNull(p, t); + if(p.getGameMode() != MCGameMode.SPECTATOR) { + throw new CREIllegalArgumentException("Player must be in spectator mode.", t); + } + MCEntity e = p.getSpectatorTarget(); + if(e == null) { + return CNull.NULL; + } + return new CString(e.getUniqueId().toString(), t); + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_1; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class set_pspectator_target extends AbstractFunction { + + @Override + public String getName() { + return "set_pspectator_target"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1, 2}; + } + + @Override + public String docs() { + return "void {[player], entity} Sets the entity for the player to spectate. If set to null, the" + + " spectator will stop following an entity. If the player is not in spectator mode an" + + " IllegalArgumentException is thrown."; + } + + @Override + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CREIllegalArgumentException.class, + CREBadEntityException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCPlayer p; + int offset = 0; + if(args.length == 1) { + p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); + } else { + p = Static.GetPlayer(args[0], t); + offset = 1; + } + Static.AssertPlayerNonNull(p, t); + if(p.getGameMode() != MCGameMode.SPECTATOR) { + throw new CREIllegalArgumentException("Player must be in spectator mode.", t); + } + if(args[offset] instanceof CNull) { + p.setSpectatorTarget(null); + } else { + p.setSpectatorTarget(Static.getEntity(args[offset], t)); + } + return CVoid.VOID; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_1; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + @seealso({com.laytonsmith.core.functions.Environment.play_sound.class}) + public static class stop_sound extends AbstractFunction { + + @Override + public Class[] thrown() { + return new Class[]{CREInvalidWorldException.class, CRELengthException.class, CREFormatException.class, + CREPlayerOfflineException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, com.laytonsmith.core.environments.Environment environment, Mixed... args) + throws ConfigRuntimeException { + + MCPlayer p = Static.GetPlayer(args[0], t); + + String soundName; + String categoryName = null; + if(args[1].isInstanceOf(CArray.TYPE)) { + CArray soundArray = (CArray) args[1]; + if(!soundArray.isAssociative()) { + throw new CRECastException("Expected an associative array", t); + } + soundName = soundArray.get("sound", t).val(); + if(soundArray.containsKey("category")) { + categoryName = soundArray.get("category", t).val(); + } + } else { + soundName = args[1].val(); + } + if(args.length == 3) { + categoryName = args[2].val(); + } + + MCSound sound; + try { + sound = MCSound.valueOf(soundName.toUpperCase()); + } catch (IllegalArgumentException iae) { + throw new CREFormatException("Sound name '" + soundName + "' is invalid.", t); + } + + MCSoundCategory category = null; + if(categoryName != null) { + try { + category = MCSoundCategory.valueOf(categoryName.toUpperCase()); + } catch (IllegalArgumentException iae) { + throw new CREFormatException("Sound category '" + categoryName + "' is invalid.", t); + } + } + p.stopSound(sound, category); + + return CVoid.VOID; + } + + @Override + public String getName() { + return "stop_sound"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{2, 3}; + } + + @Override + public String docs() { + return "void {player, sound, [category]} Stops the specified sound for a player."; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_2; + } + + } + + @api(environments = {CommandHelperEnvironment.class}) + @seealso({com.laytonsmith.core.functions.Environment.play_sound.class}) + public static class stop_sound_category extends AbstractFunction { + + @Override + public Class[] thrown() { + return new Class[]{CRELengthException.class, CREFormatException.class, CREPlayerOfflineException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, com.laytonsmith.core.environments.Environment environment, Mixed... args) + throws ConfigRuntimeException { + + MCPlayer p = Static.GetPlayer(args[0], t); + MCSoundCategory category; + try { + category = MCSoundCategory.valueOf(args[1].val().toUpperCase()); + } catch (IllegalArgumentException ex) { + throw new CREFormatException("Sound category '" + args[1].val() + "' is invalid.", t); + } + p.stopSound(category); + return CVoid.VOID; + } + + @Override + public String getName() { + return "stop_sound_category"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{2}; + } + + @Override + public String docs() { + return "void {player, category} Stops all sounds in a category for a player. (MC 1.19+)"; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_5; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + @seealso({com.laytonsmith.core.functions.Environment.play_named_sound.class}) + public static class stop_named_sound extends AbstractFunction { + + @Override + public Class[] thrown() { + return new Class[]{CREInvalidWorldException.class, CRELengthException.class, CREFormatException.class, + CREPlayerOfflineException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, com.laytonsmith.core.environments.Environment environment, Mixed... args) + throws ConfigRuntimeException { + + MCPlayer p = Static.GetPlayer(args[0], t); + String soundName; + String categoryName = null; + if(args[1].isInstanceOf(CArray.TYPE)) { + CArray soundArray = (CArray) args[1]; + if(!soundArray.isAssociative()) { + throw new CRECastException("Expected an associative array or sound string", t); + } + soundName = soundArray.get("sound", t).val(); + if(soundArray.containsKey("category")) { + categoryName = soundArray.get("category", t).val(); + } + } else { + soundName = args[1].val(); + } + if(args.length == 3) { + categoryName = args[2].val(); + } + MCSoundCategory category = null; + if(categoryName != null) { + try { + category = MCSoundCategory.valueOf(categoryName.toUpperCase()); + } catch (IllegalArgumentException iae) { + throw new CREFormatException("Sound category '" + categoryName + "' is invalid.", t); + } + } + try { + p.stopSound(soundName, category); + } catch(Exception ex) { + throw new CREFormatException(ex.getMessage(), t); + } + + return CVoid.VOID; + } + + @Override + public String getName() { + return "stop_named_sound"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{2, 3}; + } + + @Override + public String docs() { + return "void {player, sound, [category]} Stops the specified sound for the given player."; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_2; + } + + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class pcooldown extends AbstractFunction { + + @Override + public Class[] thrown() { + return new Class[]{CRELengthException.class, CREFormatException.class, CREPlayerOfflineException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCPlayer p; + String materialName; + if(args.length == 2) { + p = Static.GetPlayer(args[0], t); + materialName = args[1].val(); + } else { + p = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); + Static.AssertPlayerNonNull(p, t); + materialName = args[0].val(); + } + + MCMaterial mat = StaticLayer.GetMaterial(materialName); + if(mat == null) { + throw new CREFormatException("Material name is invalid.", t); + } + + return new CInt(p.getCooldown(mat), t); + } + + @Override + public String getName() { + return "pcooldown"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1, 2}; + } + + @Override + public String docs() { + return "int {[player], material} Gets the time left on the player's cooldown for the specified item type." + + " This returns an integer representing the time in server ticks until any items of this material" + + " can be used again by this player."; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_2; + } + + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class set_pcooldown extends AbstractFunction { + + @Override + public Class[] thrown() { + return new Class[]{CRELengthException.class, CREFormatException.class, CREPlayerOfflineException.class, + CRERangeException.class, CRECastException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCPlayer p; + String materialName; + int cooldown; + if(args.length == 3) { + p = Static.GetPlayer(args[0], t); + materialName = args[1].val(); + cooldown = ArgumentValidation.getInt32(args[2], t); + } else { + p = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); + Static.AssertPlayerNonNull(p, t); + materialName = args[0].val(); + cooldown = ArgumentValidation.getInt32(args[1], t); + } + + MCMaterial mat = StaticLayer.GetMaterial(materialName); + if(mat == null) { + throw new CREFormatException("Material name is invalid.", t); + } + + if(cooldown < 0) { + throw new CRERangeException("Cooldowns cannot be negative.", t); + } + + p.setCooldown(mat, cooldown); + return CVoid.VOID; + } + + @Override + public String getName() { + return "set_pcooldown"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{2, 3}; + } + + @Override + public String docs() { + return "int {[player], material, cooldown} Sets the player's cooldown time for the specified item type." + + " The cooldown must be a positive integer representing server ticks."; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_2; + } + + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class pattack_cooldown extends AbstractFunction { + + @Override + public Class[] thrown() { + return new Class[] { CREPlayerOfflineException.class, CRELengthException.class }; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCHumanEntity he; + if(args.length == 0) { + he = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); + } else { + he = Static.GetPlayer(args[0], t); + } + return new CDouble(he.getAttackCooldown(), t); + } + + @Override + public String getName() { + return "pattack_cooldown"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{0, 1}; + } + + @Override + public String docs() { + return "double {[player]} Gets the charge value of the player's attack cooldown. This is a " + + " double with range 0.0-1.0 inclusive. 1.0 is fully charged, while 0.0 is no charge."; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_4; + } + + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class get_plist_header extends AbstractFunction { + + @Override + public String getName() { + return "get_plist_header"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{0, 1}; + } + + @Override + public String docs() { + return "string {[player]} Gets the player list header for a player." + + " This is the text that appears above the player list that appears when hitting the tab key."; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCPlayer p; + if(args.length == 2) { + p = Static.GetPlayer(args[0], t); + } else { + p = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); + Static.AssertPlayerNonNull(p, t); + } + return new CString(p.getPlayerListHeader(), t); + } + + @Override + public Class[] thrown() { + return new Class[]{CRELengthException.class, CREPlayerOfflineException.class}; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_4; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class set_plist_header extends AbstractFunction { + + @Override + public String getName() { + return "set_plist_header"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1, 2}; + } + + @Override + public String docs() { + return "void {[player], header} Sets the player list header for a player." + + " This is the text that appears above the player list that appears when hitting the tab key." + + " Colors and new lines are accepted. Only the given player (or current player if none is given)" + + " will see these changes."; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCPlayer p; + String header; + if(args.length == 2) { + p = Static.GetPlayer(args[0], t); + header = args[1].val(); + } else { + p = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); + Static.AssertPlayerNonNull(p, t); + header = args[0].val(); + } + p.setPlayerListHeader(header); + return CVoid.VOID; + } + + @Override + public Class[] thrown() { + return new Class[]{CRELengthException.class, CREPlayerOfflineException.class}; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_4; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class get_plist_footer extends AbstractFunction { + + @Override + public String getName() { + return "get_plist_footer"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{0, 1}; + } + + @Override + public String docs() { + return "string {[player]} Gets the player list footer for a player." + + " This is the text that appears below the player list that appears when hitting the tab key."; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCPlayer p; + if(args.length == 2) { + p = Static.GetPlayer(args[0], t); + } else { + p = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); + Static.AssertPlayerNonNull(p, t); + } + return new CString(p.getPlayerListFooter(), t); + } + + @Override + public Class[] thrown() { + return new Class[]{CRELengthException.class, CREPlayerOfflineException.class}; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_4; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class set_plist_footer extends AbstractFunction { + + @Override + public String getName() { + return "set_plist_footer"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1, 2}; + } + + @Override + public String docs() { + return "void {[player], footer} Sets the player list footer for a player." + + " This is the text that appears below the player list that appears when hitting the tab key." + + " Colors and new lines are accepted. Only the given player (or current player if none is given)" + + " will see these changes."; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCPlayer p; + String footer; + if(args.length == 2) { + p = Static.GetPlayer(args[0], t); + footer = args[1].val(); + } else { + p = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); + Static.AssertPlayerNonNull(p, t); + footer = args[0].val(); + } + p.setPlayerListFooter(footer); + return CVoid.VOID; + } + + @Override + public Class[] thrown() { + return new Class[]{CRELengthException.class, CREPlayerOfflineException.class}; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_4; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + @seealso(Echoes.tellraw.class) + public static class ptellraw extends AbstractFunction { + + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class, CREFormatException.class, CREIOException.class, + CREPlayerOfflineException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCPlayer p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); + if(p == null) { + throw new CREPlayerOfflineException("ptellraw() requires player context. Consider tellraw().", t); + } + String selector = "@s"; + String json; + try { + Gson gson = new GsonBuilder().disableHtmlEscaping().create(); + if(args.length == 1) { + json = gson.toJson(Construct.GetPOJO(args[0])); + } else { + selector = ArgumentValidation.getString(args[0], t); + json = gson.toJson(Construct.GetPOJO(args[1])); + } + } catch(ClassCastException ex) { + throw new CRECastException(ex.getMessage(), t); + } catch(JsonIOException ex) { + throw new CREIOException(ex.getMessage(), t); + } + try { + Static.getServer().runasConsole("minecraft:execute as " + p.getName() + " at @s" + + " run minecraft:tellraw " + selector + " " + json); + } catch(Exception ex) { + throw new CREFormatException(ex.getMessage(), t, ex.getCause()); + } + return CVoid.VOID; + } + + @Override + public String getName() { + return "ptellraw"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1, 2}; + } + + @Override + public String docs() { + return "void {[string selector], array raw} A thin wrapper around the tellraw command from player context," + + " this simply passes the input to the command. The raw is passed in as a normal" + + " (possibly associative) array, and json encoded. No validation is done on the input," + + " so the command may fail. If not provided, the selector defaults to @s. Do not use double quotes" + + " (smart string) when providing the selector. See {{function|tellraw}} if you don't need the @s" + + " selector with player context. ---- The specification of the array may change from version to" + + " version of Minecraft, but is documented here: https://minecraft.wiki/w/Text_component_format." + + " This function is roughly equivalent to" + + " runas('~console', '/execute as '.player().' at @s tellraw '.@selector.' '.json_encode(@raw))" + + " but uses the Gson serializer instead."; + } + + @Override + public Version since() { + return MSVersion.V3_3_4; + } + + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[] { + new ExampleScript("Simple usage with a plain message", + "ptellraw('@a', array('text': 'Hello World!'));", + "<>"), + new ExampleScript("Advanced usage with embedded selectors.", + "ptellraw('@a', array(\n" + + "\tarray('selector': '@s'), // prints current player\n" + + "\tarray('text': ': Hello World!')\n" + + "));", + "<>"), + new ExampleScript("Complex object", + "ptellraw(array(\n" + + "\tarray('text': 'Hello '),\n" + + "\tarray('text': 'World', 'color': 'light_purple'),\n" + + "\tarray('text': '!')\n" + + "));", + "<>") + }; + } + + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class pstatistic extends AbstractFunction { + + @Override + public String getName() { + return "pstatistic"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1, 2, 3}; + } + + @Override + public String docs() { + return "int {[player], statistic, [type]} Returns the specified statistic for the player." + + " Some statistics require a block, item or entity type. An IllegalArgumentException will" + + " be thrown if the statistic is invalid, or if the type is invalid for that statistic." + + " The player argument is required before the type argument is." + + " ---- Valid statistics are: " + StringUtils.Join(MCPlayerStatistic.values(), ", ", ", or ", " or "); + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCPlayer p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); + String filter = null; + MCPlayerStatistic stat; + int offset = 0; + if(args.length > 1) { + offset = 1; + p = Static.GetPlayer(args[0], t); + if(args.length > 2) { + filter = args[2].val().toUpperCase(); + } + } + + try { + stat = MCPlayerStatistic.valueOf(args[offset].val().toUpperCase()); + } catch (IllegalArgumentException ex) { + throw new CREIllegalArgumentException("Invalid statistic type: " + args[offset].val(), t); + } + if(!stat.existsInCurrent()) { + throw new CREIllegalArgumentException("The statistic type \"" + args[offset].val() + "\"" + + " does not exist in this version.", t); + } + + MCPlayerStatistic.Type type = stat.getType(); + int ret = 0; + if(type != MCPlayerStatistic.Type.NONE) { + if(filter == null) { + throw new CREInsufficientArgumentsException("Missing " + type.name().toLowerCase() + + " type for statistic: " + args[offset].val(), t); + } + switch(type) { + case BLOCK: + MCMaterial block = StaticLayer.GetMaterial(filter); + if(block == null || !block.isBlock()) { + throw new CREIllegalArgumentException("Invalid block type: " + filter, t); + } + ret = p.getStatistic(stat, block); + break; + case ENTITY: + try { + ret = p.getStatistic(stat, MCEntityType.valueOf(filter.toUpperCase())); + } catch (IllegalArgumentException ex) { + throw new CREIllegalArgumentException("Invalid entity type: " + filter, t); + } + break; + case ITEM: + MCMaterial item = StaticLayer.GetMaterial(filter); + if(item == null) { + throw new CREIllegalArgumentException("Invalid item type: " + filter, t); + } + ret = p.getStatistic(stat, item); + break; + } + } else { + ret = p.getStatistic(stat); + } + return new CInt(ret, t); + } + + @Override + public Class[] thrown() { + return new Class[]{CREIllegalArgumentException.class, CREPlayerOfflineException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_4; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class set_pstatistic extends AbstractFunction { + + @Override + public String getName() { + return "set_pstatistic"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{2, 3, 4}; + } + + @Override + public String docs() { + return "void {[player], statistic, [type], amount} Sets the specified statistic for the player." + + " Some statistics require a block, item or entity type. An IllegalArgumentException will" + + " be thrown if the statistic is invalid, or if the type is invalid for that statistic." + + " The player argument is required before the type argument is." + + " ---- Valid statistics are: " + StringUtils.Join(MCPlayerStatistic.values(), ", ", ", or ", " or "); + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCPlayer p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); + String filter = null; + MCPlayerStatistic stat; + int offset = 0; + int amount = 0; + if(args.length > 2) { + offset = 1; + p = Static.GetPlayer(args[0], t); + if(args.length == 3) { + amount = ArgumentValidation.getInt32(args[2], t); + } else { + filter = args[2].val().toUpperCase(); + amount = ArgumentValidation.getInt32(args[3], t); + } + } + + try { + stat = MCPlayerStatistic.valueOf(args[offset].val().toUpperCase()); + } catch (IllegalArgumentException ex) { + throw new CREIllegalArgumentException("Invalid statistic type: " + args[offset].val(), t); + } + if(!stat.existsInCurrent()) { + throw new CREIllegalArgumentException("The statistic type \"" + args[offset].val() + "\"" + + " does not exist in this version.", t); + } + + if(amount < 0) { + throw new CRERangeException("Amount must not be negative.", t); + } + + MCPlayerStatistic.Type type = stat.getType(); + if(type != MCPlayerStatistic.Type.NONE) { + if(filter == null) { + throw new CREInsufficientArgumentsException("Missing " + type.name().toLowerCase() + + " type for statistic: " + args[offset].val(), t); + } + switch(type) { + case BLOCK: + MCMaterial block = StaticLayer.GetMaterial(filter); + if(block == null || !block.isBlock()) { + throw new CREIllegalArgumentException("Invalid block type: " + filter, t); + } + p.setStatistic(stat, block, amount); + break; + case ENTITY: + try { + p.setStatistic(stat, MCEntityType.valueOf(filter.toUpperCase()), amount); + } catch (IllegalArgumentException ex) { + throw new CREIllegalArgumentException("Invalid entity type: " + filter, t); + } + break; + case ITEM: + MCMaterial item = StaticLayer.GetMaterial(filter); + if(item == null) { + throw new CREIllegalArgumentException("Invalid item type: " + filter, t); + } + p.setStatistic(stat, item, amount); + break; + } + } else { + p.setStatistic(stat, amount); + } + return CVoid.VOID; + } + + @Override + public Class[] thrown() { + return new Class[]{CRERangeException.class, CREPlayerOfflineException.class, CRECastException.class, + CREIllegalArgumentException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_4; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class pblocking extends AbstractFunction { + + @Override + public String getName() { + return "pblocking"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{0, 1}; + } + + @Override + public String docs() { + return "boolean {[player]} Gets if a player is blocking or not."; + } + + @Override + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CRELengthException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCPlayer p; + if(args.length == 1) { + p = Static.GetPlayer(args[0], t); + } else { + p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); + Static.AssertPlayerNonNull(p, t); + } + return CBoolean.get(p.isBlocking()); + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_4; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class psprinting extends AbstractFunction { + + @Override + public String getName() { + return "psprinting"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{0, 1}; + } + + @Override + public String docs() { + return "boolean {[player]} Gets if a player is sprinting or not."; + } + + @Override + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CRELengthException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCPlayer p; + if(args.length == 1) { + p = Static.GetPlayer(args[0], t); + } else { + p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); + Static.AssertPlayerNonNull(p, t); + } + return CBoolean.get(p.isSprinting()); + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_4; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + @seealso({set_pborder.class, com.laytonsmith.core.functions.World.get_world_border.class, + com.laytonsmith.core.functions.World.set_world_border.class}) + public static class pborder extends AbstractFunction { + + @Override + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCPlayer p; + if(args.length == 1) { + p = Static.GetPlayer(args[0], t); + } else { + p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); + Static.AssertPlayerNonNull(p, t); + } + MCWorldBorder wb = p.getWorldBorder(); + if(wb == null) { + return CNull.NULL; + } + CArray ret = CArray.GetAssociativeArray(t); + ret.set("width", new CDouble(wb.getSize(), t), t); + ret.set("center", ObjectGenerator.GetGenerator().location(wb.getCenter(), false), t); + ret.set("warningtime", new CInt(wb.getWarningTime(), t), t); + ret.set("warningdistance", new CInt(wb.getWarningDistance(), t), t); + return ret; + } + + @Override + public String getName() { + return "pborder"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{0, 1}; + } + + @Override + public String docs() { + return "array {[player]} Returns an associative array for the player's virtual world border. (MC 1.18.2+)" + + " The keys are 'width', 'center', 'warningtime', and 'warningdistance'." + + " Returns null if the player is using the existing border for the world that they're in."; + } + + @Override + public Version since() { + return MSVersion.V3_3_5; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + @seealso({pborder.class, com.laytonsmith.core.functions.World.get_world_border.class, + com.laytonsmith.core.functions.World.set_world_border.class}) + public static class set_pborder extends AbstractFunction { + + @Override + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CRECastException.class, CREFormatException.class, + CRERangeException.class, CREInvalidWorldException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCPlayer p; + Mixed c; + if(args.length == 2) { + p = Static.GetPlayer(args[0], t); + c = args[1]; + } else { + p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); + Static.AssertPlayerNonNull(p, t); + c = args[0]; + } + if(c instanceof CNull) { + p.setWorldBorder(null); + return CVoid.VOID; + } + MCWorldBorder wb = p.getWorldBorder(); + if(wb == null) { + wb = Static.getServer().createWorldBorder(); + } + if(!(c.isInstanceOf(CArray.TYPE))) { + throw new CREFormatException("Expected array or null but given \"" + c.val() + "\"", t); + } + CArray params = (CArray) c; + if(params.containsKey("width")) { + if(params.containsKey("seconds")) { + wb.setSize(ArgumentValidation.getDouble(params.get("width", t), t), + ArgumentValidation.getInt32(params.get("seconds", t), t)); + } else { + wb.setSize(ArgumentValidation.getDouble(params.get("width", t), t)); + } + } + if(params.containsKey("center")) { + wb.setCenter(ObjectGenerator.GetGenerator().location(params.get("center", t), p.getWorld(), t)); + } + if(params.containsKey("warningtime")) { + wb.setWarningTime(ArgumentValidation.getInt32(params.get("warningtime", t), t)); + } + if(params.containsKey("warningdistance")) { + wb.setWarningDistance(ArgumentValidation.getInt32(params.get("warningdistance", t), t)); + } + p.setWorldBorder(wb); + return CVoid.VOID; + } + + @Override + public String getName() { + return "set_pborder"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1, 2}; + } + + @Override + public String docs() { + return "void {player, paramArray} Creates or updates a player's virtual world border. (MC 1.18.2+)" + + " In addition to the keys returned by pborder(), you can also specify 'seconds'." + + " This is the time in which the border will move from the previous width to the new 'width'." + + " If give null instead of an array, this resets the player's visible world border to the one of" + + " the world that they're in."; + } + + @Override + public Version since() { + return MSVersion.V3_3_5; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class plocale extends AbstractFunction { + + @Override + public String getName() { + return "plocale"; + } + + @Override + public String docs() { + return "string {[player]} Gets the player's locale."; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{0, 1}; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCPlayer p; + if(args.length == 0) { + p = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); + Static.AssertPlayerNonNull(p, t); + } else { + p = Static.GetPlayer(args[0], t); + } + return new CString(p.getLocale(), t); + } + + @Override + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class}; + } + + @Override + public Version since() { + return MSVersion.V3_3_5; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class phas_recipe extends AbstractFunction { + + @Override + public String getName() { + return "phas_recipe"; + } + + @Override + public String docs() { + return "boolean {[player], recipeKey} Gets whether a player has a recipe in their recipe book."; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1, 2}; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCPlayer p; + String stringKey; + if(args.length == 1) { + p = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); + Static.AssertPlayerNonNull(p, t); + stringKey = args[0].val(); + } else { + p = Static.GetPlayer(args[0], t); + stringKey = args[1].val(); + } + MCNamespacedKey key = StaticLayer.GetConvertor().GetNamespacedKey(stringKey); + if(key == null) { + throw new CREFormatException("Invalid namespaced key format: " + stringKey, t); + } + return CBoolean.get(p.hasDiscoveredRecipe(key)); + } + + @Override + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CRELengthException.class, CREFormatException.class}; + } + + @Override + public Version since() { + return MSVersion.V3_3_5; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class pgive_recipe extends AbstractFunction { + + @Override + public String getName() { + return "pgive_recipe"; + } + + @Override + public String docs() { + return "int {[player], recipeKey(s)} Adds one or more recipes to a player's recipe book." + + " Can take a single recipe key or an array of keys." + + " Returns how many recipes were newly discovered."; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1, 2}; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCPlayer p; + Mixed value; + if(args.length == 1) { + p = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); + Static.AssertPlayerNonNull(p, t); + value = args[0]; + } else { + p = Static.GetPlayer(args[0], t); + value = args[1]; + } + if(value.isInstanceOf(CArray.TYPE)) { + int result = 0; + for(Mixed element : ((CArray) value).asList()) { + MCNamespacedKey key = StaticLayer.GetConvertor().GetNamespacedKey(element.val()); + if(key == null) { + throw new CREFormatException("Invalid namespaced key format: " + element.val(), t); + } + boolean success = p.discoverRecipe(key); + result += success ? 1 : 0; + } + return new CInt(result, t); + } + MCNamespacedKey key = StaticLayer.GetConvertor().GetNamespacedKey(value.val()); + if(key == null) { + throw new CREFormatException("Invalid namespaced key format: " + value.val(), t); + } + boolean success = p.discoverRecipe(key); + return new CInt(success ? 1 : 0, t); + } + + @Override + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CRELengthException.class, CREFormatException.class}; + } + + @Override + public Version since() { + return MSVersion.V3_3_5; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class pforce_respawn extends AbstractFunction { + + @Override + public String getName() { + return "pforce_respawn"; + } + + @Override + public String docs() { + return "void {[player]} Forces a player to respawn immediately if they're currently dead."; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{0, 1}; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCPlayer p; + if(args.length == 0) { + p = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); + Static.AssertPlayerNonNull(p, t); + } else { + p = Static.GetPlayer(args[0], t); + } + p.respawn(); + return CVoid.VOID; + } + + @Override + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CRELengthException.class}; + } + + @Override + public Version since() { + return MSVersion.V3_3_5; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class phide_entity extends AbstractFunction { + + @Override + public String getName() { + return "phide_entity"; + } + + @Override + public String docs() { + return "void {[player], entityUUID} Sets an entity to no longer be seen or tracked by the player's client." + + " Resets to default on player rejoin." + + " (MC 1.18+)"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1, 2}; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCPlayer p; + MCEntity e; + if(args.length == 1) { + p = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); + Static.AssertPlayerNonNull(p, t); + e = Static.getEntity(args[0], t); + } else { + p = Static.GetPlayer(args[0], t); + e = Static.getEntity(args[1], t); + } + p.hideEntity(e); + return CVoid.VOID; + } + + @Override + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CRELengthException.class, CREBadEntityException.class}; + } + + @Override + public Version since() { + return MSVersion.V3_3_5; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class pshow_entity extends AbstractFunction { + + @Override + public String getName() { + return "pshow_entity"; + } + + @Override + public String docs() { + return "void {[player], entityUUID} Sets an entity to be sent to the player's client again. (MC 1.18+)"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1, 2}; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCPlayer p; + MCEntity e; + if(args.length == 1) { + p = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); + Static.AssertPlayerNonNull(p, t); + e = Static.getEntity(args[0], t); + } else { + p = Static.GetPlayer(args[0], t); + e = Static.getEntity(args[1], t); + } + p.showEntity(e); + return CVoid.VOID; + } + + @Override + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CRELengthException.class, CREBadEntityException.class}; + } + + @Override + public Version since() { + return MSVersion.V3_3_5; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class pcan_see_entity extends AbstractFunction { + + @Override + public String getName() { + return "pcan_see_entity"; + } + + @Override + public String docs() { + return "boolean {[player], entityUUID} Gets whether the entity is known by the player's client or" + + " hidden by a plugin. (MC 1.18+)"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1, 2}; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCPlayer p; + MCEntity e; + if(args.length == 1) { + p = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); + Static.AssertPlayerNonNull(p, t); + e = Static.getEntity(args[0], t); + } else { + p = Static.GetPlayer(args[0], t); + e = Static.getEntity(args[1], t); + } + return CBoolean.get(p.canSeeEntity(e)); + } + + @Override + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CRELengthException.class, CREBadEntityException.class}; + } + + @Override + public Version since() { + return MSVersion.V3_3_5; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class psend_equipment extends AbstractFunction { + + @Override + public String getName() { + return "psend_equipment"; + } + + @Override + public String docs() { + return "void {[player], entityUUID, equipmentArray} Changes a living entity's equipment only for the" + + " specified player. (MC 1.18+) Equipment array can be null to make all equipment not visible." + + " Otherwise equipment array must be an associative array where the keys are equipment slots and" + + " the values are item arrays or null. The equipment slots are: weapon, off_hand, helmet," + + " chestplate, leggings, boots, body (MC 1.20.6+), and saddle (MC 1.21.5+)."; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{2, 3}; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCPlayer p; + MCLivingEntity le; + Mixed equipment; + if(args.length == 2) { + p = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); + Static.AssertPlayerNonNull(p, t); + le = Static.getLivingEntity(args[0], t); + equipment = args[1]; + } else { + p = Static.GetPlayer(args[0], t); + le = Static.getLivingEntity(args[1], t); + equipment = args[2]; + } + if(equipment instanceof CNull) { + for(MCEquipmentSlot slot : MCEquipmentSlot.values()) { + p.sendEquipmentChange(le, slot, null); + } + } else if(equipment.isInstanceOf(CArray.TYPE)) { + CArray ea = (CArray) equipment; + for(String key : ea.stringKeySet()) { + try { + p.sendEquipmentChange(le, MCEquipmentSlot.valueOf(key.toUpperCase()), + ObjectGenerator.GetGenerator().item(ea.get(key, t), t)); + } catch (IllegalArgumentException iae) { + throw new CREFormatException("Not an equipment slot: " + key, t); + } + } + } else { + throw new CREFormatException("Expected last argument to be an array or null", t); + } + return CVoid.VOID; + } + + @Override + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CRELengthException.class, CREBadEntityException.class, + CREFormatException.class}; + } + + @Override + public Version since() { + return MSVersion.V3_3_5; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + } + + @api + public static class get_player_ping extends AbstractFunction { + + public String getName() { + return "get_player_ping"; + } + + public Integer[] numArgs() { + return new Integer[]{0, 1}; + } + + public String docs() { + return "int {[player]} Returns a player's average response time to ping packets in milliseconds." + + " This is an indicator of the quality of the player's connection, as represented in the tab list."; + } + + public Construct exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCPlayer p; + if(args.length == 1) { + p = Static.GetPlayer(args[0].val(), t); + } else { + p = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); + Static.AssertPlayerNonNull(p, t); + } + return new CInt(p.getPing(), t); + } + + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CRELengthException.class}; + } + + public Version since() { + return MSVersion.V3_3_5; + } + + public boolean isRestricted() { + return false; + } + + public Boolean runAsync() { + return false; + } + } + + @api + public static class get_player_input extends AbstractFunction { + + public String getName() { + return "get_player_input"; + } + + public Integer[] numArgs() { + return new Integer[]{0, 1}; + } + + public String docs() { + return "array {[player]} Returns a player's current input. (MC 1.21.3+)" + + " This can be used to detect which movement keys the player is pressing." + + " Array contains the following keys: forward, backward, left, right, jump, sneak, and sprint."; + } + + public Construct exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCPlayer p; + if(args.length == 1) { + p = Static.GetPlayer(args[0].val(), t); + } else { + p = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); + Static.AssertPlayerNonNull(p, t); + } + CArray ret = CArray.GetAssociativeArray(t); + MCPlayerInput input = p.getCurrentInput(); + ret.set("forward", CBoolean.get(input.forward()), t); + ret.set("backward", CBoolean.get(input.backward()), t); + ret.set("left", CBoolean.get(input.left()), t); + ret.set("right", CBoolean.get(input.right()), t); + ret.set("jump", CBoolean.get(input.jump()), t); + ret.set("sneak", CBoolean.get(input.sneak()), t); + ret.set("sprint", CBoolean.get(input.sprint()), t); + return ret; + } + + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CRELengthException.class}; + } + + public Version since() { + return MSVersion.V3_3_5; + } + + public boolean isRestricted() { + return true; + } + + public Boolean runAsync() { + return false; + } + } + + @api + public static class set_player_sleeping_ignored extends AbstractFunction { + + @Override public String getName() { + return "set_player_sleeping_ignored"; + } + + @Override public Integer[] numArgs() { + return new Integer[]{1, 2}; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) + throws ConfigRuntimeException { + final MCPlayer player; + final boolean value; + if(args.length == 1) { + player = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); + Static.AssertPlayerNonNull(player, t); + value = ArgumentValidation.getBoolean(args[0], t); + } else { + player = Static.GetPlayer(args[0], t); + value = ArgumentValidation.getBoolean(args[1], t); + } + player.setSleepingIgnored(value); + return CVoid.VOID; + } + + @Override + public Class[] thrown() { + return new Class[]{ + CREPlayerOfflineException.class, + CREInsufficientArgumentsException.class, + CRECastException.class + }; + } + + @Override public boolean isRestricted() { + return false; + } + + @Override public Boolean runAsync() { + return false; + } + + @Override public MSVersion since() { + return MSVersion.V3_3_5; + } + + @Override + public String docs() { + return "void {[player], boolean} Sets whether is ignored when " + + "counting sleepers to skip night."; + } + } + + @api + public static class is_player_sleeping_ignored extends AbstractFunction { + + @Override public String getName() { + return "is_player_sleeping_ignored"; + } + + @Override public Integer[] numArgs() { + return new Integer[]{0, 1}; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) + throws ConfigRuntimeException { + final MCPlayer player; + if(args.length == 0) { + player = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); + Static.AssertPlayerNonNull(player, t); + } else { + player = Static.GetPlayer(args[0], t); + } + return CBoolean.get(player.isSleepingIgnored()); + } + + @Override + public Class[] thrown() { + return new Class[] { + CREPlayerOfflineException.class, + CREInsufficientArgumentsException.class + }; + } + + @Override public boolean isRestricted() { + return false; + } + + @Override public Boolean runAsync() { + return false; + } + + @Override public MSVersion since() { + return MSVersion.V3_3_5; + } + + @Override + public String docs() { + return "boolean {[player]} Returns whether is currently ignored " + + "by the night-skip sleep check."; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + @seealso({raw_pcan_see.class}) + public static class raw_set_pvanish extends AbstractFunction { + + @Override + public String getName() { + return "raw_set_pvanish"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{2, 3}; + } + + @Override + public String docs() { + return "void {[player], isVanished, otherPlayer} Sets the visibility of this player to another player." + + " If isVanished is true, the player's model and tablist name will not be visible to the other player."; + } + + @Override + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CRELengthException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCPlayer thisPlayer; + boolean isVanished; + MCPlayer otherPlayer; + if(args.length == 2) { + thisPlayer = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); + isVanished = ArgumentValidation.getBooleanObject(args[0], t); + otherPlayer = Static.GetPlayer(args[1], t); + } else { + thisPlayer = Static.GetPlayer(args[0], t); + isVanished = ArgumentValidation.getBooleanObject(args[1], t); + otherPlayer = Static.GetPlayer(args[2], t); + } + otherPlayer.setVanished(isVanished, thisPlayer); + return CVoid.VOID; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_0; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + @seealso({raw_set_pvanish.class}) + public static class raw_pcan_see extends AbstractFunction { + + @Override + public String getName() { + return "raw_pcan_see"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1, 2}; + } + + @Override + public String docs() { + return "boolean {[player], otherPlayer} Returns whether this player can see another player or not." + + " Hidden players are sometimes called vanished players."; + } + + @Override + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CRELengthException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCPlayer thisPlayer; + MCPlayer otherPlayer; + if(args.length == 1) { + thisPlayer = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); + otherPlayer = Static.GetPlayer(args[0], t); + } else { + thisPlayer = Static.GetPlayer(args[0], t); + otherPlayer = Static.GetPlayer(args[1], t); + } + return CBoolean.get(thisPlayer.canSee(otherPlayer)); + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_0; } } } diff --git a/src/main/java/com/laytonsmith/core/functions/PluginMeta.java b/src/main/java/com/laytonsmith/core/functions/PluginMeta.java index e8365b4ee8..f7c1ff5926 100644 --- a/src/main/java/com/laytonsmith/core/functions/PluginMeta.java +++ b/src/main/java/com/laytonsmith/core/functions/PluginMeta.java @@ -5,35 +5,43 @@ import com.laytonsmith.abstraction.StaticLayer; import com.laytonsmith.abstraction.pluginmessages.MCMessenger; import com.laytonsmith.annotations.api; -import com.laytonsmith.core.CHVersion; +import com.laytonsmith.core.ArgumentValidation; +import com.laytonsmith.core.MSVersion; import com.laytonsmith.core.Static; import com.laytonsmith.core.constructs.CArray; import com.laytonsmith.core.constructs.CBoolean; import com.laytonsmith.core.constructs.CByteArray; import com.laytonsmith.core.constructs.CString; import com.laytonsmith.core.constructs.CVoid; -import com.laytonsmith.core.constructs.Construct; import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.environments.CommandHelperEnvironment; import com.laytonsmith.core.environments.Environment; +import com.laytonsmith.core.exceptions.CRE.CRECastException; +import com.laytonsmith.core.exceptions.CRE.CREIllegalArgumentException; +import com.laytonsmith.core.exceptions.CRE.CRENotFoundException; +import com.laytonsmith.core.exceptions.CRE.CREPlayerOfflineException; +import com.laytonsmith.core.exceptions.CRE.CREPluginChannelException; +import com.laytonsmith.core.exceptions.CRE.CREThrowable; import com.laytonsmith.core.exceptions.ConfigRuntimeException; -import com.laytonsmith.core.functions.Exceptions.ExceptionType; +import com.laytonsmith.core.natives.interfaces.Mixed; import java.util.Set; /** - * + * */ public class PluginMeta { - public static String docs(){ + + public static String docs() { return "This class contains the functions use to communicate with other plugins and the server in general."; } - - @api + + @api(environments = {CommandHelperEnvironment.class}) public static class fake_incoming_plugin_message extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException, ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CRECastException.class, CREPlayerOfflineException.class, + CREIllegalArgumentException.class}; } @Override @@ -47,18 +55,22 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCPluginMeta meta = StaticLayer.GetConvertor().GetPluginMeta(); MCPlayer p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); int offset = 0; - if(args.length == 3){ + if(args.length == 3) { offset = 1; p = Static.GetPlayer(args[0], t); } - String channel = args[0 + offset].val(); - CByteArray ba = Static.getByteArray(args[1 + offset], t); + String channel = args[offset].val(); + CByteArray ba = ArgumentValidation.getByteArray(args[1 + offset], t); Static.AssertPlayerNonNull(p, t); - meta.fakeIncomingMessage(p, channel, ba.asByteArrayCopy()); + try { + meta.fakeIncomingMessage(p, channel, ba.asByteArrayCopy()); + } catch (IllegalArgumentException ex) { + throw new CREIllegalArgumentException(ex.getMessage(), t); + } return CVoid.VOID; } @@ -80,18 +92,19 @@ public String docs() { } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } - + } - - @api + + @api(environments = {CommandHelperEnvironment.class}) public static class send_plugin_message extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException, ExceptionType.PlayerOfflineException}; + public Class[] thrown() { + return new Class[]{CRECastException.class, CREPlayerOfflineException.class, + CREIllegalArgumentException.class}; } @Override @@ -105,18 +118,22 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCPlayer p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); int offset = 0; - if(args.length == 3){ + if(args.length == 3) { offset = 1; p = Static.GetPlayer(args[0], t); } - String channel = args[0 + offset].val(); - CByteArray ba = Static.getByteArray(args[1 + offset], t); + String channel = args[offset].val(); + CByteArray ba = ArgumentValidation.getByteArray(args[1 + offset], t); Static.AssertPlayerNonNull(p, t); - p.sendPluginMessage(channel, ba.asByteArrayCopy()); + try { + p.sendPluginMessage(channel, ba.asByteArrayCopy()); + } catch (IllegalArgumentException ex) { + throw new CREIllegalArgumentException(ex.getMessage(), t); + } return CVoid.VOID; } @@ -132,24 +149,27 @@ public Integer[] numArgs() { @Override public String docs() { - return "void {[player,] channel, message} Sends a plugin message to the player. Channel should be a string (the" - + " channel name) and message should be a byte_array primitive. Depending on the plugin, these parameters" + return "void {[player,] channel, message} Sends a plugin message to the player." + + " Channel name should be a string that is all lower-case, no longer than 32 characters," + + " and contain a colon, or it will throw an IllegalArgumentException." + + " The message should be a byte_array primitive. Depending on the plugin, these parameters" + " will vary. If message is null an empty byte_array is sent."; } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } - + } - - @api + + @api(environments = {CommandHelperEnvironment.class}) public static class register_channel extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PluginChannelException}; + public Class[] thrown() { + return new Class[]{CREPluginChannelException.class, CRENotFoundException.class, + CREIllegalArgumentException.class}; } @Override @@ -163,16 +183,24 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - MCMessenger msgr = Static.getServer().getMessenger(); + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCMessenger messenger = Static.getServer().getMessenger(); + if(messenger == null) { + throw new CRENotFoundException( + "Could not find the internal Messenger object (are you running in cmdline mode?)", t); + } String channel = args[0].toString(); - - if (!msgr.isIncomingChannelRegistered(channel)) { - msgr.registerIncomingPluginChannel(channel); + + if(!messenger.isIncomingChannelRegistered(channel)) { + try { + messenger.registerIncomingPluginChannel(channel); + } catch (IllegalArgumentException ex) { + throw new CREIllegalArgumentException(ex.getMessage(), t); + } } else { - throw new ConfigRuntimeException("The channel '" + channel + "' is already registered.", ExceptionType.PluginChannelException, t); + throw new CREPluginChannelException("The channel '" + channel + "' is already registered.", t); } - + return CVoid.VOID; } @@ -189,21 +217,24 @@ public Integer[] numArgs() { @Override public String docs() { return "void {channel} Registers a plugin channel for CommandHelper to listen on." + + " Channel name should be a string that is all lower-case, no longer than 32 characters," + + " and contain a colon, or it will throw an IllegalArgumentException." + " Incoming messages can be inspected by binding to 'plugin_message_received'."; } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } } - - @api + + @api(environments = {CommandHelperEnvironment.class}) public static class unregister_channel extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PluginChannelException}; + public Class[] thrown() { + return new Class[]{CREPluginChannelException.class, CRENotFoundException.class, + CREIllegalArgumentException.class}; } @Override @@ -217,16 +248,24 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - MCMessenger msgr = Static.getServer().getMessenger(); + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCMessenger messenger = Static.getServer().getMessenger(); + if(messenger == null) { + throw new CRENotFoundException( + "Could not find the internal Messenger object (are you running in cmdline mode?)", t); + } String channel = args[0].toString(); - - if (msgr.isIncomingChannelRegistered(channel)) { - msgr.unregisterIncomingPluginChannel(channel); + + if(messenger.isIncomingChannelRegistered(channel)) { + try { + messenger.unregisterIncomingPluginChannel(channel); + } catch (IllegalArgumentException ex) { + throw new CREIllegalArgumentException(ex.getMessage(), t); + } } else { - throw new ConfigRuntimeException("The channel '" + channel + "' is not registered.", ExceptionType.PluginChannelException, t); + throw new CREPluginChannelException("The channel '" + channel + "' is not registered.", t); } - + return CVoid.VOID; } @@ -246,17 +285,17 @@ public String docs() { } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } } - - @api + + @api(environments = {CommandHelperEnvironment.class}) public static class is_channel_registered extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{CRENotFoundException.class, CREIllegalArgumentException.class}; } @Override @@ -270,8 +309,17 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - return CBoolean.get(Static.getServer().getMessenger().isIncomingChannelRegistered(args[0].toString())); + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCMessenger messenger = Static.getServer().getMessenger(); + if(messenger == null) { + throw new CRENotFoundException( + "Could not find the internal Messenger object (are you running in cmdline mode?)", t); + } + try { + return CBoolean.get(messenger.isIncomingChannelRegistered(args[0].toString())); + } catch (IllegalArgumentException ex) { + throw new CREIllegalArgumentException(ex.getMessage(), t); + } } @Override @@ -291,17 +339,17 @@ public String docs() { } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } } - - @api + + @api(environments = {CommandHelperEnvironment.class}) public static class get_registered_channels extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{CRENotFoundException.class, CREIllegalArgumentException.class}; } @Override @@ -315,14 +363,19 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - Set chans = Static.getServer().getMessenger().getIncomingChannels(); + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCMessenger messenger = Static.getServer().getMessenger(); + if(messenger == null) { + throw new CRENotFoundException( + "Could not find the internal Messenger object (are you running in cmdline mode?)", t); + } + Set chans = messenger.getIncomingChannels(); CArray arr = new CArray(t); - - for (String chan : chans) { - arr.push(new CString(chan, t)); + + for(String chan : chans) { + arr.push(new CString(chan, t), t); } - + return arr; } @@ -343,8 +396,8 @@ public String docs() { } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } } } diff --git a/src/main/java/com/laytonsmith/core/functions/Recipes.java b/src/main/java/com/laytonsmith/core/functions/Recipes.java index ced2960ec9..16749a8b29 100644 --- a/src/main/java/com/laytonsmith/core/functions/Recipes.java +++ b/src/main/java/com/laytonsmith/core/functions/Recipes.java @@ -4,17 +4,23 @@ import com.laytonsmith.abstraction.MCItemStack; import com.laytonsmith.abstraction.MCRecipe; import com.laytonsmith.annotations.api; -import com.laytonsmith.core.CHVersion; +import com.laytonsmith.core.MSVersion; import com.laytonsmith.core.ObjectGenerator; import com.laytonsmith.core.Static; import com.laytonsmith.core.constructs.CArray; import com.laytonsmith.core.constructs.CBoolean; import com.laytonsmith.core.constructs.CVoid; -import com.laytonsmith.core.constructs.Construct; import com.laytonsmith.core.constructs.Target; +import com.laytonsmith.core.environments.CommandHelperEnvironment; import com.laytonsmith.core.environments.Environment; +import com.laytonsmith.core.exceptions.CRE.CRECastException; +import com.laytonsmith.core.exceptions.CRE.CREFormatException; +import com.laytonsmith.core.exceptions.CRE.CREIllegalArgumentException; +import com.laytonsmith.core.exceptions.CRE.CREThrowable; +import com.laytonsmith.core.exceptions.ConfigCompileException; import com.laytonsmith.core.exceptions.ConfigRuntimeException; -import com.laytonsmith.core.functions.Exceptions.ExceptionType; +import com.laytonsmith.core.natives.interfaces.Mixed; + import java.util.List; /** @@ -22,12 +28,12 @@ * @author cgallarno */ public class Recipes { - - public static String docs(){ - return "This class of functions allows recipes to be managed."; - } - - public static abstract class recipeFunction extends AbstractFunction { + + public static String docs() { + return "This class of functions allows recipes to be managed."; + } + + public abstract static class recipeFunction extends AbstractFunction { @Override public boolean isRestricted() { @@ -39,95 +45,259 @@ public Boolean runAsync() { return false; } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class add_recipe extends recipeFunction { + + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class, CREFormatException.class, CREIllegalArgumentException.class}; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + try { + return CBoolean.get(Static.getServer().addRecipe(ObjectGenerator.GetGenerator().recipe(args[0], t))); + } catch (IllegalStateException ex) { + // recipe with the given key probably already exists + return CBoolean.FALSE; + } + } + + @Override + public String getName() { + return "add_recipe"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1}; + } + + @Override + public String docs() { + return "boolean {recipeArray} Adds a recipe to the server and returns whether it was added or not. ----" + + " The recipeArray can contain the following keys:

" + + "'''type''' : (required) The type of recipe. Expected to be BLASTING, CAMPFIRE, FURNACE," + + " SHAPED, SHAPELESS, SMOKING, or STONECUTTING.
" + + "'''key''' : (required) A unique string id for this recipe.
" + + "'''result''' : (required) The result item array of the recipe.
" + + "'''group''' : (optional) A group id for grouping recipes in the recipe book.
" + + "'''shape''' : (shaped recipes) The shape of the ingredients in the crafting grid." + + " This is a normal array of strings where each element is a row in the grid, and each string has" + + " a character for each column in the grid.
" + + "'''ingredients''' : (shaped and shapeless recipes) Ingredients array of the recipe." + + " Shaped recipes are an associative array mapping each character in the shape to a single" + + " ingredient item or material or an array of possible ingredient items or material." + + " Shapeless recipes are a normal array of ingredients, where each element can be a single item or" + + " material, or each element can be an array of possible items or materials." + + " Ingredients that are item arrays with meta will require exact ingredient matches.
" + + "'''input''' : (cooking and stonecutting recipes) The input item type or array of possible types" + + " for this recipe.
" + + "'''cookingtime''' : (cooking) An integer amount of time in ticks for the input item to cook.
" + + "'''experience''' : (cooking) An integer amount of experience generated by this recipe.
" + + "
" + + "Examples:

" + + " Shaped Recipe Data. Turns 9 stone into obsidian." + + "
{\n"
+					+ "    key: 'stone_to_obsidian',\n"
+					+ "    type: 'SHAPED',\n"
+					+ "    result: {name: 'OBSIDIAN'},\n"
+					+ "    shape: {'AAA', 'AAA', 'AAA'},\n"
+					+ "    ingredients: {A: 'STONE'}\n"
+					+ "}"
+					+ "
" + + " Shapeless Recipe Data. Combines tall grass and dirt to make grass block." + + "
"
+					+ "{\n"
+					+ "    key: 'dirt_to_grass',\n"
+					+ "    type: 'SHAPELESS',\n"
+					+ "    result: {name: 'GRASS_BLOCK'},\n"
+					+ "    ingredients: {'DIRT', 'GRASS'}\n"
+					+ "}"
+					+ "
" + + " Furnace Recipe Data. Turn grass or mycelium into dirt through smelting. (multi-item type input)" + + "
"
+					+ "{\n"
+					+ "    key: 'cook_grass_to_dirt'\n"
+					+ "    type: 'FURNACE',\n"
+					+ "    result: {name: 'DIRT'},\n"
+					+ "    input: {'MYCELIUM', 'GRASS_BLOCK'},\n"
+					+ "    experience: 0.001,\n"
+					+ "    cookingtime: 200\n"
+					+ "}
" + + " StoneCutting Recipe Data. Turns diamond hoe into diamond. (single item type input)" + + "
"
+					+ "{\n"
+					+ "    key: 'diamond_hoe_to_diamond'\n"
+					+ "    type: 'STONECUTTING',\n"
+					+ "    result: {name: 'DIAMOND'},\n"
+					+ "    input: 'DIAMOND_HOE'\n"
+					+ "}
"; + } + @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; + } + + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("Demonstrates adding a shaped recipe.", + "add_recipe(array(\n" + + "\t'key': 'player_head',\n" + + "\t'type': 'SHAPED',\n" + + "\t'result': array('name': 'PLAYER_HEAD'),\n" + + "\t'shape': array(\n" + + "\t\t'AAA',\n" + + "\t\t'ABA',\n" + + "\t\t'AAA'),\n" + + "\t'ingredients': array(\n" + + "\t\t'A': 'CLAY_BALL',\n" + + "\t\t'B': 'SKELETON_SKULL')));", + "Creates recipe where a skeleton skull is surrounded by clay balls to creates a player head."), + + new ExampleScript("Demonstrates adding a shaped recipe with exact ingredients using item arrays." + + " The item ingredient must match exactly, including unspecified meta. (except 'qty')" + + " In this example, the regen potion ingredient must NOT be extended or upgraded for it to match," + + " because those are the default values.", + "add_recipe(array(\n" + + "\t'key': 'regen_extended',\n" + + "\t'type': 'SHAPED',\n" + + "\t'result': array('name': 'POTION', 'meta': array('potiontype': 'LONG_REGENERATION')),\n" + + "\t'shape': array(\n" + + "\t\t'RRR',\n" + + "\t\t'RPR',\n" + + "\t\t'RRR'),\n" + + "\t'ingredients': array(\n" + + "\t\t'R': 'REDSTONE',\n" + + "\t\t'P': array('name': 'POTION', 'meta': array('potiontype': 'REGENERATION')))));", + "Turns a normal regen potion surrounded by redstone into an extended regen potion."), + + new ExampleScript("Demonstrates a shaped recipe with multiple ingredient choices." + + " If just one of the ingredient choices is an item array (i.e. associative array instead of string)," + + " all other ingredient choices for that key are also treated as exact ingredients.", + "add_recipe(array(\n" + + "\t'key': 'mushroom_stem',\n" + + "\t'type': 'SHAPED',\n" + + "\t'result': array('name': 'MUSHROOM_STEM'),\n" + + "\t'shape': array('X', 'X', 'X'),\n" + + "\t'ingredients': array('X': array('RED_MUSHROOM', 'BROWN_MUSHROOM'))));", + "Crafts a mushroom stem block."), + + new ExampleScript("Demonstrates a shapeless recipe with exact ingredients." + + " This is only possible on Paper servers. On Spigot, only the item material will be considered." + + " Each element in the ingredients array may be a single item/material or an array of item/material" + + " choices (like how different types of planks can create a stick).", + "add_recipe(array(\n" + + "\t'key': 'debug_stick',\n" + + "\t'type': 'SHAPELESS',\n" + + "\t'result': array('name': 'DEBUG_STICK'),\n" + + "\t'ingredients': array(\n" + + "\t\tarray('name': 'NETHER_STAR'),\n" + + "\t\tarray('name': 'STICK', 'meta': array('repair': 1, 'enchants': array('silk_touch': 1))))));", + "Turns a nether star and a stick enchanted with silk touch into a debug stick."), + }; } } - - @api - public static class add_recipe extends recipeFunction { + + @api(environments = {CommandHelperEnvironment.class}) + public static class remove_recipe extends recipeFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException, ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[0]; } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - return CBoolean.get(Static.getServer().addRecipe(ObjectGenerator.GetGenerator().recipe(args[0], t))); + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + return CBoolean.get(Static.getServer().removeRecipe(args[0].val())); + } + + @Override + public Version since() { + return MSVersion.V3_3_4; } @Override public String getName() { - return "add_recipe"; + return "remove_recipe"; } @Override public Integer[] numArgs() { - return new Integer[] {1}; + return new Integer[]{1}; } @Override public String docs() { - return "boolean {RecipeArray} Adds a recipe to the server and returns whether it was added or not. Please read http://wiki.sk89q.com/wiki/CommandHelper/Array_Formatting to see how the recipe array is formatted."; + return "boolean {recipe_key} Remove a certain recipe by its registered key." + + " Returns whether the recipe was removed successfully or not."; } } - - @api + + @api(environments = {CommandHelperEnvironment.class}) public static class get_recipes_for extends recipeFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CREFormatException.class, CRECastException.class}; } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { CArray ret = new CArray(t); MCItemStack item = ObjectGenerator.GetGenerator().item(args[0], t); List recipes = Static.getServer().getRecipesFor(item); for(MCRecipe recipe : recipes) { - ret.push(ObjectGenerator.GetGenerator().recipe(recipe, t)); + ret.push(ObjectGenerator.GetGenerator().recipe(recipe, t), t); } - + return ret; } @Override public String getName() { - return "get_recipe_for"; + return "get_recipes_for"; } @Override public Integer[] numArgs() { - return new Integer[] {1}; + return new Integer[]{1}; } @Override public String docs() { - return "array {itemArray} Gets all recipes that have a result of the given item. " + - "NOTE: Gets all recipes for result item regardless of meta and enchants, althogh the array has correct data."; + return "array {itemArray} Gets all recipes that have a result of the given item." + + " NOTE: Gets all recipes for result item regardless of meta and enchants," + + " although the array has correct data."; } - + + @Override + public Version since() { + return MSVersion.V3_3_1; + } + } - - @api + + @api(environments = {CommandHelperEnvironment.class}) public static class get_all_recipes extends recipeFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{}; } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { CArray ret = new CArray(t); List recipes = Static.getServer().allRecipes(); for(MCRecipe recipe : recipes) { - ret.push(ObjectGenerator.GetGenerator().recipe(recipe, t)); + ret.push(ObjectGenerator.GetGenerator().recipe(recipe, t), t); } - + return ret; } @@ -138,28 +308,32 @@ public String getName() { @Override public Integer[] numArgs() { - return new Integer[] {0}; + return new Integer[]{0}; } @Override public String docs() { return "array {} Gets all recipes on the server."; } - + + @Override + public Version since() { + return MSVersion.V3_3_1; + } + } - - @api + + @api(environments = {CommandHelperEnvironment.class}) public static class clear_recipes extends recipeFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{}; } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { Static.getServer().clearRecipes(); - return CVoid.VOID; } @@ -170,28 +344,32 @@ public String getName() { @Override public Integer[] numArgs() { - return new Integer[] {0}; + return new Integer[]{0}; } @Override public String docs() { - return "Void {} Clears all recipes."; + return "void {} Clears all recipes."; + } + + @Override + public Version since() { + return MSVersion.V3_3_1; } - + } - - @api + + @api(environments = {CommandHelperEnvironment.class}) public static class reset_recipes extends recipeFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{}; } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { Static.getServer().resetRecipes(); - return CVoid.VOID; } @@ -202,13 +380,18 @@ public String getName() { @Override public Integer[] numArgs() { - return new Integer[] {0}; + return new Integer[]{0}; } @Override public String docs() { - return "Void {} Resets all recipes to the default recipes."; + return "void {} Resets all recipes to the default recipes."; + } + + @Override + public Version since() { + return MSVersion.V3_3_1; } - + } } diff --git a/src/main/java/com/laytonsmith/core/functions/Redis.java b/src/main/java/com/laytonsmith/core/functions/Redis.java index 3c246951ae..9b5de957a0 100644 --- a/src/main/java/com/laytonsmith/core/functions/Redis.java +++ b/src/main/java/com/laytonsmith/core/functions/Redis.java @@ -1,4 +1,3 @@ - package com.laytonsmith.core.functions; //import com.laytonsmith.PureUtilities.Version; @@ -13,18 +12,18 @@ //import java.util.Arrays; //import java.util.List; //import redis.clients.jedis.Jedis; - /** * */ public class Redis { - public static String docs(){ + + public static String docs() { return "This class of functions provides hooks into a redis system."; } - -// @api + +// @api // public static class redis extends AbstractFunction { -// +// // private static List functionList = null; // /** // * The list of valid commands is built dynamically based on the functions listed in @@ -45,7 +44,7 @@ public static String docs(){ // //Generic returns aren't supported // continue; // } -// +// // for(Class c : m.getParameterTypes()){ // c // } @@ -87,7 +86,7 @@ public static String docs(){ // @Override // public String docs() { // StringBuilder docs = new StringBuilder(); -// +// // return docs.toString(); // } // @@ -95,6 +94,6 @@ public static String docs(){ // public Version since() { // return CHVersion.V3_3_1; // } -// +// // } } diff --git a/src/main/java/com/laytonsmith/core/functions/Reflection.java b/src/main/java/com/laytonsmith/core/functions/Reflection.java index 746b72828f..83e53f70ee 100644 --- a/src/main/java/com/laytonsmith/core/functions/Reflection.java +++ b/src/main/java/com/laytonsmith/core/functions/Reflection.java @@ -1,36 +1,61 @@ package com.laytonsmith.core.functions; import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery; +import com.laytonsmith.PureUtilities.ClassLoading.ClassMirror.ClassMirror; +import com.laytonsmith.PureUtilities.ClassLoading.DynamicEnum; import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.annotations.MDynamicEnum; import com.laytonsmith.annotations.MEnum; import com.laytonsmith.annotations.api; import com.laytonsmith.annotations.core; -import com.laytonsmith.core.CHVersion; +import com.laytonsmith.core.ArgumentValidation; +import com.laytonsmith.core.FullyQualifiedClassName; +import com.laytonsmith.core.MSVersion; import com.laytonsmith.core.Optimizable; import com.laytonsmith.core.ParseTree; +import com.laytonsmith.core.Procedure; import com.laytonsmith.core.Script; import com.laytonsmith.core.Static; +import com.laytonsmith.core.compiler.CompilerEnvironment; import com.laytonsmith.core.compiler.FileOptions; +import com.laytonsmith.core.compiler.Keyword; +import com.laytonsmith.core.compiler.KeywordList; import com.laytonsmith.core.constructs.CArray; +import com.laytonsmith.core.constructs.CBoolean; +import com.laytonsmith.core.constructs.CClassType; import com.laytonsmith.core.constructs.CFunction; import com.laytonsmith.core.constructs.CInt; import com.laytonsmith.core.constructs.CNull; import com.laytonsmith.core.constructs.CString; -import com.laytonsmith.core.constructs.Construct; import com.laytonsmith.core.constructs.IVariable; +import com.laytonsmith.core.constructs.NativeTypeList; import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.environments.CommandHelperEnvironment; import com.laytonsmith.core.environments.Environment; import com.laytonsmith.core.environments.GlobalEnv; +import com.laytonsmith.core.environments.StaticRuntimeEnv; import com.laytonsmith.core.events.Event; import com.laytonsmith.core.events.EventList; +import com.laytonsmith.core.exceptions.CRE.CRECastException; +import com.laytonsmith.core.exceptions.CRE.CREFormatException; +import com.laytonsmith.core.exceptions.CRE.CREIOException; +import com.laytonsmith.core.exceptions.CRE.CREIllegalArgumentException; +import com.laytonsmith.core.exceptions.CRE.CREInsufficientArgumentsException; +import com.laytonsmith.core.exceptions.CRE.CRENotFoundException; +import com.laytonsmith.core.exceptions.CRE.CREThrowable; import com.laytonsmith.core.exceptions.ConfigCompileException; import com.laytonsmith.core.exceptions.ConfigRuntimeException; -import com.laytonsmith.core.functions.Exceptions.ExceptionType; +import com.laytonsmith.core.natives.interfaces.MEnumTypeValue; +import com.laytonsmith.core.natives.interfaces.Mixed; +import com.laytonsmith.core.objects.ObjectDefinition; +import com.laytonsmith.core.objects.ObjectDefinitionTable; +import com.laytonsmith.core.telemetry.Telemetry; import com.laytonsmith.persistence.DataSourceFactory; import com.laytonsmith.persistence.PersistenceNetwork; + import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; @@ -41,7 +66,7 @@ import java.util.regex.Pattern; /** - * + * */ @core public class Reflection { @@ -50,17 +75,18 @@ public static String docs() { return "This class of functions allows scripts to hook deep into the interpreter itself," + " and get meta information about the operations of a script. This is useful for" + " debugging, testing, and ultra dynamic scripting. See the" - + " [[CommandHelper/Reflection|guide to reflection]] on the wiki for more" + + " [[Reflection|guide to reflection]] on the wiki for more" + " details. In order to make the most of these functions, you should familiarize" + " yourself with the general workings of the language. These functions explore" + " extremely advanced concepts, and should normally not be used; especially" + " if you are not familiar with the language."; } - @api(environments={CommandHelperEnvironment.class}) + @api public static class reflect_pull extends AbstractFunction { - private static Set protocols; + private static Set protocols; + @Override public String getName() { return "reflect_pull"; @@ -74,26 +100,96 @@ public Integer[] numArgs() { @Override public String docs() { return "mixed {param, [args, ...]} Returns information about the runtime in a usable" - + " format. Depending on the information returned, it may be useable directly," + + " format. Depending on the information returned, it may be usable directly," + " or it may be more of a referential format. ---- The following items can be retrieved:" - + "" - + "" - + "" - + "" - + "" - + "" - + "" - + "" - + "" - + "
paramargsreturns/description
labelReturn the label that the script is currently running under
commandReturns the command that was used to fire off this script (if applicable)
varlist[name]Returns a list of currently in scope variables. If name" - + " is provided, the currently set value is instead returned.
line_numThe current line number
fileThe absolute path to the current file
colThe current column number
datasourcesAn array of data source protocols available
enum[enum name]An array of enum names, or if one if provided, a list of all" - + " the values in that enum
"; - //+ "" - } - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.FormatException, ExceptionType.IOException}; + + "{|\n" + + "|-\n" + + "! param\n" + + "! args\n" + + "! returns/description\n" + + "|-\n" + + "| label\n" + + "| \n" + + "| Return the label that the script is currently running under\n" + + "|-\n" + + "| command\n" + + "|\n" + + "| Returns the command that was used to fire off this script (if applicable)\n" + + "|-\n" + + "| varlist\n" + + "| [name]\n" + + "| Returns a list of currently in scope variables. If name" + + " is provided, the currently set value is instead returned.\n" + + "|-\n" + + "| line_num\n" + + "|\n" + + "| The current line number\n" + + "|-\n" + + "| file\n" + + "|\n" + + "| The absolute path to the current file\n" + + "|-\n" + + "| dir\n" + + "|\n" + + "| The absolute path to the directory that the current file is in\n" + + "|-\n" + + "| col\n" + + "|\n" + + "| The current column number\n" + + "|-\n" + + "| datasources\n" + + "|\n" + + "| An array of data source protocols available\n" + + "|-\n" + + "| enum\n" + + "| [enum name]\n" + + "| An array of enum names, or if one is provided, a list of all" + + " the values in that enum\n" + + "|-\n" + + "| keywords\n" + + "| [keyword name]\n" + + "| Lists the keywords, if no parameter is provided, otherwise" + + " provides the documentation for the specified keyword\n" + + "|-\n" + + "| telemetry_session\n" + + "|\n" + + "| The session id used in telemetry for the current session. This will return null if" + + " telemetry is disabled. In general, this key is used to track individual sessions. If" + + " needed, this can be provided to the maintainers to associate your data with you. If" + + " you wish for your telemetry data to remain anonymous, do not provide this key to the" + + " maintainers. (People without access to the telemetry system cannot do anything with" + + " the key anyways.)\n" + + "|-\n" + + "| file_options_author\n" + + "|\n" + + "| Returns the author name, as set in the file options. Empty string is returned if it" + + " is not set.\n" + + "|-\n" + + "| file_options_created\n" + + "|\n" + + "| Returns the created date, as set in the file options. Empty string is returned if it" + + " is not set.\n" + + "|-\n" + + "| file_options_description\n" + + "|\n" + + "| Returns the file description, as set in the file options. Empty string is returned if it" + + " is not set.\n" + + "|-\n" + + "| file_options_copyright\n" + + "|\n" + + "| Returns the copyright information, as set in the file options. Empty string is returned if it" + + " is not set.\n" + + "|-\n" + + "| file_options_license\n" + + "|\n" + + "| Returns the code license, as set in the file options. Empty string is returned if it" + + " is not set.\n" + + "|}"; + } + + @Override + public Class[] thrown() { + return new Class[]{CREFormatException.class, CREIOException.class}; } @Override @@ -107,81 +203,208 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { - if (args.length < 1) { - throw new ConfigRuntimeException("Not enough parameters was sent to " + getName(), ExceptionType.InsufficientArgumentsException, t); + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + if(args.length < 1) { + throw new CREInsufficientArgumentsException("Not enough parameters was sent to " + getName(), t); } String param = args[0].val(); - if ("label".equalsIgnoreCase(param)) { + if("label".equalsIgnoreCase(param)) { return new CString(env.getEnv(GlobalEnv.class).GetLabel(), t); - } else if ("command".equalsIgnoreCase(param)) { + } else if("command".equalsIgnoreCase(param)) { return new CString(env.getEnv(CommandHelperEnvironment.class).GetCommand(), t); - } else if ("varlist".equalsIgnoreCase(param)) { - if (args.length == 1) { + } else if("varlist".equalsIgnoreCase(param)) { + if(args.length == 1) { //No name provided CArray ca = new CArray(t); - for (String name : env.getEnv(GlobalEnv.class).GetVarList().keySet()) { - ca.push(new CString(name, t)); + for(String name : env.getEnv(GlobalEnv.class).GetVarList().keySet()) { + ca.push(new CString(name, t), t); } return ca; - } else if (args.length == 2) { + } else if(args.length == 2) { //The name was provided String name = args[1].val(); - return env.getEnv(GlobalEnv.class).GetVarList().get(name, t).ival(); + return env.getEnv(GlobalEnv.class).GetVarList().get(name, t, env).ival(); } - } else if ("line_num".equalsIgnoreCase(param)) { + } else if("line_num".equalsIgnoreCase(param)) { return new CInt(t.line(), t); - } else if ("file".equalsIgnoreCase(param)) { - if (t.file() == null) { + } else if("file".equalsIgnoreCase(param)) { + if(t.file() == null) { return new CString("Unknown (maybe the interpreter?)", t); } else { try { - return new CString(t.file().getCanonicalPath().replace("\\", "/"), t); + return new CString(t.file().getCanonicalPath().replace('\\', '/'), t); } catch (IOException ex) { - throw new ConfigRuntimeException(ex.getMessage(), ExceptionType.IOException, t); + throw new CREIOException(ex.getMessage(), t); } } - } else if ("col".equalsIgnoreCase(param)) { + } else if("dir".equalsIgnoreCase(param)) { + if(t.file() == null) { + return new CString("Unknown (maybe the interpreter?)", t); + } else { + try { + String dir = t.file().getParentFile().getCanonicalPath().replace('\\', '/') + "/"; + return new CString(dir, t); + } catch (IOException ex) { + throw new CREIOException(ex.getMessage(), t); + } + } + } else if("col".equalsIgnoreCase(param)) { return new CInt(t.col(), t); - } else if("datasources".equalsIgnoreCase(param)){ - if(protocols == null){ - protocols = new HashSet(); - for(String s : DataSourceFactory.GetSupportedProtocols()){ + } else if("datasources".equalsIgnoreCase(param)) { + if(protocols == null) { + protocols = new HashSet<>(); + for(String s : DataSourceFactory.GetSupportedProtocols()) { protocols.add(new CString(s, Target.UNKNOWN)); } } return new CArray(t, protocols); - } else if("enum".equalsIgnoreCase(param)){ + } else if("enum".equalsIgnoreCase(param)) { CArray a = new CArray(t); - Set> enums = ClassDiscovery.getDefaultInstance().loadClassesWithAnnotationThatExtend(MEnum.class, Enum.class); - if(args.length == 1){ - //No name provided - for(Class e : enums){ - a.push(new CString(e.getAnnotation(MEnum.class).value(), t)); + Set> enums = ClassDiscovery.getDefaultInstance().getClassesWithAnnotationThatExtend(MEnum.class, Enum.class); + Set> dEnums = ClassDiscovery.getDefaultInstance().getClassesWithAnnotationThatExtend(MDynamicEnum.class, DynamicEnum.class); + if(args.length == 1) { + try { + //No name provided + for(ClassMirror e : enums) { + String name = (String) e.getAnnotation(MEnum.class).getValue("value"); + a.push(CClassType.get(FullyQualifiedClassName.forNativeEnum(e.loadClass())), t); + } + for(ClassMirror d : dEnums) { + String name = (String) d.getAnnotation(MDynamicEnum.class).getValue("value"); + a.push(CClassType.get(FullyQualifiedClassName.forFullyQualifiedClass(name)), t); + } + } catch (ClassNotFoundException ex) { + throw new Error(ex); } - } else if(args.length == 2){ - String enumName = args[1].val(); - for(Class e : enums){ - if(e.getAnnotation(MEnum.class).value().equals(enumName)){ - for(Enum ee : e.getEnumConstants()){ - a.push(new CString(ee.name(), t)); - } - break; + } else if(args.length == 2) { + FullyQualifiedClassName enumName = FullyQualifiedClassName.forName(args[1].val(), t, env); + try { + for(MEnumTypeValue v : NativeTypeList.getNativeEnumType(enumName).values()) { + a.push(v, t); } + } catch (ClassNotFoundException ex) { + // Actually, I don't think this can + throw new CRECastException("Cannot find enum of type " + enumName, t, ex); } } return a; + } else if("keywords".equalsIgnoreCase(param)) { + if(args.length == 1) { + CArray a = new CArray(t); + List l = new ArrayList<>(KeywordList.getKeywordNames()); + l.forEach((String t1) -> { + a.push(new CString(t1, Target.UNKNOWN), Target.UNKNOWN); + }); + return new ArrayHandling.array_sort().exec(t, env, a); + } else if(args.length == 2) { + Keyword k = KeywordList.getKeywordByName(args[1].val()); + if(k == null) { + throw new CREIllegalArgumentException(args[1].val() + " is not a valid keyword", t); + } + return new CString(k.docs(), Target.UNKNOWN); + } + } else if("telemetry_session".equalsIgnoreCase(param)) { + String session = Telemetry.GetDefault().getSessionId(); + if(session == null) { + return CNull.NULL; + } + return new CString(session, t); + } else if("file_options_author".equalsIgnoreCase(param)) { + String author = env.getEnv(GlobalEnv.class).GetFileOptions().getAuthor(); + return new CString(author, t); + } else if("file_options_created".equalsIgnoreCase(param)) { + String created = env.getEnv(GlobalEnv.class).GetFileOptions().getCreated(); + return new CString(created, t); + } else if("file_options_description".equalsIgnoreCase(param)) { + String description = env.getEnv(GlobalEnv.class).GetFileOptions().getDescription(); + return new CString(description, t); + } else if("file_options_copyright".equalsIgnoreCase(param)) { + String copyright = env.getEnv(GlobalEnv.class).GetFileOptions().getCopyright(); + return new CString(copyright, t); + } else if("file_options_license".equalsIgnoreCase(param)) { + String license = env.getEnv(GlobalEnv.class).GetFileOptions().getLicense(); + return new CString(license, t); + } + + throw new CREFormatException("The arguments passed to " + getName() + " are incorrect. Please check them and try again.", t); + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_1; + } + } + + @api + public static class reflect_type extends AbstractFunction { + + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return null; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + CClassType type = ArgumentValidation.getClassType(args[0], t); + CArray ret = CArray.GetAssociativeArray(t); + ret.set("fqcn", type.getFQCN().getFQCN()); + ret.set("name", type.getFQCN().getSimpleName()); + ret.set("interfaces", new CArray(t, type.getTypeInterfaces(environment)), t); + ret.set("superclasses", new CArray(t, type.getTypeSuperclasses(environment)), t); + + CArray typeDocs = new CArray(t); + // When type unions are a thing, this will need to be implemented slightly differently. + for(CClassType m : Arrays.asList(type)) { + CArray docs = CArray.GetAssociativeArray(t); + docs.set("package", type.getPackage() == null ? CNull.NULL : type.getPackage(), t); + docs.set("isNative", CBoolean.get(type.getNativeType() != null), t); + docs.set("docs", m.getTypeDocs(t, environment)); + docs.set("since", m.getTypeSince(t, environment).toString()); + typeDocs.push(docs, t); } + ret.set("typeDocs", typeDocs, t); + return ret; + } + + @Override + public String getName() { + return "reflect_type"; + } - throw new ConfigRuntimeException("The arguments passed to " + getName() + " are incorrect. Please check them and try again.", - ExceptionType.FormatException, t); + @Override + public Integer[] numArgs() { + return new Integer[]{1}; } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public String docs() { + return getBundledDocs(); } + + @Override + public Version since() { + return MSVersion.V3_3_4; + } + + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("With ClassType references", "reflect_type(string)"), + new ExampleScript("Via typeof()", "int @i = 1;\nmsg(reflect_type(typeof(@i)));") + }; + } + } @api @@ -204,8 +427,8 @@ public static DocField getValue(String value) { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CREFormatException.class}; } @Override @@ -219,31 +442,31 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { String element = args[0].val(); DocField docField; try { docField = DocField.getValue(args[1].val()); } catch (IllegalArgumentException e) { - throw new ConfigRuntimeException("Invalid docField provided: " + args[1].val(), ExceptionType.FormatException, t); + throw new CREFormatException("Invalid docField provided: " + args[1].val(), t); } //For now, we have special handling, since functions are actually the only thing that will work, //but eventually this will be a generic interface. - if (element.startsWith("@")) { - IVariable var = environment.getEnv(GlobalEnv.class).GetVarList().get(element, t); - if (var == null) { - throw new ConfigRuntimeException("Invalid variable provided: " + element + " does not exist in the current scope", ExceptionType.FormatException, t); + if(element.startsWith("@")) { + IVariable var = env.getEnv(GlobalEnv.class).GetVarList().get(element, t, env); + if(var == null) { + throw new CREFormatException("Invalid variable provided: " + element + " does not exist in the current scope", t); } - } else if (element.startsWith("_")) { - if (!environment.getEnv(GlobalEnv.class).GetProcs().containsKey(element)) { - throw new ConfigRuntimeException("Invalid procedure name provided: " + element + " does not exist in the current scope", ExceptionType.FormatException, t); + } else if(element.startsWith("_")) { + if(!env.getEnv(GlobalEnv.class).GetProcs().containsKey(element)) { + throw new CREFormatException("Invalid procedure name provided: " + element + " does not exist in the current scope", t); } } else { try { - Function f = (Function) FunctionList.getFunction(new CFunction(element, t)); + Function f = (Function) FunctionList.getFunction(new CFunction(element, t), env.getEnvClasses()); return new CString(formatFunctionDoc(f.docs(), docField), t); } catch (ConfigCompileException ex) { - throw new ConfigRuntimeException("Unknown function: " + element, ExceptionType.FormatException, t); + throw new CREFormatException("Unknown function: " + element, t); } } return CNull.NULL; @@ -252,37 +475,43 @@ public Construct exec(Target t, Environment environment, Construct... args) thro public String formatFunctionDoc(String docs, DocField field) { Pattern p = Pattern.compile("(?s)\\s*(.*?)\\s*\\{(.*?)\\}\\s*(.*)\\s*"); Matcher m = p.matcher(docs); - if (!m.find()) { - throw new Error("An error has occured in " + getName() + ". While trying to get the documentation" + if(!m.find()) { + throw new Error("An error has occurred in " + getName() + ". While trying to get the documentation" + ", it was unable to parse this: " + docs); } - if (field == DocField.RETURN || field == DocField.TYPE) { + if(field == DocField.RETURN || field == DocField.TYPE) { return m.group(1); - } else if (field == DocField.ARGS) { + } else if(field == DocField.ARGS) { return m.group(2); - } else if (field == DocField.DESCRIPTION) { + } else if(field == DocField.DESCRIPTION) { return m.group(3); } throw new Error("Unhandled case in formatFunctionDoc!"); } @Override - public ParseTree optimizeDynamic(Target t, List children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { - if(children.isEmpty()){ + public ParseTree optimizeDynamic(Target t, Environment env, + Set> envs, + List children, FileOptions fileOptions) + throws ConfigCompileException, ConfigRuntimeException { + if(children.isEmpty()) { //They are requesting this function's documentation. We can just return a string, //and then it will never actually get called, so we handle it entirely in here. return new ParseTree(new CString(docs(), t), null); } - if (children.get(0).isConst()) { + if(children.size() == 1) { + return null; // not enough arguments, so an exception will be thrown later + } + if(children.get(0).isConst()) { //If it's a function, we can check to see if it actually exists, //and make it a compile error if it doesn't, even if parameter 2 is dynamic String value = children.get(0).getData().val(); - if (!value.startsWith("_") && !value.startsWith("@")) { + if(!value.startsWith("_") && !value.startsWith("@")) { //It's a function - FunctionList.getFunction(new CFunction(value, t)); + FunctionList.getFunction(new CFunction(value, t), env.getEnvClasses()); } } - if (children.get(1).isConst()) { + if(children.get(1).isConst()) { try { DocField.getValue(children.get(1).getData().val()); } catch (IllegalArgumentException e) { @@ -291,12 +520,12 @@ public ParseTree optimizeDynamic(Target t, List children, FileOptions } return null; } - + @Override public Set optimizationOptions() { return EnumSet.of( - OptimizationOption.CONSTANT_OFFLINE, - OptimizationOption.OPTIMIZE_DYNAMIC + OptimizationOption.CONSTANT_OFFLINE, + OptimizationOption.OPTIMIZE_DYNAMIC ); } @@ -321,17 +550,26 @@ public String docs() { } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; + } + + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("Return type", "reflect_docs('array_contains', 'return'); // Using 'type' would also work"), + new ExampleScript("Args", "reflect_docs('array_contains', 'args');"), + new ExampleScript("Description", "reflect_docs('array_contains', 'description');") + }; } } - + @api public static class get_functions extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{}; } @Override @@ -344,29 +582,29 @@ public Boolean runAsync() { return false; } - private static Map> funcs = new HashMap>(); - - private void initf() { - for (FunctionBase f : FunctionList.getFunctionList(api.Platforms.INTERPRETER_JAVA)) { + private static final Map> FUNCS = new HashMap>(); + + private void initf(Environment env) { + for(FunctionBase f : FunctionList.getFunctionList(api.Platforms.INTERPRETER_JAVA, env.getEnvClasses())) { String[] pack = f.getClass().getEnclosingClass().getName().split("\\."); String clazz = pack[pack.length - 1]; - if (!funcs.containsKey(clazz)) { - funcs.put(clazz, new ArrayList()); + if(!FUNCS.containsKey(clazz)) { + FUNCS.put(clazz, new ArrayList()); } - funcs.get(clazz).add(f.getName()); + FUNCS.get(clazz).add(f.getName()); } } - + @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { CArray ret = CArray.GetAssociativeArray(t); - if (funcs.keySet().size() < 10) { - initf(); + if(FUNCS.keySet().size() < 10) { + initf(environment); } - for (String cname : funcs.keySet()) { + for(String cname : FUNCS.keySet()) { CArray fnames = new CArray(t); - for (String fname : funcs.get(cname)) { - fnames.push(new CString(fname, t)); + for(String fname : FUNCS.get(cname)) { + fnames.push(new CString(fname, t), t); } ret.set(new CString(cname, t), fnames, t); } @@ -392,16 +630,16 @@ public String docs() { @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } } - + @api public static class get_events extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{}; } @Override @@ -415,13 +653,13 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, - Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, + Mixed... args) throws ConfigRuntimeException { CArray ret = new CArray(t); - for (Event event : EventList.GetEvents()) { - ret.push(new CString(event.getName(), t)); + for(Event event : EventList.GetEvents()) { + ret.push(new CString(event.getName(), t), t); } - ret.sort(CArray.SortType.STRING_IC); + ret.sort(CArray.ArraySortType.STRING_IC); return ret; } @@ -442,16 +680,16 @@ public String docs() { @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } } - - @api + + @api(environments = {CommandHelperEnvironment.class}) public static class get_aliases extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{}; } @Override @@ -465,12 +703,12 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { CArray ret = new CArray(t); - for (Script s : Static.getAliasCore().getScripts()) { - ret.push(new CString(s.getSignature(), t)); + for(Script s : Static.getAliasCore().getScripts()) { + ret.push(new CString(s.getSignature(), t), t); } - ret.sort(CArray.SortType.STRING_IC); + ret.sort(CArray.ArraySortType.STRING_IC); return ret; } @@ -491,16 +729,16 @@ public String docs() { @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } } - + @api public static class reflect_value_source extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{}; } @Override @@ -514,8 +752,8 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - PersistenceNetwork pn = environment.getEnv(GlobalEnv.class).GetPersistenceNetwork(); + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + PersistenceNetwork pn = environment.getEnv(StaticRuntimeEnv.class).GetPersistenceNetwork(); return new CString(pn.getKeySource(args[0].val().split("\\.")).toString(), t); } @@ -534,13 +772,199 @@ public String docs() { return "string {persistenceKey} Returns the source file that this key will store a value to in the Persistence Network." + " For instance, in your persistence.ini file, if you have the entry \"storage.test.**=json:///path/to/file.json\"," + " then reflect_value_source('storage.test.testing') would return 'json:///path/to/file.json'. This is useful for" - + " debugging, as it will definitively trace back the source/destination of a value."; + + " debugging, as it will definitively trace back the source/destination of a value. Note that unlike get and" + + " store_value, the \"storage\" prefix is required, if you're searching for values that are stored using" + + " these functions."; + } + + @Override + public Version since() { + return MSVersion.V3_3_1; + } + + } + + @api + public static class get_procedures extends AbstractFunction { + + @Override + public Class[] thrown() { + return new Class[]{}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + CArray ret = new CArray(t); + for(Map.Entry p : environment.getEnv(GlobalEnv.class).GetProcs().entrySet()) { + ret.push(new CString(p.getKey(), t), t); + } + ret.sort(CArray.ArraySortType.STRING_IC); + return ret; + } + + @Override + public String getName() { + return "get_procedures"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{0}; + } + + @Override + public String docs() { + return "array {} Returns an array of procedures callable in the current scope."; + } + + @Override + public Version since() { + return MSVersion.V3_3_1; + } + + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("Simple example", "msg(get_procedures());\n" + + "proc _testProc() {}\n" + + "msg(get_procedures());"), + new ExampleScript("Example with procedures within procedures", "msg(get_procedures());\n" + + "proc _testProc() {\n" + + "\tproc _innerProc() {}\n" + + "\tmsg(get_procedures());\n" + + "}\n" + + "_testProc();\n" + + "msg(get_procedures());") + }; + } + } + + @api + public static class get_classes extends AbstractFunction { + + @Override + public Class[] thrown() { + return null; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return null; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + CArray ret = new CArray(t); + ObjectDefinitionTable odt = environment.getEnv(CompilerEnvironment.class).getObjectDefinitionTable(); + for(ObjectDefinition od : odt.getObjectDefinitionSet()) { + try { + ret.push(CClassType.get(FullyQualifiedClassName.forFullyQualifiedClass(od.getClassName())), t); + } catch (ClassNotFoundException ex) { + throw ConfigRuntimeException.CreateUncatchableException(ex.getMessage(), t); + } + } +// for(FullyQualifiedClassName c : NativeTypeList.getNativeTypeList()) { +// CClassType cct; +// try { +// cct = CClassType.get(c); +// } catch (ClassNotFoundException ex) { +// throw ConfigRuntimeException.CreateUncatchableException(ex.getMessage(), t); +// } +// if(cct == CNull.TYPE) { +// continue; +// } +// ret.push(cct, t); +// } + return ret; + } + + @Override + public String getName() { + return "get_classes"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{0}; + } + + @Override + public String docs() { + return "array {} Returns a list of all known classes. This may not be completely exhaustive, but will" + + " at least contain all system defined classes. The returned value is an array of ClassType" + + " objects."; + } + + @Override + public Version since() { + return MSVersion.V3_3_3; + } + + } + + @api + public static class class_type extends AbstractFunction { + + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class, CRENotFoundException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return null; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + String type = ArgumentValidation.getStringObject(args[0], t); + try { + return CClassType.get(FullyQualifiedClassName.forName(args[0].val(), t, environment)); + } catch (CRECastException | ClassNotFoundException ex) { + throw new CRENotFoundException("Could not find type " + type, t); + } + } + + @Override + public String getName() { + return "class_type"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1}; + } + + @Override + public String docs() { + return "ClassType {string name} Returns a ClassType object for the given fully qualified class name." + + " If the given class doesn't exist, a NotFoundException is thrown."; } @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_4; } - + } } diff --git a/src/main/java/com/laytonsmith/core/functions/Regex.java b/src/main/java/com/laytonsmith/core/functions/Regex.java index 9dce559cb8..d4fc2aba6b 100644 --- a/src/main/java/com/laytonsmith/core/functions/Regex.java +++ b/src/main/java/com/laytonsmith/core/functions/Regex.java @@ -1,27 +1,31 @@ - - package com.laytonsmith.core.functions; -import com.laytonsmith.PureUtilities.Common.ReflectionUtils; import com.laytonsmith.annotations.api; import com.laytonsmith.annotations.core; import com.laytonsmith.annotations.seealso; -import com.laytonsmith.core.CHVersion; +import com.laytonsmith.core.ArgumentValidation; +import com.laytonsmith.core.MSVersion; +import com.laytonsmith.core.ObjectGenerator; import com.laytonsmith.core.Optimizable; import com.laytonsmith.core.ParseTree; -import com.laytonsmith.core.Static; import com.laytonsmith.core.compiler.FileOptions; import com.laytonsmith.core.constructs.CArray; +import com.laytonsmith.core.constructs.CClosure; import com.laytonsmith.core.constructs.CFunction; import com.laytonsmith.core.constructs.CInt; -import com.laytonsmith.core.constructs.CNull; import com.laytonsmith.core.constructs.CString; import com.laytonsmith.core.constructs.Construct; import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.environments.Environment; +import com.laytonsmith.core.exceptions.CRE.CRECastException; +import com.laytonsmith.core.exceptions.CRE.CREFormatException; +import com.laytonsmith.core.exceptions.CRE.CREThrowable; +import com.laytonsmith.core.functions.StringHandling.replace; +import com.laytonsmith.core.functions.StringHandling.split; import com.laytonsmith.core.exceptions.ConfigCompileException; import com.laytonsmith.core.exceptions.ConfigRuntimeException; -import com.laytonsmith.core.functions.Exceptions.ExceptionType; +import com.laytonsmith.core.natives.interfaces.Callable; +import com.laytonsmith.core.natives.interfaces.Mixed; import java.util.EnumSet; import java.util.HashSet; import java.util.List; @@ -31,105 +35,93 @@ import java.util.regex.PatternSyntaxException; /** - * + * */ @core public class Regex { - - public static String docs(){ - return "This class provides regular expression functions. For more details, please see the page on " - + "[[CommandHelper/Regex|regular expressions]]. Note that all the functions are just passthroughs" - + " to the Java regex mechanism. If you need to set a flag on the regex, where the api calls" - + " for a pattern, instead send array('pattern', 'flags') where flags is any of i, m, or s." - + " Alternatively, using the embedded flag system that Java provides is also valid. Named captures are" - + " also supported if you are using Java 7, otherwise they are not supported."; - } - - @api public static class reg_match extends AbstractFunction implements Optimizable { - - @Override - public String getName() { - return "reg_match"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{2}; - } - - @Override - public String docs() { - return "array {pattern, subject} Searches for the given pattern, and returns an array with the results. Captures are supported." - + " If the pattern is not found anywhere in the subject, an empty array is returned. The indexes of the array" - + " follow typical regex fashion; the 0th element is the whole match, and 1-n are the captures specified in" - + " the regex."; - } - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.FormatException}; - } - - @Override - public boolean isRestricted() { - return false; - } - - - @Override - public CHVersion since() { - return CHVersion.V3_2_0; - } - - @Override - public Boolean runAsync() { - return null; - } - - @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { - Pattern pattern = getPattern(args[0], t); - String subject = args[1].val(); - CArray ret = CArray.GetAssociativeArray(t); - Matcher m = pattern.matcher(subject); - if(m.find()){ - ret.set(0, new CString(m.group(0), t), t); - for(int i = 1; i <= m.groupCount(); i++){ - if(m.group(i) == null){ - ret.set(i, CNull.NULL, t); - } else { - ret.set(i, Static.resolveConstruct(m.group(i), t), t); - } - } - //Named groups are only supported in Java 7, but we can - //dynamically enable this feature if they have it. - Set namedGroups = getNamedGroups(pattern.pattern()); - try{ - for(String key : namedGroups){ - ret.set(key, (String)ReflectionUtils.invokeMethod(Matcher.class, m, "group", new Class[]{String.class}, new Object[]{key}), t); - } - } catch(ReflectionUtils.ReflectionException ex){ - throw new ConfigRuntimeException("Named captures are only supported with Java 7.", ExceptionType.FormatException, t); + + public static String docs() { + return "This class provides regular expression functions. For more details, please see the page on " + + "[[Regex|regular expressions]]. Note that all the functions are just passthroughs" + + " to the Java regex mechanism. If you need to set a flag on the regex, where the api calls" + + " for a pattern, instead send array('pattern', 'flags') where flags is any of i, m, or s." + + " Alternatively, using the embedded flag system that Java provides is also valid."; + } + + @api + public static class reg_match extends AbstractFunction implements Optimizable { + + @Override + public String getName() { + return "reg_match"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{2}; + } + + @Override + public String docs() { + return "array {pattern, subject} Searches for the given pattern, and returns an array with the results. Captures are supported." + + " If the pattern is not found anywhere in the subject, an empty array is returned. The indexes of the array" + + " follow typical regex fashion; the 0th element is the whole match, and 1-n are the captures specified in" + + " the regex."; + } + + @Override + public Class[] thrown() { + return new Class[]{CREFormatException.class}; + } + + @Override + public boolean isRestricted() { + return false; + } + + @Override + public MSVersion since() { + return MSVersion.V3_2_0; + } + + @Override + public Boolean runAsync() { + return null; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + Pattern pattern = getPattern(args[0], t); + String subject = args[1].val(); + Matcher m = pattern.matcher(subject); + if(m.find()) { + CArray ret = ObjectGenerator.GetGenerator().regMatchValue(m, t); + for(String key : getNamedGroups(pattern.pattern())) { + ret.set(key, m.group(key), t); } - } - return ret; - } - - @Override - public ParseTree optimizeDynamic(Target t, List children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { - if(!children.get(0).getData().isDynamic()){ - getPattern(children.get(0).getData(), t); - } - return null; - } - + return ret; + } + return CArray.GetAssociativeArray(t); + } + + @Override + public ParseTree optimizeDynamic(Target t, Environment env, + Set> envs, + List children, FileOptions fileOptions) + throws ConfigCompileException, ConfigRuntimeException { + if(!Construct.IsDynamicHelper(children.get(0).getData())) { + getPattern(children.get(0).getData(), t); + } + return null; + } + @Override public Set optimizationOptions() { return EnumSet.of( - OptimizationOption.CONSTANT_OFFLINE, - OptimizationOption.CACHE_RETURN, - OptimizationOption.OPTIMIZE_DYNAMIC, - OptimizationOption.NO_SIDE_EFFECTS + OptimizationOption.CONSTANT_OFFLINE, + OptimizationOption.CACHE_RETURN, + OptimizationOption.OPTIMIZE_DYNAMIC, + OptimizationOption.NO_SIDE_EFFECTS ); } @@ -137,534 +129,548 @@ public Set optimizationOptions() { public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Basic usage", "reg_match('(\\\\d)(\\\\d)(\\\\d)', 'abc123')"), - //Java 7 can't be assumed to be working on the system running the doc gen, so we'll hardcode these. - new ExampleScript("Named captures (Only works if your system is running Java 7)", - "reg_match('abc(?\\\\d+)(xyz)', 'abc123xyz')", "{0: abc123xyz, 1: 123, 2: xyz, foo: 123}"), - new ExampleScript("Named captures with backreferences (Only works if your system is running Java 7)", - "reg_match('abc(?\\\\d+)def\\\\k', 'abc123def123')['foo']", "123") + new ExampleScript("Named captures ", + "reg_match('abc(?\\\\d+)(xyz)', 'abc123xyz')", "{0: abc123xyz, 1: 123, 2: xyz, foo: 123}"), + new ExampleScript("Named captures with backreferences", + "reg_match('abc(?\\\\d+)def\\\\k', 'abc123def123')['foo']", "123") }; } - - } - - @api public static class reg_match_all extends AbstractFunction implements Optimizable { + + } + + @api + public static class reg_match_all extends AbstractFunction implements Optimizable { @Override - public String getName() { - return "reg_match_all"; - } + public String getName() { + return "reg_match_all"; + } @Override - public Integer[] numArgs() { - return new Integer[]{2}; - } + public Integer[] numArgs() { + return new Integer[]{2}; + } @Override - public String docs() { - return "array {pattern, subject} Searches subject for all matches to the regular expression given in pattern, unlike reg_match," - + " which just returns the first match."; - } + public String docs() { + return "array {pattern, subject} Searches subject for all matches to the regular expression given in pattern, unlike reg_match," + + " which just returns the first match."; + } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.FormatException}; - } + public Class[] thrown() { + return new Class[]{CREFormatException.class}; + } @Override - public boolean isRestricted() { - return false; - } + public boolean isRestricted() { + return false; + } - @Override - public CHVersion since() { - return CHVersion.V3_2_0; - } + public MSVersion since() { + return MSVersion.V3_2_0; + } @Override - public Boolean runAsync() { - return null; - } + public Boolean runAsync() { + return null; + } @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { - Pattern pattern = getPattern(args[0], t); - String subject = args[1].val(); - CArray fret = new CArray(t); - Matcher m = pattern.matcher(subject); + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + Pattern pattern = getPattern(args[0], t); + String subject = args[1].val(); + CArray fret = new CArray(t); + Matcher m = pattern.matcher(subject); Set namedGroups = getNamedGroups(pattern.pattern()); - while(m.find()){ - CArray ret = CArray.GetAssociativeArray(t); - ret.set(0, new CString(m.group(0), t), t); - - for(int i = 1; i <= m.groupCount(); i++){ - ret.set(i, new CString(m.group(i), t), t); - } - //Named groups are only supported in Java 7, but we can - //dynamically enable this feature if they have it. - try{ - for(String key : namedGroups){ - ret.set(key, (String)ReflectionUtils.invokeMethod(Matcher.class, m, "group", new Class[]{String.class}, new Object[]{key}), t); - } - } catch(ReflectionUtils.ReflectionException e){ - throw new ConfigRuntimeException("Named captures are only supported with Java 7.", ExceptionType.FormatException, t); + while(m.find()) { + CArray ret = ObjectGenerator.GetGenerator().regMatchValue(m, t); + + for(String key : namedGroups) { + ret.set(key, m.group(key), t); } - fret.push(ret); - } - return fret; - } - - @Override - public ParseTree optimizeDynamic(Target t, List children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { - if(!children.get(0).getData().isDynamic()){ - getPattern(children.get(0).getData(), t); - } - return null; - } - + fret.push(ret, t); + } + return fret; + } + + @Override + public ParseTree optimizeDynamic(Target t, Environment env, + Set> envs, + List children, FileOptions fileOptions) + throws ConfigCompileException, ConfigRuntimeException { + if(!Construct.IsDynamicHelper(children.get(0).getData())) { + getPattern(children.get(0).getData(), t); + } + return null; + } + @Override public Set optimizationOptions() { return EnumSet.of( - OptimizationOption.CONSTANT_OFFLINE, - OptimizationOption.CACHE_RETURN, - OptimizationOption.OPTIMIZE_DYNAMIC, - OptimizationOption.NO_SIDE_EFFECTS + OptimizationOption.CONSTANT_OFFLINE, + OptimizationOption.CACHE_RETURN, + OptimizationOption.OPTIMIZE_DYNAMIC, + OptimizationOption.NO_SIDE_EFFECTS ); } - + @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Basic usage", "reg_match_all('(\\\\d{3})', 'abc123456')"), - //Same thing here, can't guarantee we're running Java 7 when these are generated. - new ExampleScript("Named captures (Only works if your system is running Java 7)", - "reg_match_all('abc(?\\\\d+)(xyz)', 'abc123xyz')[0]['foo']", "123"), - new ExampleScript("Named captures with backreferences (Only works if your system is running Java 7)", - "reg_match_all('abc(?\\\\d+)def\\\\k', 'abc123def123')[0]['foo']", "123") + new ExampleScript("Named captures", + "reg_match_all('abc(?\\\\d+)(xyz)', 'abc123xyz')[0]['foo']", "123"), + new ExampleScript("Named captures with backreferences", + "reg_match_all('abc(?\\\\d+)def\\\\k', 'abc123def123')[0]['foo']", "123") }; } - - } - - @api public static class reg_replace extends AbstractFunction implements Optimizable { + + } + + @api + public static class reg_replace extends AbstractFunction implements Optimizable { @Override - public String getName() { - return "reg_replace"; - } + public String getName() { + return "reg_replace"; + } @Override - public Integer[] numArgs() { - return new Integer[]{3}; - } + public Integer[] numArgs() { + return new Integer[]{3}; + } @Override - public String docs() { - return "string {pattern, replacement, subject} Replaces any occurances of pattern with the replacement in subject." - + " Back references are allowed."; - } + public String docs() { + return "string {pattern, replacement, subject} Replaces any occurrences of pattern with the replacement in subject." + + " Back references are allowed." + + " 'replacement' can be a string, or a closure that accepts a found occurrence of 'pattern'" + + " and returns the replacement string value."; + } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.FormatException}; - } + public Class[] thrown() { + return new Class[]{CREFormatException.class, CRECastException.class}; + } @Override - public boolean isRestricted() { - return false; - } + public boolean isRestricted() { + return false; + } - @Override - public CHVersion since() { - return CHVersion.V3_2_0; - } + public MSVersion since() { + return MSVersion.V3_2_0; + } @Override - public Boolean runAsync() { - return null; - } + public Boolean runAsync() { + return null; + } @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { - Pattern pattern = getPattern(args[0], t); - String replacement = args[1].val(); - String subject = args[2].val(); - String ret = ""; - - try { - ret = pattern.matcher(subject).replaceAll(replacement); - } catch (IndexOutOfBoundsException e) { - throw new Exceptions.FormatException("Expecting a regex group at parameter 1 of reg_replace", t); - } catch(IllegalArgumentException e){ - throw new Exceptions.FormatException(e.getMessage(), t); + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + Pattern pattern = getPattern(args[0], t); + Mixed replacement = args[1]; + String subject = args[2].val(); + String ret = ""; + + try { + if(replacement instanceof Callable replacer) { + ret = pattern.matcher(subject).replaceAll(mr -> ArgumentValidation.getStringObject( + replacer.executeCallable(env, t, ObjectGenerator.GetGenerator().regMatchValue(mr, t)), t)); + } else { + ret = pattern.matcher(subject).replaceAll(replacement.val()); + } + } catch (IndexOutOfBoundsException e) { + throw new CREFormatException("Expecting a regex group at parameter 1 of reg_replace", t); + } catch (IllegalArgumentException e) { + throw new CREFormatException(e.getMessage(), t); } - - return new CString(ret, t); - } - @Override - public ParseTree optimizeDynamic(Target t, List children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { - ParseTree data = children.get(0); - if(!data.getData().isDynamic()){ - String pattern = data.getData().val(); - if(isLiteralRegex(pattern)){ + return new CString(ret, t); + } + + @Override + public ParseTree optimizeDynamic(Target t, Environment env, + Set> envs, + List children, FileOptions fileOptions) + throws ConfigCompileException, ConfigRuntimeException { + ParseTree patternArg = children.get(0); + Mixed patternData = patternArg.getData(); + ParseTree replacementArg = children.get(1); + Mixed replacementData = replacementArg.getData(); + if(!Construct.IsDynamicHelper(patternData) && !(replacementData instanceof CClosure) && !Construct.IsDynamicHelper(replacementData)) { + String pattern = patternData.val(); + String replacement = replacementData.val(); + if(isLiteralRegex(pattern) && !isBackreference(replacement)) { //We want to replace this with replace() //Note the alternative order of arguments - ParseTree replace = new ParseTree(new CFunction("replace", t), data.getFileOptions()); - replace.addChildAt(0, children.get(2)); //subject -> main - replace.addChildAt(1, new ParseTree(new CString(getLiteralRegex(pattern), t), replace.getFileOptions())); //pattern -> what - replace.addChildAt(2, children.get(1)); //replacement -> that - return replace; + ParseTree replaceNode = new ParseTree(new CFunction(replace.NAME, t), patternArg.getFileOptions()); + replaceNode.addChildAt(0, children.get(2)); //subject -> main + replaceNode.addChildAt(1, new ParseTree(new CString(getLiteralRegex(pattern), t), replaceNode.getFileOptions())); //pattern -> what + replaceNode.addChildAt(2, children.get(1)); //replacement -> that + return replaceNode; } else { - getPattern(data.getData(), t); + getPattern(patternArg.getData(), t); } - } - return null; -// if(!children.get(0).getData().isDynamic()){ -// getPattern(children.get(0).getData(), t); -// } -// return null; - } - + } + return null; +// if(!children.get(0).getData().isDynamic()){ +// getPattern(children.get(0).getData(), t); +// } +// return null; + } + @Override public Set optimizationOptions() { return EnumSet.of( - OptimizationOption.CONSTANT_OFFLINE, - OptimizationOption.CACHE_RETURN, - OptimizationOption.OPTIMIZE_DYNAMIC, - OptimizationOption.NO_SIDE_EFFECTS + OptimizationOption.CONSTANT_OFFLINE, + OptimizationOption.CACHE_RETURN, + OptimizationOption.OPTIMIZE_DYNAMIC, + OptimizationOption.NO_SIDE_EFFECTS ); } - + @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Basic usage", "reg_replace('\\\\d', 'Z', '123abc')"), - new ExampleScript("Using backreferences", "reg_replace('abc(\\\\d+)', '$1', 'abc123'"), - new ExampleScript("Using backreferences with named captures (Only works if your system is running Java 7)", - "reg_replace('abc(?\\\\d+)', '${foo}', 'abc123')", "123") + new ExampleScript("Using backreferences", "reg_replace('abc(\\\\d+)', '$1', 'abc123')"), + new ExampleScript("Using backreferences with named captures", + "reg_replace('abc(?\\\\d+)', '${foo}', 'abc123')", "123"), + new ExampleScript("Using closure as replacement function", + "reg_replace('cat|dog', closure(@match) {return array('dog': 'cat', 'cat': 'dog')[@match[0]]}," + + " 'Oscar is a cat. Lucy is a dog.')") }; } - - } - - @api + + } + + @api @seealso({StringHandling.split.class, ArrayHandling.array_implode.class}) - public static class reg_split extends AbstractFunction implements Optimizable{ - - private final static String split = new StringHandling.split().getName(); + public static class reg_split extends AbstractFunction implements Optimizable { @Override - public String getName() { - return "reg_split"; - } + public String getName() { + return "reg_split"; + } @Override - public Integer[] numArgs() { - return new Integer[]{2, 3}; - } + public Integer[] numArgs() { + return new Integer[]{2, 3}; + } @Override - public String docs() { - return "array {pattern, subject, [limit]} Splits a string on the given regex, and returns an array of the parts. If" - + " nothing matched, an array with one element, namely the original subject, is returned." + public String docs() { + return "array {pattern, subject, [limit]} Splits a string on the given regex, and returns an array of the parts. If" + + " nothing matched, an array with one element, namely the original subject, is returned." + " Limit defaults to infinity, but if set, only" + " that number of splits will occur."; - } + } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.FormatException, ExceptionType.CastException}; - } + public Class[] thrown() { + return new Class[]{CREFormatException.class, CRECastException.class}; + } @Override - public boolean isRestricted() { - return false; - } + public boolean isRestricted() { + return false; + } - @Override - public CHVersion since() { - return CHVersion.V3_2_0; - } + public MSVersion since() { + return MSVersion.V3_2_0; + } @Override - public Boolean runAsync() { - return null; - } + public Boolean runAsync() { + return null; + } @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { - Pattern pattern = getPattern(args[0], t); - String subject = args[1].val(); + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + Pattern pattern = getPattern(args[0], t); + String subject = args[1].val(); /** - * We use a different indexing notation than Java's regex split. In the case of - * 0 for the limit, we will still return an array of length 1, assuming there are actual - * splits available. In Java, a split of 0 will return the same as length 1. In our method - * though, the limit is the number of splits themselves, so 1 means that the array will be - * length 2, as in, there were 1 splits performed. This matches the behavior of split(). + * We use a different indexing notation than Java's regex split. In the case of 0 for the limit, we will + * still return an array of length 1, assuming there are actual splits available. In Java, a split of 0 will + * return the same as length 1. In our method though, the limit is the number of splits themselves, so 1 + * means that the array will be length 2, as in, there were 1 splits performed. This matches the behavior of + * split(). */ int limit = Integer.MAX_VALUE - 1; - if(args.length >= 3){ - limit = Static.getInt32(args[2], t); + if(args.length >= 3) { + limit = ArgumentValidation.getInt32(args[2], t); + } + String[] rsplit = pattern.split(subject, limit + 1); + CArray ret = new CArray(t); + for(String split : rsplit) { + ret.push(new CString(split, t), t); } - String [] rsplit = pattern.split(subject, limit + 1); - CArray ret = new CArray(t); - for(String split : rsplit){ - ret.push(new CString(split, t)); - } - return ret; - } - - @Override - public ParseTree optimizeDynamic(Target t, List children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { - ParseTree data = children.get(0); - if(!data.getData().isDynamic()){ + return ret; + } + + @Override + public ParseTree optimizeDynamic(Target t, Environment env, + Set> envs, + List children, FileOptions fileOptions) + throws ConfigCompileException, ConfigRuntimeException { + ParseTree data = children.get(0); + if(!Construct.IsDynamicHelper(data.getData())) { String pattern = data.getData().val(); - if(isLiteralRegex(pattern)){ + if(isLiteralRegex(pattern)) { //We want to replace this with split() - ParseTree splitNode = new ParseTree(new CFunction(split, t), data.getFileOptions()); + ParseTree splitNode = new ParseTree(new CFunction(split.NAME, t), data.getFileOptions()); splitNode.addChildAt(0, new ParseTree(new CString(getLiteralRegex(pattern), t), splitNode.getFileOptions())); splitNode.addChildAt(1, children.get(1)); return splitNode; } else { getPattern(data.getData(), t); } - } - return null; - } - + } + return null; + } + @Override public Set optimizationOptions() { return EnumSet.of( - OptimizationOption.CACHE_RETURN, - OptimizationOption.OPTIMIZE_DYNAMIC, - OptimizationOption.NO_SIDE_EFFECTS + OptimizationOption.CACHE_RETURN, + OptimizationOption.OPTIMIZE_DYNAMIC, + OptimizationOption.NO_SIDE_EFFECTS ); } - + @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Basic usage", "reg_split('\\\\d', 'a1b2c3')") }; } - - } - - @api public static class reg_count extends AbstractFunction implements Optimizable { + + } + + @api + public static class reg_count extends AbstractFunction implements Optimizable { @Override - public String getName() { - return "reg_count"; - } + public String getName() { + return "reg_count"; + } @Override - public Integer[] numArgs() { - return new Integer[]{2}; - } + public Integer[] numArgs() { + return new Integer[]{2}; + } @Override - public String docs() { - return "int {pattern, subject} Counts the number of occurances in the subject."; - } + public String docs() { + return "int {pattern, subject} Counts the number of occurrences in the subject."; + } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.FormatException}; - } + public Class[] thrown() { + return new Class[]{CREFormatException.class}; + } @Override - public boolean isRestricted() { - return false; - } + public boolean isRestricted() { + return false; + } - @Override - public CHVersion since() { - return CHVersion.V3_2_0; - } + public MSVersion since() { + return MSVersion.V3_2_0; + } @Override - public Boolean runAsync() { - return null; - } + public Boolean runAsync() { + return null; + } @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { - Pattern pattern = getPattern(args[0], t); - String subject = args[1].val(); - long ret = 0; - Matcher m = pattern.matcher(subject); - while(m.find()){ - ret++; - } - return new CInt(ret, t); - } + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + Pattern pattern = getPattern(args[0], t); + String subject = args[1].val(); + long ret = 0; + Matcher m = pattern.matcher(subject); + while(m.find()) { + ret++; + } + return new CInt(ret, t); + } + + @Override + public ParseTree optimizeDynamic(Target t, Environment env, + Set> envs, + List children, FileOptions fileOptions) + throws ConfigCompileException, ConfigRuntimeException { + if(!Construct.IsDynamicHelper(children.get(0).getData())) { + getPattern(children.get(0).getData(), t); + } + return null; + } - @Override - public ParseTree optimizeDynamic(Target t, List children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { - if(!children.get(0).getData().isDynamic()){ - getPattern(children.get(0).getData(), t); - } - return null; - } - @Override public Set optimizationOptions() { return EnumSet.of( - OptimizationOption.CONSTANT_OFFLINE, - OptimizationOption.CACHE_RETURN, - OptimizationOption.OPTIMIZE_DYNAMIC, - OptimizationOption.NO_SIDE_EFFECTS + OptimizationOption.CONSTANT_OFFLINE, + OptimizationOption.CACHE_RETURN, + OptimizationOption.OPTIMIZE_DYNAMIC, + OptimizationOption.NO_SIDE_EFFECTS ); } - + @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Basic usage", "reg_count('\\\\d', '123abc')") }; } - } - - @api - public static class reg_escape extends AbstractFunction implements Optimizable{ + } + + @api + public static class reg_escape extends AbstractFunction implements Optimizable { @Override - public ExceptionType[] thrown() { - return null; - } + public Class[] thrown() { + return null; + } @Override - public boolean isRestricted() { - return false; - } + public boolean isRestricted() { + return false; + } @Override - public Boolean runAsync() { - return null; - } + public Boolean runAsync() { + return null; + } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - return new CString(java.util.regex.Pattern.quote(args[0].val()), t); - } + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + return new CString(java.util.regex.Pattern.quote(args[0].val()), t); + } @Override - public String getName() { - return "reg_escape"; - } + public String getName() { + return "reg_escape"; + } @Override - public Integer[] numArgs() { - return new Integer[]{1}; - } + public Integer[] numArgs() { + return new Integer[]{1}; + } @Override - public String docs() { - return "string {arg} Escapes arg so that it may be used directly in a regular expression, without fear that" - + " it will have special meaning; that is, it escapes all special characters. Use this if you need" - + " to use user input or similar as a literal search index."; - } + public String docs() { + return "string {arg} Escapes arg so that it may be used directly in a regular expression, without fear that" + + " it will have special meaning; that is, it escapes all special characters. Use this if you need" + + " to use user input or similar as a literal search index."; + } @Override - public CHVersion since() { - return CHVersion.V3_3_1; - } + public MSVersion since() { + return MSVersion.V3_3_1; + } @Override public Set optimizationOptions() { return EnumSet.of( - OptimizationOption.CONSTANT_OFFLINE, - OptimizationOption.CACHE_RETURN, - OptimizationOption.NO_SIDE_EFFECTS + OptimizationOption.CONSTANT_OFFLINE, + OptimizationOption.CACHE_RETURN, + OptimizationOption.NO_SIDE_EFFECTS ); } - + @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Basic usage", "reg_escape('\\\\d+')") }; } - - } - - private static Pattern getPattern(Construct c, Target t) throws ConfigRuntimeException{ - String regex = ""; - int flags = 0; - String sflags = ""; - if(c instanceof CArray){ - CArray ca = (CArray)c; - regex = ca.get(0, t).val(); - sflags = ca.get(1, t).val(); - for(int i = 0; i < sflags.length(); i++){ - if(sflags.toLowerCase().charAt(i) == 'i'){ - flags |= java.util.regex.Pattern.CASE_INSENSITIVE; - } else if(sflags.toLowerCase().charAt(i) == 'm'){ - flags |= java.util.regex.Pattern.MULTILINE; - } else if(sflags.toLowerCase().charAt(i) == 's'){ - flags |= java.util.regex.Pattern.DOTALL; - } else { - throw new ConfigRuntimeException("Unrecognized flag: " + sflags.toLowerCase().charAt(i), ExceptionType.FormatException, t); - } - } - } else { - regex = c.val(); - } - try{ - return Pattern.compile(regex, flags); - } catch(PatternSyntaxException e){ - throw new ConfigRuntimeException(e.getMessage(), ExceptionType.FormatException, t); - } - } - - private static boolean isLiteralRegex(String regex){ + + } + + private static Pattern getPattern(Mixed c, Target t) throws ConfigRuntimeException { + String regex = ""; + int flags = 0; + String sflags = ""; + if(c.isInstanceOf(CArray.TYPE)) { + CArray ca = (CArray) c; + regex = ca.get(0, t).val(); + sflags = ca.get(1, t).val(); + for(int i = 0; i < sflags.length(); i++) { + if(sflags.toLowerCase().charAt(i) == 'i') { + flags |= java.util.regex.Pattern.CASE_INSENSITIVE; + } else if(sflags.toLowerCase().charAt(i) == 'm') { + flags |= java.util.regex.Pattern.MULTILINE; + } else if(sflags.toLowerCase().charAt(i) == 's') { + flags |= java.util.regex.Pattern.DOTALL; + } else { + throw new CREFormatException("Unrecognized flag: " + sflags.toLowerCase().charAt(i), t); + } + } + } else { + regex = c.val(); + } + try { + return Pattern.compile(regex, flags); + } catch (PatternSyntaxException e) { + throw new CREFormatException(e.getMessage(), c.getTarget()); + } + } + + private static boolean isBackreference(String replacement) { + return replacement.length() > 0 && replacement.charAt(0) == '$'; + } + + private static boolean isLiteralRegex(String regex) { //These are the special characters in a regex. If a regex does not contain any of these //characters, we can use a faster method in many cases, though the extra overhead of doing //this check only makes sense during optimization, not runtime. - + //We also are going to check for the special case where the whole regex starts with \Q and ends with \E, which //indicates that they did something like: reg_split(reg_escape('literal string'), '') which is an easily //optimizable case, but we will have to transform the regex to get the actual split index, but that's up //to the function to call getLiteralRegex. If the internal of the regex further contains more \Q or \E identifiers, //they are doing something more complex, so we're just gonna forgo optimizing that. - if(regex.startsWith("\\Q") && regex.endsWith("\\E") - && !regex.substring(2, regex.length() - 2).contains("\\Q") - && !regex.substring(2, regex.length() - 2).contains("\\E") - ){ + if(regex.startsWith("\\Q") && regex.endsWith("\\E") + && !regex.substring(2, regex.length() - 2).contains("\\Q") + && !regex.substring(2, regex.length() - 2).contains("\\E")) { return true; } String chars = "[\\^$.|?*+()"; - for(int i = 0; i < chars.length(); i++){ - if(regex.contains(Character.toString(chars.charAt(i)))){ + for(int i = 0; i < chars.length(); i++) { + if(regex.contains(Character.toString(chars.charAt(i)))) { return false; } } return true; } - - private static String getLiteralRegex(String regex){ - if(regex.startsWith("\\Q") && regex.endsWith("\\E") - && !regex.substring(2, regex.length() - 2).contains("\\Q") - && !regex.substring(2, regex.length() - 2).contains("\\E") - ){ + + private static String getLiteralRegex(String regex) { + if(regex.startsWith("\\Q") && regex.endsWith("\\E") + && !regex.substring(2, regex.length() - 2).contains("\\Q") + && !regex.substring(2, regex.length() - 2).contains("\\E")) { return regex.substring(2, regex.length() - 2); } else { return regex; - } + } } - + private static final Pattern NAMED_GROUP = Pattern.compile("\\(\\?<([a-zA-Z][a-zA-Z0-9]*)>"); - private static Set getNamedGroups(String regex){ + + private static Set getNamedGroups(String regex) { Matcher m = NAMED_GROUP.matcher(regex); Set ret = new HashSet(); - while(m.find()){ + while(m.find()) { ret.add(m.group(1)); } return ret; } - + } diff --git a/src/main/java/com/laytonsmith/core/functions/ResourceManager.java b/src/main/java/com/laytonsmith/core/functions/ResourceManager.java index e94c442b71..290d6ba6a3 100644 --- a/src/main/java/com/laytonsmith/core/functions/ResourceManager.java +++ b/src/main/java/com/laytonsmith/core/functions/ResourceManager.java @@ -1,4 +1,3 @@ - package com.laytonsmith.core.functions; import com.laytonsmith.PureUtilities.Common.StringUtils; @@ -7,16 +6,22 @@ import com.laytonsmith.abstraction.StaticLayer; import com.laytonsmith.annotations.api; import com.laytonsmith.annotations.core; -import com.laytonsmith.core.CHVersion; +import com.laytonsmith.core.ArgumentValidation; +import com.laytonsmith.core.MSVersion; import com.laytonsmith.core.constructs.CResource; import com.laytonsmith.core.constructs.CVoid; -import com.laytonsmith.core.constructs.Construct; import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.environments.Environment; +import com.laytonsmith.core.exceptions.CRE.CRECastException; +import com.laytonsmith.core.exceptions.CRE.CREFormatException; +import com.laytonsmith.core.exceptions.CRE.CRENotFoundException; +import com.laytonsmith.core.exceptions.CRE.CRENullPointerException; +import com.laytonsmith.core.exceptions.CRE.CREThrowable; import com.laytonsmith.core.exceptions.ConfigRuntimeException; -import com.laytonsmith.core.functions.Exceptions.ExceptionType; +import com.laytonsmith.core.natives.interfaces.Mixed; import java.util.HashMap; import java.util.Map; +import java.util.Random; import org.xml.sax.SAXException; /** @@ -24,69 +29,74 @@ */ @core public class ResourceManager { - public static String docs(){ + + public static String docs() { return "This class contains functions for resource management. This entire class of functions WILL" + " be deprecated at some point in the future, so don't rely too heavily on it."; } - + public static enum ResourceTypes { XML_DOCUMENT(XMLDocument.class), - STRING_BUILDER(StringBuffer.class); + STRING_BUILDER(StringBuffer.class), + RANDOM(Random.class); private final Class type; - private ResourceTypes(Class type){ + + private ResourceTypes(Class type) { this.type = type; } - - public Class getType(){ + + public Class getType() { return type; } - - public static ResourceTypes getResourceByType(Class type){ - for(ResourceTypes c : values()){ - if(c.getType() == type){ + + public static ResourceTypes getResourceByType(Class type) { + for(ResourceTypes c : values()) { + if(c.getType() == type) { return c; } } throw new IllegalArgumentException(); } } - - private static final Map> resources = new HashMap>(); + + private static final Map> RESOURCES = new HashMap<>(); + static { - StaticLayer.GetConvertor().addShutdownHook(new Runnable() { + StaticLayer.GetConvertor().addPersistentShutdownHook(new Runnable() { @Override public void run() { - resources.clear(); + RESOURCES.clear(); } }); } - + /** - * This is used to get the appropriately cast resource from a CResource. If the - * types aren't matched up, an appropriate exception is thrown. + * This is used to get the appropriately cast resource from a CResource. If the types aren't matched up, an + * appropriate exception is thrown. + * * @param * @param resource * @param type * @param t - * @return + * @return */ - public static T GetResource(CResource resource, Class type, Target t){ - if(type.isAssignableFrom(resource.getResource().getClass())){ + public static T GetResource(CResource resource, Class type, Target t) { + if(type.isAssignableFrom(resource.getResource().getClass())) { return (T) resource.getResource(); } else { - throw new Exceptions.CastException("Unexpected resource type. Expected resource of type " + throw new CRECastException("Unexpected resource type. Expected resource of type " + ResourceTypes.getResourceByType(type).name() + " but found " + ResourceTypes.getResourceByType(resource.getResource().getClass()).name() + " instead.", t); } } - + @api public static class res_create_resource extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException, ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CRECastException.class, CREFormatException.class}; } @Override @@ -100,33 +110,42 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { ResourceTypes type; - Construct data = null; - try{ + Mixed data = null; + try { type = ResourceTypes.valueOf(args[0].val()); - } catch(IllegalArgumentException e){ - throw new Exceptions.FormatException(e.getMessage(), t); + } catch (IllegalArgumentException e) { + throw new CREFormatException(e.getMessage(), t); } - if(args.length > 1){ + if(args.length > 1) { data = args[1]; } CResource resource; - switch(type){ + switch(type) { case XML_DOCUMENT: try { - if(data == null){ - throw new ConfigRuntimeException("data cannot be empty", ExceptionType.NullPointerException, t); + if(data == null) { + throw new CRENullPointerException("data cannot be empty", t); } resource = new CResource(new XMLDocument(data.val()), t); } catch (SAXException ex) { - throw new Exceptions.FormatException(ex.getMessage(), t); + throw new CREFormatException(ex.getMessage(), t); } break; case STRING_BUILDER: resource = new CResource(new StringBuffer(), new CResource.ResourceToString() { - @Override + @Override + public String getString(CResource res) { + return res.getResource().toString(); + } + }, t); + break; + case RANDOM: + resource = new CResource<>(new Random( + ArgumentValidation.getInt(data, t)), new CResource.ResourceToString() { + @Override public String getString(CResource res) { return res.getResource().toString(); } @@ -135,7 +154,7 @@ public String getString(CResource res) { default: throw new Error("Unhandled case in switch statement"); } - resources.put(resource.getId(), resource); + RESOURCES.put(resource.getId(), resource); return resource; } @@ -151,27 +170,32 @@ public Integer[] numArgs() { @Override public String docs() { - return "resource {type, [data]} Creates a new resource, which is stored in memory. Various" + return "Resource {type, [data]} Creates a new resource, which is stored in memory. Various" + " functions require resources of certain types, which are created with this function." + " Barring resources that you intend on keeping around indefinitely, each call" + " to res_create_resource should be paired with a res_free_resource, being careful" + " to catch any exceptions and still calling res_free_resource anyways. Each resource" - + " has its own data to create the resource. Type may be one of: " - + StringUtils.Join(ResourceTypes.values(), ", ", ", or "); + + " has its own data to create the resource. Type may be one of: " + + StringUtils.Join(ResourceTypes.values(), ", ", ", or ") + + " It's worth noting that this function (and any function that relies on it) is a stopgap" + + " measure, and will eventually be deprecated and removed. For certain types of operations," + + " it's completely unwieldy to implement them in a procedural manner, so this is a temporary" + + " alternative to proper object support."; } @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } - + } - + @api public static class res_free_resource extends AbstractFunction { + @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException, ExceptionType.NotFoundException}; + public Class[] thrown() { + return new Class[]{CRECastException.class, CRENotFoundException.class}; } @Override @@ -185,17 +209,17 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - if(args[0] instanceof CResource){ + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + if(args[0].isInstanceOf(CResource.TYPE)) { CResource resource = (CResource) args[0]; - if(resources.containsKey(resource.getId())){ - resources.remove(resource.getId()); + if(RESOURCES.containsKey(resource.getId())) { + RESOURCES.remove(resource.getId()); return CVoid.VOID; } else { - throw new ConfigRuntimeException("That resource is not a valid resource.", ExceptionType.NotFoundException, t); + throw new CRENotFoundException("That resource is not a valid resource.", t); } } else { - throw new Exceptions.CastException("Expected a resource", t); + throw new CRECastException("Expected a resource", t); } } @@ -211,13 +235,13 @@ public Integer[] numArgs() { @Override public String docs() { - return "void {resource} Frees the given resource. This should ALWAYS be called at some point after creating a resource" + return "void {Resource} Frees the given resource. This should ALWAYS be called at some point after creating a resource" + " with res_create_resource, once you are done with the resource."; } @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } } } diff --git a/src/main/java/com/laytonsmith/core/functions/Routines.java b/src/main/java/com/laytonsmith/core/functions/Routines.java deleted file mode 100644 index bd4e1e962c..0000000000 --- a/src/main/java/com/laytonsmith/core/functions/Routines.java +++ /dev/null @@ -1,148 +0,0 @@ - -package com.laytonsmith.core.functions; - -import com.laytonsmith.annotations.core; - -/** - * - */ -@core -public class Routines { - public static String docs(){ - return "This class of functions provides Routines capabilities. Currently all these functions" - + " are only available via cmdline."; - } -// -// @api public static class get_lock extends AbstractFunction { -// -// @Override -// public Exceptions.ExceptionType[] thrown() { -// return new Exceptions.ExceptionType[]{}; -// } -// -// @Override -// public boolean isRestricted() { -// return true; -// } -// -// @Override -// public Boolean runAsync() { -// return null; -// } -// -// @Override -// public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { -// return new CLock(t); -// } -// -// @Override -// public String getName() { -// return "get_lock"; -// } -// -// @Override -// public Integer[] numArgs() { -// return new Integer[]{0}; -// } -// -// @Override -// public String docs() { -// return "lock {} Returns a lock object, which can be used to provide a reference counting" -// + " mutex amongst threads."; -// } -// -// @Override -// public Version since() { -// return CHVersion.V3_3_1; -// } -// -// } -// -// @api public static class is_lock extends AbstractFunction { -// -// @Override -// public Exceptions.ExceptionType[] thrown() { -// return null; -// } -// -// @Override -// public boolean isRestricted() { -// return false; -// } -// -// @Override -// public Boolean runAsync() { -// return null; -// } -// -// @Override -// public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { -// return CBoolean.get(args[0] instanceof CLock); -// } -// -// @Override -// public String getName() { -// return "is_lock"; -// } -// -// @Override -// public Integer[] numArgs() { -// return new Integer[]{1}; -// } -// -// @Override -// public String docs() { -// return "boolean {object} Returns true iff the object is a lock object."; -// } -// -// @Override -// public Version since() { -// return CHVersion.V3_3_1; -// } -// -// } -// -// @api public static class routine extends AbstractFunction { -// -// @Override -// public Exceptions.ExceptionType[] thrown() { -// return new Exceptions.ExceptionType[]{}; -// } -// -// @Override -// public boolean isRestricted() { -// return true; -// } -// -// @Override -// public Boolean runAsync() { -// return null; -// } -// -// @Override -// public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { -// throw new UnsupportedOperationException("TODO: Not supported yet."); -// } -// -// @Override -// public String getName() { -// return "routine"; -// } -// -// @Override -// public Integer[] numArgs() { -// return new Integer[]{Integer.MAX_VALUE}; -// } -// -// @Override -// public String docs() { -// return ""; -// } -// -// @Override -// public Version since() { -// return CHVersion.V3_3_1; -// } -// -// } -} diff --git a/src/main/java/com/laytonsmith/core/functions/SQL.java b/src/main/java/com/laytonsmith/core/functions/SQL.java index edbdd79c61..18e1d36c3c 100644 --- a/src/main/java/com/laytonsmith/core/functions/SQL.java +++ b/src/main/java/com/laytonsmith/core/functions/SQL.java @@ -3,15 +3,18 @@ import com.laytonsmith.PureUtilities.Common.StringUtils; import com.laytonsmith.PureUtilities.RunnableQueue; import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.abstraction.Implementation; import com.laytonsmith.abstraction.StaticLayer; import com.laytonsmith.annotations.api; import com.laytonsmith.annotations.core; -import com.laytonsmith.core.CHLog; -import com.laytonsmith.core.CHVersion; +import com.laytonsmith.annotations.seealso; +import com.laytonsmith.core.ArgumentValidation; +import com.laytonsmith.core.MSLog; +import com.laytonsmith.core.MSVersion; +import com.laytonsmith.core.LogLevel; import com.laytonsmith.core.ObjectGenerator; import com.laytonsmith.core.Optimizable; import com.laytonsmith.core.ParseTree; -import com.laytonsmith.core.Static; import com.laytonsmith.core.compiler.FileOptions; import com.laytonsmith.core.constructs.CArray; import com.laytonsmith.core.constructs.CBoolean; @@ -26,11 +29,19 @@ import com.laytonsmith.core.constructs.Construct; import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.environments.Environment; -import com.laytonsmith.core.environments.GlobalEnv; +import com.laytonsmith.core.environments.StaticRuntimeEnv; import com.laytonsmith.core.exceptions.ConfigCompileException; import com.laytonsmith.core.exceptions.ConfigRuntimeException; -import com.laytonsmith.core.functions.Exceptions.ExceptionType; import com.laytonsmith.core.Profiles; +import com.laytonsmith.core.ProfilesImpl; +import com.laytonsmith.core.compiler.CompilerEnvironment; +import com.laytonsmith.core.compiler.CompilerWarning; +import com.laytonsmith.core.exceptions.CRE.CRECastException; +import com.laytonsmith.core.exceptions.CRE.CRESQLException; +import com.laytonsmith.core.exceptions.CRE.CREThrowable; +import com.laytonsmith.core.functions.StringHandling.concat; +import com.laytonsmith.core.functions.StringHandling.sconcat; +import com.laytonsmith.core.natives.interfaces.Mixed; import com.laytonsmith.database.SQLProfile; import java.sql.Connection; import java.sql.DriverManager; @@ -58,11 +69,23 @@ public static String docs() { } @api - public static class query extends AbstractFunction implements Optimizable{ + @seealso({unsafe_query.class, query_async.class, com.laytonsmith.tools.docgen.templates.SQL.class, + com.laytonsmith.tools.docgen.templates.Profiles.class}) + public static class query extends AbstractFunction implements Optimizable { + + private final boolean doWarn; + + public query() { + this(true); + } + + protected query(boolean doWarn) { + this.doWarn = doWarn; + } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.SQLException}; + public Class[] thrown() { + return new Class[]{CRESQLException.class}; } @Override @@ -75,48 +98,101 @@ public Boolean runAsync() { return null; } + private static final Object CONNECTION_POOL_LOCK = new Object(); + private static Map connectionPool = null; + private static final boolean USE_CONNECTION_POOL = true; + + private Connection getConnection(String connectionString, Target t) throws SQLException { + if(!USE_CONNECTION_POOL) { + return DriverManager.getConnection(connectionString); + } + synchronized(CONNECTION_POOL_LOCK) { + if(connectionPool == null) { + connectionPool = new HashMap<>(); + StaticLayer.GetConvertor().addShutdownHook(new Runnable() { + + @Override + public void run() { + synchronized(CONNECTION_POOL_LOCK) { + for(Connection c : connectionPool.values()) { + try { + c.close(); + } catch (SQLException ex) { + // + } + } + connectionPool = null; + } + } + }); + } + if(!connectionPool.containsKey(connectionString)) { + connectionPool.put(connectionString, DriverManager.getConnection(connectionString)); + } + Connection c = connectionPool.get(connectionString); + boolean isValid = false; + try { + isValid = c.isValid(3); + } catch (AbstractMethodError ex) { + // isValid is added in later versions. We want to continue working, (as if the connection + // is not valid) but still warn the user that this will + // be slower. + MSLog.GetLogger().Log(MSLog.Tags.GENERAL, LogLevel.WARNING, "SQL driver does not support the \"isValid\" method, which" + + " is causing " + Implementation.GetServerType().getBranding() + " to use a slower method.", t); + } + if(c.isClosed() || !isValid) { + // The connection is closed or invalid, so redo it. + c = DriverManager.getConnection(connectionString); + connectionPool.put(connectionString, c); + } + return c; + } + } + @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { try { Profiles.Profile profile; - if (args[0] instanceof CArray) { + if(args[0].isInstanceOf(CArray.TYPE)) { Map data = new HashMap<>(); - for (String key : ((CArray) args[0]).stringKeySet()) { + for(String key : ((CArray) args[0]).stringKeySet()) { data.put(key, ((CArray) args[0]).get(key, t).val()); } - profile = Profiles.getProfile(data); + profile = ProfilesImpl.getProfile(data); } else { - Profiles profiles = environment.getEnv(GlobalEnv.class).getProfiles(); + Profiles profiles = environment.getEnv(StaticRuntimeEnv.class).getProfiles(); profile = profiles.getProfileById(args[0].val()); } - if(!(profile instanceof SQLProfile)){ - throw new ConfigRuntimeException("Profile must be an SQL type profile, but found \"" + profile.getType() + "\"", ExceptionType.CastException, t); + if(!(profile instanceof SQLProfile)) { + throw new CRECastException("Profile must be an SQL type profile, but found \"" + profile.getType() + "\"", t); } String query = args[1].val(); - Construct[] params = new Construct[args.length - 2]; - for (int i = 2; i < args.length; i++) { + Mixed[] params = new Mixed[args.length - 2]; + for(int i = 2; i < args.length; i++) { int index = i - 2; params[index] = args[i]; - if(params[index] instanceof CNull){ + if(params[index] instanceof CNull) { params[index] = null; } } //Parameters are now all parsed into java objects. SQLProfile sqlProfile = (SQLProfile) profile; - Connection conn = DriverManager.getConnection(sqlProfile.getConnectionString()); + Connection conn = getConnection(sqlProfile.getConnectionString(), t); int autogeneratedKeys = Statement.RETURN_GENERATED_KEYS; - if(!sqlProfile.getAutogeneratedKeys(query)){ + if(!sqlProfile.getAutogeneratedKeys(query)) { autogeneratedKeys = Statement.NO_GENERATED_KEYS; } - try (PreparedStatement ps = conn.prepareStatement(query, autogeneratedKeys)) { - for (int i = 0; i < params.length; i++) { - int type = ps.getParameterMetaData().getParameterType(i + 1); - if (params[i] == null) { + try(PreparedStatement ps = conn.prepareStatement(query, autogeneratedKeys)) { + for(int i = 0; i < params.length; i++) { + int type = sqlProfile.providesParameterTypes() + ? ps.getParameterMetaData().getParameterType(i + 1) + : Types.VARCHAR; + if(params[i] == null) { try { - if (ps.getParameterMetaData().isNullable(i + 1) == ParameterMetaData.parameterNoNulls) { - throw new ConfigRuntimeException("Parameter " + (i + 1) + " cannot be set to null. Check your parameters and try again.", ExceptionType.SQLException, t); + if(ps.getParameterMetaData().isNullable(i + 1) == ParameterMetaData.parameterNoNulls) { + throw new CRESQLException("Parameter " + (i + 1) + " cannot be set to null. Check your parameters and try again.", t); } - } catch(SQLException ex){ + } catch (SQLException ex) { //Ignored. This appears to be able to happen in various cases, but in the case where it *does* work, we don't want //to completely disable the feature. } @@ -124,76 +200,95 @@ public Construct exec(Target t, Environment environment, Construct... args) thro continue; } try { - if (params[i] instanceof CInt) { - ps.setLong(i + 1, Static.getInt(params[i], t)); - } else if (params[i] instanceof CDouble) { - ps.setDouble(i + 1, (Double) Static.getDouble(params[i], t)); - } else if (params[i] instanceof CString) { - ps.setString(i + 1, (String) params[i].val()); - } else if (params[i] instanceof CByteArray) { + if(params[i].isInstanceOf(CInt.TYPE)) { + ps.setLong(i + 1, ArgumentValidation.getInt(params[i], t)); + } else if(params[i].isInstanceOf(CDouble.TYPE)) { + ps.setDouble(i + 1, (Double) ArgumentValidation.getDouble(params[i], t)); + } else if(params[i].isInstanceOf(CString.TYPE)) { + if(type == Types.NCHAR || type == Types.NVARCHAR || type == Types.LONGNVARCHAR) { + ps.setNString(i + 1, (String) params[i].val()); + } else { + ps.setString(i + 1, (String) params[i].val()); + } + } else if(params[i].isInstanceOf(CByteArray.TYPE)) { ps.setBytes(i + 1, ((CByteArray) params[i]).asByteArrayCopy()); - } else if (params[i] instanceof CBoolean) { - ps.setBoolean(i + 1, Static.getBoolean(params[i])); - }else{ - throw new ConfigRuntimeException("The type " + params[i].getClass().getSimpleName() - + " of parameter " + (i + 1) + " is not supported." - , ExceptionType.CastException, t); + } else if(params[i].isInstanceOf(CBoolean.TYPE)) { + ps.setBoolean(i + 1, ArgumentValidation.getBoolean(params[i], t)); + } else { + throw new CRECastException("The type " + params[i].getClass().getSimpleName() + + " of parameter " + (i + 1) + " is not supported.", t); } } catch (ClassCastException ex) { - throw new ConfigRuntimeException("Could not cast parameter " + (i + 1) + " to " + throw new CRECastException("Could not cast parameter " + (i + 1) + " to " + ps.getParameterMetaData().getParameterTypeName(i + 1) + " from " - + params[i].getClass().getSimpleName() + "." - , ExceptionType.CastException, t, ex); + + params[i].getClass().getSimpleName() + ".", t, ex); } } boolean isResultSet = ps.execute(); - if (isResultSet) { + if(isResultSet) { //Result set CArray ret = new CArray(t); ResultSetMetaData md = ps.getMetaData(); ResultSet rs = ps.getResultSet(); - while (rs != null && rs.next()) { - CArray row = new CArray(t); - for (int i = 1; i <= md.getColumnCount(); i++) { + while(rs != null && rs.next()) { + CArray row = CArray.GetAssociativeArray(t); + for(int i = 1; i <= md.getColumnCount(); i++) { Construct value; int columnType = md.getColumnType(i); - if (columnType == Types.INTEGER - || columnType == Types.TINYINT - || columnType == Types.SMALLINT - || columnType == Types.BIGINT) { - value = new CInt(rs.getLong(i), t); - } else if (columnType == Types.FLOAT - || columnType == Types.DOUBLE - || columnType == Types.REAL - || columnType == Types.DECIMAL - || columnType == Types.NUMERIC) { - value = new CDouble(rs.getDouble(i), t); - } else if (columnType == Types.VARCHAR - || columnType == Types.CHAR - || columnType == Types.LONGVARCHAR) { - value = new CString(rs.getString(i), t); - } else if (columnType == Types.BLOB - || columnType == Types.BINARY - || columnType == Types.VARBINARY - || columnType == Types.LONGVARBINARY) { - value = CByteArray.wrap(rs.getBytes(i), t); - } else if (columnType == Types.DATE - || columnType == Types.TIME - || columnType == Types.TIMESTAMP) { - if (md.getColumnTypeName(i).equals("YEAR")){ + switch(columnType) { + case Types.INTEGER: + case Types.TINYINT: + case Types.SMALLINT: + case Types.BIGINT: value = new CInt(rs.getLong(i), t); - } else { - value = new CInt(rs.getTimestamp(i).getTime(), t); - } - } else if (columnType == Types.BOOLEAN - || columnType == Types.BIT) { - value = CBoolean.get(rs.getBoolean(i)); - } else { - throw new ConfigRuntimeException("SQL returned a unhandled column type " - + md.getColumnTypeName(i) + " for column " + md.getColumnName(i) + "." - , ExceptionType.CastException, t); + break; + case Types.FLOAT: + case Types.DOUBLE: + case Types.REAL: + case Types.DECIMAL: + case Types.NUMERIC: + value = new CDouble(rs.getDouble(i), t); + break; + case Types.NCHAR: + case Types.NVARCHAR: + case Types.LONGNVARCHAR: + value = new CString(rs.getNString(i), t); + break; + case Types.VARCHAR: + case Types.CHAR: + case Types.LONGVARCHAR: + value = new CString(rs.getString(i), t); + break; + case Types.BLOB: + case Types.BINARY: + case Types.VARBINARY: + case Types.LONGVARBINARY: + value = CByteArray.wrap(rs.getBytes(i), t); + break; + + case Types.DATE: + case Types.TIME: + case Types.TIMESTAMP: + if(md.getColumnTypeName(i).equals("YEAR")) { + value = new CInt(rs.getLong(i), t); + } else if(rs.getTimestamp(i) == null) { + // Normally we check for null below, but since + // we want to dereference the value now, we have + // to have a specific null check here. + value = CNull.NULL; + } else { + value = new CInt(rs.getTimestamp(i).getTime(), t); + } + break; + case Types.BOOLEAN: + case Types.BIT: + value = CBoolean.get(rs.getBoolean(i)); + break; + default: + throw new CRECastException("SQL returned a unhandled column type " + + md.getColumnTypeName(i) + " for column " + md.getColumnName(i) + ".", t); } - if(rs.wasNull()){ + if(rs.wasNull()) { // Since mscript can assign null to primitives, we // can set it to null regardless of the data type. value = CNull.NULL; @@ -204,13 +299,13 @@ public Construct exec(Target t, Environment environment, Construct... args) thro // the user will expect in the results. row.set(md.getColumnLabel(i), value, t); } - ret.push(row); + ret.push(row, t); } return ret; } else { ResultSet rs = ps.getGeneratedKeys(); - if (rs.next()) { - //This was an insert or something that returned generated keys. So we return + if(rs.next()) { + //This was an insert or something that returned generated keys. So we return //that here. return new CInt(rs.getInt(1), t); } @@ -218,52 +313,61 @@ public Construct exec(Target t, Environment environment, Construct... args) thro return CNull.NULL; } } finally { - conn.close(); + if(!USE_CONNECTION_POOL) { + conn.close(); + } } } catch (Profiles.InvalidProfileException | SQLException ex) { - throw new ConfigRuntimeException(ex.getMessage(), ExceptionType.SQLException, t, ex); + throw new CRESQLException(ex.getMessage(), t, ex); } } @Override - public ParseTree optimizeDynamic(Target t, List children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { - if(children.size() < 2){ + public ParseTree optimizeDynamic(Target t, Environment env, + Set> envs, + List children, FileOptions fileOptions) + throws ConfigCompileException, ConfigRuntimeException { + if(children.size() < 2) { throw new ConfigCompileException(getName() + " expects at least 2 arguments", t); } //We can check 2 things here, one, that the statement isn't dynamic, and if not, then //2, that the parameter count matches the ? count. No checks can be done for typing, //without making a connection to the db though, so we won't do that here. - Construct queryData = children.get(1).getData(); - if(queryData instanceof CFunction){ + Mixed queryData = children.get(1).getData(); + if(queryData instanceof CFunction) { //If it's a concat or sconcat, warn them that this is bad - if("sconcat".equals(queryData.val()) || "concat".equals(queryData.val())){ - CHLog.GetLogger().w(CHLog.Tags.COMPILER, "Use of concatenated query detected! This" + if(doWarn && (sconcat.NAME.equals(queryData.val()) || concat.NAME.equals(queryData.val()))) { + String msg = "Use of concatenated query detected! This" + " is very bad practice, and could lead to SQL injection vulnerabilities" + " in your code. It is highly recommended that you use prepared queries," - + " which ensure that your parameters are properly escaped.", t); + + " which ensure that your parameters are properly escaped. If you really" + + " must use concatenation, and you promise you know what you're doing, you" + + " can use " + new unsafe_query().getName() + "() to suppress this warning."; + env.getEnv(CompilerEnvironment.class).addCompilerWarning(fileOptions, + new CompilerWarning(msg, t, null)); } - } else if(queryData instanceof CString){ + } else if(queryData.isInstanceOf(CString.TYPE)) { //It's a hard coded query, so we can double check parameter lengths and other things String query = queryData.val(); int count = 0; - for(char c : query.toCharArray()){ - if(c == '?'){ + for(char c : query.toCharArray()) { + if(c == '?') { count++; } } //-2 accounts for the profile data and query - if(children.size() - 2 != count){ + if(children.size() - 2 != count) { throw new ConfigCompileException( StringUtils.PluralTemplateHelper(count, "%d parameter token was", "%d parameter tokens were") - + " found in the query, but " - + StringUtils.PluralTemplateHelper(children.size() - 2, "%d parameter was", "%d parameters were") - + " provided to query().", t); + + " found in the query, but " + + StringUtils.PluralTemplateHelper(children.size() - 2, "%d parameter was", "%d parameters were") + + " provided to query().", t); } //TODO: Need to get the SQL Profile data from the environment before this can be done. //Profile validation will simply ensure that the profile stated is listed in the profiles, //and that a connection can in fact be made. //Also need to figure out how to validate a prepared statement. -// if(children.get(0).isConst() && children.get(0).getData() instanceof CString){ +// if(children.get(0).isConst() && children.get(0).getData().isInstanceOf(CString.TYPE)){ // if(true){ //Prefs.verifyQueries() // String profileName = children.get(0).getData().val(); // SQLProfiles.Profile profile = null; @@ -299,7 +403,7 @@ public String docs() { @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } @Override @@ -310,16 +414,51 @@ public Set optimizationOptions() { } @api + @seealso({query.class, com.laytonsmith.tools.docgen.templates.SQL.class, + com.laytonsmith.tools.docgen.templates.Profiles.class}) + public static class unsafe_query extends query { + + public unsafe_query() { + super(false); + } + + @Override + public String docs() { + return "mixed {profile, query, [parameters...]} Executes a query, just like the {{function|query}} function, however," + + " no validation is done to ensure that SQL injections might occur (essentially allowing for concatenation directly" + + " in the query). Otherwise, functions exactly the same as query()."; + } + + @Override + public String getName() { + return "unsafe_query"; + } + + @Override + public Version since() { + return MSVersion.V3_3_1; + } + + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return null; + } + + } + + @api + @seealso({query.class, com.laytonsmith.tools.docgen.templates.SQL.class, + com.laytonsmith.tools.docgen.templates.Profiles.class}) public static class query_async extends AbstractFunction { RunnableQueue queue = null; boolean started = false; - private synchronized void startup(){ - if(queue == null){ + private synchronized void startup() { + if(queue == null) { queue = new RunnableQueue("MethodScript-queryAsync"); } - if(!started){ + if(!started) { queue.invokeLater(null, new Runnable() { @Override @@ -341,8 +480,8 @@ public void run() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException}; + public Class[] thrown() { + return new Class[]{CRECastException.class}; } @Override @@ -356,34 +495,35 @@ public Boolean runAsync() { } @Override - public Construct exec(final Target t, final Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(final Target t, final Environment environment, Mixed... args) throws ConfigRuntimeException { startup(); - Construct arg = args[args.length - 1]; - if(!(arg instanceof CClosure)){ - throw new ConfigRuntimeException("The last argument to " + getName() + " must be a closure.", ExceptionType.CastException, t); + Mixed arg = args[args.length - 1]; + if(!(arg.isInstanceOf(CClosure.TYPE))) { + throw new CRECastException("The last argument to " + getName() + " must be a closure.", t); } - final CClosure closure = ((CClosure)arg); - final Construct[] newArgs = new Construct[args.length - 1]; + final CClosure closure = ((CClosure) arg); + final Mixed[] newArgs = new Mixed[args.length - 1]; //Make a new array minus the closure System.arraycopy(args, 0, newArgs, 0, newArgs.length); - queue.invokeLater(environment.getEnv(GlobalEnv.class).GetDaemonManager(), new Runnable() { + queue.invokeLater(environment.getEnv(StaticRuntimeEnv.class).GetDaemonManager(), new Runnable() { @Override public void run() { - Construct returnValue = CNull.NULL; - Construct exception = CNull.NULL; - try{ + Mixed returnValue = CNull.NULL; + Mixed exception = CNull.NULL; + try { returnValue = new query().exec(t, environment, newArgs); - } catch(ConfigRuntimeException ex){ - exception = ObjectGenerator.GetGenerator().exception(ex, t); + } catch (ConfigRuntimeException ex) { + exception = ObjectGenerator.GetGenerator().exception(ex, environment, t); } - final Construct cret = returnValue; - final Construct cex = exception; - StaticLayer.GetConvertor().runOnMainThreadLater(environment.getEnv(GlobalEnv.class).GetDaemonManager(), new Runnable() { + final Mixed cret = returnValue; + final Mixed cex = exception; + StaticLayer.GetConvertor().runOnMainThreadLater( + environment.getEnv(StaticRuntimeEnv.class).GetDaemonManager(), new Runnable() { @Override public void run() { - closure.execute(new Construct[]{cret, cex}); + closure.executeCallable(new Mixed[]{cret, cex}); } }); } @@ -409,13 +549,13 @@ public String docs() { + " The callback should have the following signature: closure(@contents, @exception){ <code> }." + " @contents will contain the return value that query would normally return. If @exception is not" + " null, then an exception occurred during the query, and that exception will be passed in. If" - + " @exception is null, then no error occured, though @contents may still be null if query() would" + + " @exception is null, then no error occurred, though @contents may still be null if query() would" + " otherwise have returned null."; } @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } } diff --git a/src/main/java/com/laytonsmith/core/functions/Sandbox.java b/src/main/java/com/laytonsmith/core/functions/Sandbox.java index da5dcd1467..037cf53459 100644 --- a/src/main/java/com/laytonsmith/core/functions/Sandbox.java +++ b/src/main/java/com/laytonsmith/core/functions/Sandbox.java @@ -1,414 +1,374 @@ package com.laytonsmith.core.functions; -import com.laytonsmith.abstraction.MCEnchantment; -import com.laytonsmith.abstraction.MCItemStack; -import com.laytonsmith.abstraction.MCPlayer; -import com.laytonsmith.abstraction.StaticLayer; +import com.laytonsmith.PureUtilities.Common.StreamUtils; +import com.laytonsmith.abstraction.enums.MCChatColor; +import com.laytonsmith.core.FileWriteMode; +import com.laytonsmith.PureUtilities.Common.StringUtils; +import com.laytonsmith.PureUtilities.TermColors; +import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.PureUtilities.ZipReader; import com.laytonsmith.annotations.api; import com.laytonsmith.annotations.hide; -import com.laytonsmith.core.BukkitDirtyRegisteredListener; -import com.laytonsmith.core.CHVersion; +import com.laytonsmith.annotations.noboilerplate; +import com.laytonsmith.commandhelper.BukkitDirtyRegisteredListener; +import com.laytonsmith.core.ArgumentValidation; +import com.laytonsmith.core.MSVersion; +import com.laytonsmith.core.MethodScriptCompiler; +import com.laytonsmith.core.ParseTree; +import com.laytonsmith.core.Security; import com.laytonsmith.core.Static; -import com.laytonsmith.core.constructs.CArray; -import com.laytonsmith.core.constructs.CBoolean; -import com.laytonsmith.core.constructs.CNull; +import com.laytonsmith.core.compiler.analysis.StaticAnalysis; +import com.laytonsmith.core.constructs.CByteArray; +import com.laytonsmith.core.constructs.CDouble; +import com.laytonsmith.core.constructs.CInt; +import com.laytonsmith.core.constructs.CResource; import com.laytonsmith.core.constructs.CString; import com.laytonsmith.core.constructs.CVoid; -import com.laytonsmith.core.constructs.Construct; import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.environments.CommandHelperEnvironment; import com.laytonsmith.core.environments.Environment; import com.laytonsmith.core.environments.GlobalEnv; +import com.laytonsmith.core.environments.StaticRuntimeEnv; import com.laytonsmith.core.events.BoundEvent; +import com.laytonsmith.core.exceptions.CRE.CREBindException; +import com.laytonsmith.core.exceptions.CRE.CRECastException; +import com.laytonsmith.core.exceptions.CRE.CREIOException; +import com.laytonsmith.core.exceptions.CRE.CREIncludeException; +import com.laytonsmith.core.exceptions.CRE.CRESecurityException; +import com.laytonsmith.core.exceptions.CRE.CREThrowable; +import com.laytonsmith.core.exceptions.ConfigCompileException; +import com.laytonsmith.core.exceptions.ConfigCompileGroupException; import com.laytonsmith.core.exceptions.ConfigRuntimeException; -import com.laytonsmith.core.functions.Exceptions.ExceptionType; +import com.laytonsmith.core.natives.interfaces.Mixed; import org.bukkit.event.Cancellable; +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import org.apache.commons.io.FileUtils; + /** * */ public class Sandbox { - public static String docs() { - return "This class is for functions that are experimental. They don't actually get added" - + " to the documentation, and are subject to removal at any point in time, nor are they" - + " likely to have good documentation."; - } - - //This broke as of 1.1 -// @api -// public static class plugin_cmd extends AbstractFunction { -// -// public String getName() { -// return "plugin_cmd"; -// } -// -// public Integer[] numArgs() { -// return new Integer[]{2}; -// } -// -// public String docs() { -// return "void {plugin, cmd} "; -// } -// -// public ExceptionType[] thrown() { -// return null; -// } -// -// public boolean isRestricted() { -// return true; -// } -// -// // -// public boolean preResolveVariables() { -// return true; -// } -// -// public CHVersion since() { -// return "0.0.0"; -// } -// -// public Boolean runAsync() { -// return false; -// } -// -// public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { -// Object o = AliasCore.parent.getServer().getPluginManager(); -// if (o instanceof SimplePluginManager) { -// SimplePluginManager spm = (SimplePluginManager) o; -// try { -// Method m = spm.getClass().getDeclaredMethod("getEventListeners", Event.Type.class); -// m.setAccessible(true); -// SortedSet sl = (SortedSet) m.invoke(spm, Event.Type.SERVER_COMMAND); -// for(RegisteredListener l : sl){ -// if (l.getPlugin().getDescription().getName().equalsIgnoreCase(args[0].val())) { -// if(env.GetCommandSender() instanceof ConsoleCommandSender){ -// l.callEvent(new ServerCommandEvent((ConsoleCommandSender)env.GetCommandSender(), args[1].val())); -// } -// } -// } -// SortedSet ss = (SortedSet) m.invoke(spm, Event.Type.PLAYER_COMMAND_PREPROCESS); -// -// for (RegisteredListener l : ss) { -// if (l.getPlugin().getDescription().getName().equalsIgnoreCase(args[0].val())) { -// if(env.GetCommandSender() instanceof MCPlayer){ -// l.callEvent(new PlayerCommandPreprocessEvent(((BukkitMCPlayer)env.GetPlayer())._Player(), args[1].val())); -// } -// PluginCommand.class.getDeclaredMethods(); -// Constructor c = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class); -// c.setAccessible(true); -// List argList = Arrays.asList(args[1].val().split(" ")); -// Command com = (Command) c.newInstance(argList.get(0).substring(1), l.getPlugin()); -// l.getPlugin().onCommand(((BukkitMCCommandSender)env.GetCommandSender())._CommandSender(), com, argList.get(0).substring(1), argList.subList(1, argList.size()).toArray(new String[]{})); -// } -// } -// } catch (InstantiationException ex) { -// Logger.getLogger(Sandbox.class.getName()).log(Level.SEVERE, null, ex); -// } catch (IllegalAccessException ex) { -// Logger.getLogger(Sandbox.class.getName()).log(Level.SEVERE, null, ex); -// } catch (IllegalArgumentException ex) { -// Logger.getLogger(Sandbox.class.getName()).log(Level.SEVERE, null, ex); -// } catch (InvocationTargetException ex) { -// Logger.getLogger(Sandbox.class.getName()).log(Level.SEVERE, null, ex); -// } catch (NoSuchMethodException ex) { -// Logger.getLogger(Sandbox.class.getName()).log(Level.SEVERE, null, ex); -// } catch (SecurityException ex) { -// Logger.getLogger(Sandbox.class.getName()).log(Level.SEVERE, null, ex); -// } -// } -// -// return CVoid.VOID; -// } -// } - - - @api(environments={CommandHelperEnvironment.class}) - public static class super_cancel extends AbstractFunction { - - @Override - public String getName() { - return "super_cancel"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{0}; - } - - @Override - public String docs() { - return "void {} \"Super Cancels\" an event. This only will work if play-dirty is set to true. If an event is" - + " super cancelled, not only is the cancelled flag set to true, the event stops propagating down, so" - + " no other plugins (as in other server plugins, not just CH scripts) will receive the event at all " - + " (other than monitor level plugins). This is useful for overridding" - + " event handlers for plugins that don't respect the cancelled flag. This function hooks into the play-dirty" - + " framework that injects custom event handlers into bukkit."; - } - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.BindException}; - } - - @Override - public boolean isRestricted() { - return true; - } - @Override - public CHVersion since() { - return CHVersion.V3_3_0; - } - - @Override - public Boolean runAsync() { - return null; - } - - @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - BoundEvent.ActiveEvent original = environment.getEnv(GlobalEnv.class).GetEvent(); - if (original == null) { - throw new ConfigRuntimeException("is_cancelled cannot be called outside an event handler", ExceptionType.BindException, t); - } - if (original.getUnderlyingEvent() != null && original.getUnderlyingEvent() instanceof Cancellable - && original.getUnderlyingEvent() instanceof org.bukkit.event.Event) { - ( (Cancellable) original.getUnderlyingEvent() ).setCancelled(true); - BukkitDirtyRegisteredListener.setCancelled((org.bukkit.event.Event) original.getUnderlyingEvent()); - } - environment.getEnv(GlobalEnv.class).GetEvent().setCancelled(true); - return CVoid.VOID; - } - } - - @api(environments={CommandHelperEnvironment.class}) - public static class enchant_inv_unsafe extends AbstractFunction { - - @Override - public String getName() { - return "enchant_inv_unsafe"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{3, 4}; - } - - @Override - public String docs() { - return "void {[player], slot, type, level} Works the same as enchant_inv, except anything goes. " - + " You can enchant a fish with a level 5000 enchantment if you wish. Side effects" - + " may include nausia, dry mouth, insomnia, or server crashes. (Seriously, this might" - + " crash your server, be careful with it.)"; - } - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException, ExceptionType.EnchantmentException, ExceptionType.PlayerOfflineException}; - } - - @Override - public boolean isRestricted() { - return true; - } - @Override - public CHVersion since() { - return CHVersion.V0_0_0; - } + public static String docs() { + return "This class is for functions that are experimental. They don't actually get added" + + " to the documentation, and are subject to removal at any point in time, nor are they" + + " likely to have good documentation."; + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class super_cancel extends AbstractFunction { @Override - public Boolean runAsync() { - return false; - } + public String getName() { + return "super_cancel"; + } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - MCPlayer m = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); - int offset = 1; - if (args.length == 4) { - m = Static.GetPlayer(args[0].val(), t); - offset = 0; - } - MCItemStack is = null; - if (args[1 - offset] instanceof CNull) { - is = m.getItemInHand(); - } else { - int slot = Static.getInt32(args[1 - offset], t); - is = m.getInventory().getItem(slot); - } - CArray enchantArray = new CArray(t); - if (!( args[2 - offset] instanceof CArray )) { - enchantArray.push(args[2 - offset]); - } else { - enchantArray = (CArray) args[2 - offset]; - } + public Integer[] numArgs() { + return new Integer[]{0}; + } - CArray levelArray = new CArray(t); - if (!( args[3 - offset] instanceof CArray )) { - levelArray.push(args[3 - offset]); - } else { - levelArray = (CArray) args[3 - offset]; - } - for (String key : enchantArray.stringKeySet()) { - MCEnchantment e = StaticLayer.GetEnchantmentByName(Enchantments.ConvertName(enchantArray.get(key, t).val()).toUpperCase()); - if (e == null) { - throw new ConfigRuntimeException(enchantArray.get(key, t).val().toUpperCase() + " is not a valid enchantment type", ExceptionType.EnchantmentException, t); - } - int level = Static.getInt32(new CString(Enchantments.ConvertLevel(levelArray.get(key, t).val()), t), t); + @Override + public String docs() { + return "void {} \"Super Cancels\" an event. This only will work if play-dirty is set to true. If an event is" + + " super cancelled, not only is the cancelled flag set to true, the event stops propagating down, so" + + " no other plugins (as in other server plugins, not just CH scripts) will receive the event at all " + + " (other than monitor level plugins). This is useful for overriding" + + " event handlers for plugins that don't respect the cancelled flag. This function hooks into the play-dirty" + + " framework that injects custom event handlers into bukkit."; + } - is.addUnsafeEnchantment(e, level); - } - return CVoid.VOID; - } - } + @Override + public Class[] thrown() { + return new Class[]{CREBindException.class}; + } - @api(environments={CommandHelperEnvironment.class}) - public static class raw_set_pvanish extends AbstractFunction { + @Override + public boolean isRestricted() { + return true; + } @Override - public String getName() { - return "raw_set_pvanish"; - } + public MSVersion since() { + return MSVersion.V3_3_0; + } @Override - public Integer[] numArgs() { - return new Integer[]{2, 3}; - } + public Boolean runAsync() { + return null; + } @Override - public String docs() { - return "void {[player], isVanished, otherPlayer} Sets the visibility" - + " of the current player (or the one specified) to visible or invisible" - + " (based on the value of isVanished) from the view of the otherPlayer." - + " This is the raw access function, you probably shouldn't use this, as" - + " the CommandHelper vanish api functions will probably be easier to use."; - } + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + BoundEvent.ActiveEvent original = environment.getEnv(GlobalEnv.class).GetEvent(); + if(original == null) { + throw new CREBindException("is_cancelled cannot be called outside an event handler", t); + } + if(original.getUnderlyingEvent() != null && original.getUnderlyingEvent() instanceof Cancellable + && original.getUnderlyingEvent() instanceof org.bukkit.event.Event) { + ((Cancellable) original.getUnderlyingEvent()).setCancelled(true); + BukkitDirtyRegisteredListener.setCancelled((org.bukkit.event.Event) original.getUnderlyingEvent()); + } + environment.getEnv(GlobalEnv.class).GetEvent().setCancelled(true); + return CVoid.VOID; + } + } + + private static String GenerateMooSaying(String text) { + String[] saying = text.split("\r\n|\n|\n\r"); + int longest = 0; + for(String s : saying) { + longest = java.lang.Math.max(longest, s.length()); + } + String divider = ""; + for(int i = 0; i < longest + 4; i++) { + divider += "-"; + } + String[] lines = new String[saying.length]; + for(int i = 0; i < saying.length; i++) { + int spaces = longest - saying[i].length(); + String sSpaces = ""; + for(int j = 0; j < spaces; j++) { + sSpaces += " "; + } + lines[i] = "| " + saying[i] + sSpaces + " |"; + } + return divider + "\n" + + StringUtils.Join(lines, "\n") + "\n" + + divider + "\n"; + } + + @api + @hide("This is an easter egg.") + public static class moo extends DummyFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException}; - } + public Integer[] numArgs() { + return new Integer[]{1}; + } @Override - public boolean isRestricted() { - return true; //lol, very - } + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + return new CString(GenerateMooSaying(args[0].val()) + + " \\ ^__^\n" + + " \\ (oo)\\_______\n" + + " (__)\\ )\\/\\\n" + + " ||----w |\n" + + " || ||\n", t); + } + + } + + @api + @hide("This is an easter egg.") + public static class moo2 extends DummyFunction { + @Override - public Boolean runAsync() { - return false; - } + public Integer[] numArgs() { + return new Integer[]{1}; + } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - MCPlayer me; - boolean isVanished; - MCPlayer other; - if (args.length == 2) { - me = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); - isVanished = Static.getBoolean(args[0]); - other = Static.GetPlayer(args[1], t); - } else { - me = Static.GetPlayer(args[0], t); - isVanished = Static.getBoolean(args[1]); - other = Static.GetPlayer(args[2], t); - } + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + return new CString( + GenerateMooSaying(args[0].val()) + + " ^__^ /\n" + + " _______/(oo) /\n" + + " /\\/( /(__)\n" + + " | w----||\n" + + " || ||\n", t); + } - other.setVanished(isVanished, me); - - return CVoid.VOID; - } - - @Override - public CHVersion since() { - return CHVersion.V3_3_0; - } - } - - @api(environments={CommandHelperEnvironment.class}) - public static class raw_pcan_see extends AbstractFunction { + } + + @api + @hide("This is an easter egg.") + public static class upupdowndownleftrightleftrightbastart extends DummyFunction { @Override - public String getName() { - return "raw_pcan_see"; - } + public Integer[] numArgs() { + return new Integer[]{Integer.MAX_VALUE}; + } @Override - public Integer[] numArgs() { - return new Integer[]{1, 2}; - } + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + return new CString(" .-*)) `*-.\n" + + " /* ((* *'.\n" + + "| *)) * *\\\n" + + "| * ((* * /\n" + + " \\ *)) * .'\n" + + " '-.((*_.-'", t); + } + + } + + @api + @hide("This is an easter egg") + @noboilerplate + public static class norway extends DummyFunction { + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCChatColor red = MCChatColor.RED; + MCChatColor white = MCChatColor.WHITE; + MCChatColor blue = MCChatColor.BLUE; + int multiplier = 2; + char c = '='; + String one = multiply(c, 1 * multiplier); + String two = multiply(c, 2 * multiplier); + String six = multiply(c, 6 * multiplier); + String seven = multiply(c, 7 * multiplier); + String twelve = multiply(c, 12 * multiplier); + String thirteen = multiply(c, 13 * multiplier); + String twentytwo = multiply(c, 22 * multiplier); + PrintStream out = StreamUtils.GetSystemOut(); + String vertical = Static.MCToANSIColors(red + six + white + one + blue + two + white + one + red + twelve); + for(int i = 0; i < 6; ++i) { + out.println(vertical + TermColors.RESET); + } + out.println(Static.MCToANSIColors(white + seven + blue + two + white + thirteen) + TermColors.RESET); + for(int i = 0; i < 2; ++i) { + out.println(Static.MCToANSIColors(blue + twentytwo) + TermColors.RESET); + } + out.println(Static.MCToANSIColors(white + seven + blue + two + white + thirteen) + TermColors.RESET); + for(int i = 0; i < 6; ++i) { + out.println(vertical + TermColors.RESET); + } + + return CVoid.VOID; + } @Override - public String docs() { - return "boolean {[player], other} Returns a boolean stating if the other player can" - + " see this player or not. This is the raw access function, you probably shouldn't use this, as" - + " the CommandHelper vanish api functions will probably be easier to use."; - } + public Integer[] numArgs() { + return new Integer[]{0, 3}; + } + + public static String multiply(char c, int times) { + StringBuilder b = new StringBuilder(); + for(int i = 0; i < times; ++i) { + b.append(c); + } + return b.toString(); + } + + } + + @api + public static class srand extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException}; - } + public Class[] thrown() { + return new Class[]{CRECastException.class}; + } @Override - public boolean isRestricted() { - return true; - } + public boolean isRestricted() { + return false; + } + @Override - public Boolean runAsync() { - return false; - } + public Boolean runAsync() { + return null; + } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - MCPlayer me; - MCPlayer other; - if (args.length == 1) { - me = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); - other = Static.GetPlayer(args[0], t); - } else { - me = Static.GetPlayer(args[0], t); - other = Static.GetPlayer(args[1], t); - } - return CBoolean.get(me.canSee(other)); - } + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + Random r; + try { + r = (Random) ArgumentValidation.getObject(args[0], t, CResource.class).getResource(); + } catch (ClassCastException ex) { + throw new CRECastException("Expected a resource of type " + ResourceManager.ResourceTypes.RANDOM, t, ex); + } + double d = r.nextDouble(); + return new CDouble(d, t); + } @Override - public CHVersion since() { - return CHVersion.V3_3_0; - } - } + public String getName() { + return "srand"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1}; + } + + @Override + public String docs() { + return "double {Resource} Returns a new rand value using the provided a RANDOM Resource." + + " If the seed used to create the resource is the same, each resulting" + + " series of numbers will be the same."; + } + + @Override + public Version since() { + return MSVersion.V3_3_2; + } + + } @api - @hide("This is an easter egg.") - public static class moo extends DummyFunction { + public static class test_composite_function extends CompositeFunction { + + @Override + protected String script() { + // Note that @a is not going to be in scope for the user's scripts. + return "@a = ((@arguments[0] + @arguments[1]) > 0);" + + "return(@a);"; + } + + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class}; + } + + @Override + public boolean isRestricted() { + return false; + } + + @Override + public Boolean runAsync() { + return null; + } + + @Override + public String getName() { + return "test_composite_function"; + } @Override public Integer[] numArgs() { - return new Integer[]{1}; + return new Integer[]{2}; } + @Override + public String docs() { + return "boolean {a, b} This is a test function, which demonstrates to extension authors how to make a composite function." + + " It returns true if a and b added together are greater than 0, false otherwise."; + } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - String saying = args[0].val(); - String divider = ""; - for(int i = 0; i < saying.length() + 4; i++){ - divider += "-"; - } - return new CString(divider + "\n" - + "| " + saying + " |\n" - + divider + "\n" - + " \\ ^__^\n" - + " \\ (oo)\\_______\n" - + " (__)\\ )\\/\\\n" - + " ||----w |\n" - + " || ||\n", t); + public Version since() { + return MSVersion.V3_3_2; } } @api - @hide("This is an easter egg.") - public static class moo2 extends DummyFunction { + public static class x_recompile_includes extends AbstractFunction { + + @Override + public String getName() { + return "x_recompile_includes"; + } @Override public Integer[] numArgs() { @@ -416,22 +376,175 @@ public Integer[] numArgs() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - String saying = args[0].val(); - String divider = ""; - for(int i = 0; i < saying.length() + 4; i++){ - divider += "-"; + public String docs() { + return "int {path} Recompiles specified files already compiled with include()." + + " If there's no compile errors, scripts that then include() these files will use the updated code." + + " Note that this bypasses Static Analysis, even if you have it enabled." + + " The path can be a directory or file. It is executed recursively through all subdirectories." + + " If there's a compile error in any of the files, the function will throw an exception and other" + + " scripts will continue to use the previous version of the code when included. Returns number" + + " of files recompiled."; + } + + @Override + public Class[] thrown() { + return new Class[]{CREIOException.class, CREIncludeException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_2; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + try { + File file = Static.GetFileFromArgument(args[0].val(), env, t, null).getCanonicalFile(); + if(!Static.InCmdLine(env, true) && !Security.CheckSecurity(file)) { + throw new CRESecurityException("The script cannot access " + file + + " due to restrictions imposed by the base-dir setting.", t); + } + IncludeCache cache = env.getEnv(StaticRuntimeEnv.class).getIncludeCache(); + if(file.isDirectory()) { + Map includes = new HashMap<>(); + compileDirectory(includes, file, env, cache, t); + cache.addAll(includes); + return new CInt(includes.size(), t); + } else if(cache.has(file)) { + cache.add(file, compileFile(file, env, t)); + return new CInt(1, t); + } + return new CInt(0, t); + } catch (IOException ex) { + throw new CREIOException(ex.getMessage(), t, ex); + } + } + + private void compileDirectory(Map includes, File dir, Environment env, IncludeCache cache, Target t) { + File[] files = dir.listFiles(); + if(files != null) { + for(File f : files) { + if(f.isDirectory()) { + compileDirectory(includes, f, env, cache, t); + } else if(cache.has(f)) { + includes.put(f, compileFile(f, env, t)); + } + } } - return new CString( - " " + divider + "\n" - + " | " + saying + " |\n" - + " " + divider + "\n" - + " ^__^ /\n" - + " _______/(oo) /\n" - + " /\\/( /(__)\n" - + " | w----||\n" - + " || ||\n", t); } + private ParseTree compileFile(File file, Environment env, Target t) { + try { + String s = new ZipReader(file).getFileContents(); + StaticAnalysis staticAnalysis = new StaticAnalysis(true); + staticAnalysis.setLocalDisabled(true); + return MethodScriptCompiler.compile(MethodScriptCompiler.lex(s, env, file, true), env, env.getEnvClasses(), + staticAnalysis); + } catch (ConfigCompileException ex) { + throw new CREIncludeException("There was a compile error when trying to recompile the script at " + + file + "\n" + ex.getMessage() + " :: " + file.getName() + ":" + ex.getLineNum(), t); + } catch (ConfigCompileGroupException ex) { + StringBuilder b = new StringBuilder(); + b.append("There were compile errors when trying to recompile the script at ").append(file).append("\n"); + for(ConfigCompileException e : ex.getList()) { + b.append(e.getMessage()).append(" :: ").append(e.getFile().getName()).append(":") + .append(e.getLineNum()).append("\n"); + } + throw new CREIncludeException(b.toString(), t); + } catch (IOException ex) { + throw new CREIOException("The script at " + file + " could not be found or read in.", t); + } + } } + + @api + @noboilerplate + public static class x_write extends AbstractFunction { + @Override + public Class[] thrown() { + return new Class[]{CRESecurityException.class, CREIOException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return null; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + if(!Static.InCmdLine(environment, true)) { + throw new CRESecurityException(getName() + " is only available in cmdline mode.", t); + } + File location = Static.GetFileFromArgument(args[0].val(), environment, t, null); + if(location.isDirectory()) { + throw new CREIOException("Path already exists, and is a directory", t); + } + + byte[] content; + if(!(args[1].isInstanceOf(CByteArray.TYPE))) { + content = args[1].val().getBytes(Charset.forName("UTF-8")); + } else { + content = ArgumentValidation.getByteArray(args[1], t).asByteArrayCopy(); + } + FileWriteMode mode = FileWriteMode.SAFE_WRITE; + if(args.length > 2) { + mode = ArgumentValidation.getEnum(args[2], FileWriteMode.class, t); + } + if(mode == FileWriteMode.SAFE_WRITE && location.exists()) { + throw new CREIOException("File already exists, refusing to overwrite.", t); + } + + try { + FileUtils.writeByteArrayToFile(location, content, mode == FileWriteMode.APPEND); + } catch (IOException e) { + throw new CREIOException(e.getMessage(), t, e); + } + return CVoid.VOID; + } + + @Override + public String getName() { + return "x_write"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{2, 3}; + } + + @Override + public String docs() { + return "void {path, content, [mode]} Writes a file to the file system. This method only works from the" + + " cmdline," + + " if not in cmdline, a SecurityException is thrown. Because of this, there is no check against" + + " the base-dir path. ---- The path, if relative, is relative to this script" + + " file. If the path already exists, and is a directory, an IOException is thrown." + + " The content may be a string, in which case it is written out as UTF-8 text. It could also" + + " be a byte_array, in which cases it is written as is. Mode can be one of the following, but" + + " defaults to SAFE_WRITE:\n" + + createEnumTable(FileWriteMode.class); + } + + @Override + public Version since() { + return MSVersion.V3_3_4; + } + + } + } diff --git a/src/main/java/com/laytonsmith/core/functions/Scheduling.java b/src/main/java/com/laytonsmith/core/functions/Scheduling.java index 0f8e71e949..6c9cff182a 100644 --- a/src/main/java/com/laytonsmith/core/functions/Scheduling.java +++ b/src/main/java/com/laytonsmith/core/functions/Scheduling.java @@ -1,5 +1,6 @@ package com.laytonsmith.core.functions; +import com.laytonsmith.PureUtilities.Common.ArrayUtils; import com.laytonsmith.PureUtilities.Common.MutableObject; import com.laytonsmith.PureUtilities.Common.Range; import com.laytonsmith.PureUtilities.Common.StringUtils; @@ -12,12 +13,16 @@ import com.laytonsmith.annotations.hide; import com.laytonsmith.annotations.noboilerplate; import com.laytonsmith.annotations.seealso; -import com.laytonsmith.core.CHVersion; +import com.laytonsmith.core.MSVersion; +import com.laytonsmith.core.ArgumentValidation; import com.laytonsmith.core.LogLevel; import com.laytonsmith.core.Optimizable; import com.laytonsmith.core.ParseTree; import com.laytonsmith.core.Static; import com.laytonsmith.core.compiler.FileOptions; +import com.laytonsmith.core.compiler.signature.FunctionSignatures; +import com.laytonsmith.core.compiler.signature.SignatureBuilder; +import com.laytonsmith.core.constructs.CArray; import com.laytonsmith.core.constructs.CClosure; import com.laytonsmith.core.constructs.CInt; import com.laytonsmith.core.constructs.CNull; @@ -27,13 +32,23 @@ import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.environments.Environment; import com.laytonsmith.core.environments.GlobalEnv; +import com.laytonsmith.core.environments.StaticRuntimeEnv; +import com.laytonsmith.core.exceptions.CRE.CRECastException; +import com.laytonsmith.core.exceptions.CRE.CREInsufficientArgumentsException; +import com.laytonsmith.core.exceptions.CRE.CREInterruptedException; +import com.laytonsmith.core.exceptions.CRE.CREThrowable; +import com.laytonsmith.core.exceptions.CRE.CRENullPointerException; +import com.laytonsmith.core.exceptions.CRE.CREFormatException; +import com.laytonsmith.core.exceptions.CRE.CRERangeException; import com.laytonsmith.core.exceptions.CancelCommandException; import com.laytonsmith.core.exceptions.ConfigCompileException; import com.laytonsmith.core.exceptions.ConfigRuntimeException; import com.laytonsmith.core.exceptions.ProgramFlowManipulationException; -import com.laytonsmith.core.functions.Exceptions.ExceptionType; +import com.laytonsmith.core.natives.interfaces.Callable; +import com.laytonsmith.core.natives.interfaces.Mixed; import com.laytonsmith.core.profiler.ProfilePoint; import com.laytonsmith.core.taskmanager.CoreTaskType; +import com.laytonsmith.core.taskmanager.TaskHandler; import com.laytonsmith.core.taskmanager.TaskManager; import com.laytonsmith.core.taskmanager.TaskState; import com.laytonsmith.core.taskmanager.TimeoutTaskHandler; @@ -50,12 +65,14 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.TimeZone; import java.util.TreeSet; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -93,8 +110,8 @@ public String docs() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{}; } @Override @@ -103,8 +120,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_1_0; + public MSVersion since() { + return MSVersion.V3_1_0; } @Override @@ -113,7 +130,7 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws CancelCommandException, ConfigRuntimeException { return new CInt(System.currentTimeMillis(), t); } } @@ -138,8 +155,8 @@ public String docs() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{}; } @Override @@ -148,8 +165,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_1_0; + public MSVersion since() { + return MSVersion.V3_1_0; } @Override @@ -158,7 +175,7 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws CancelCommandException, ConfigRuntimeException { return new CInt(System.nanoTime(), t); } } @@ -166,6 +183,7 @@ public Construct exec(Target t, Environment env, Construct... args) throws Cance @api @hide("Only meant for cmdline/testing") @noboilerplate + @seealso({Threading.x_interrupt.class}) public static class sleep extends AbstractFunction { @Override @@ -182,12 +200,13 @@ public Integer[] numArgs() { public String docs() { return "void {seconds} Sleeps the script for the specified number of seconds, up to the maximum time limit defined in the preferences file." + " Seconds may be a double value, so 0.5 would be half a second." + + " If the \"interrupt status\" is true then throw InterruptedException" + " PLEASE NOTE: Sleep times are NOT very accurate, and should not be relied on for preciseness."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException}; + public Class[] thrown() { + return new Class[]{CRECastException.class, CREInterruptedException.class}; } @Override @@ -196,17 +215,18 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_1_0; + public MSVersion since() { + return MSVersion.V3_1_0; } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { - Construct x = args[0]; - double time = Static.getNumber(x, t); + public Mixed exec(Target t, Environment env, Mixed... args) throws CancelCommandException, ConfigRuntimeException { + Mixed x = args[0]; + double time = ArgumentValidation.getNumber(x, t); try { - Thread.sleep((int)(time * 1000)); + Thread.sleep((int) (time * 1000)); } catch (InterruptedException ex) { + throw new CREInterruptedException(ex, t); } return CVoid.VOID; } @@ -218,7 +238,7 @@ public Boolean runAsync() { } } - @api(environments={GlobalEnv.class}) + @api(environments = {GlobalEnv.class}) public static class set_interval extends AbstractFunction { @Override @@ -233,17 +253,17 @@ public Integer[] numArgs() { @Override public String docs() { - return "int {timeInMS, [initialDelayInMS,] closure} Sets a task to run every so often. This works similarly to set_timeout," + return "int {int timeInMS, [int initialDelayInMS,] Callable task} Sets a task to run every so often. This works similarly to set_timeout," + " except the task will automatically re-register itself to run again. Note that the resolution" + " of the time is in ms, however, the server will only have a resolution of up to 50 ms, meaning" - + " that a time of 1-50ms is essentially the same as 50ms. The inital delay defaults to the same" + + " that a time of 1-50ms is essentially the same as 50ms. The initial delay defaults to the same" + " thing as timeInMS, that is, there will be a pause between registration and initial firing. However," + " this can be set to 0 (or some other number) to adjust how long of a delay there is before it begins."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException}; + public Class[] thrown() { + return new Class[]{CRECastException.class, CRERangeException.class}; } @Override @@ -257,64 +277,76 @@ public Boolean runAsync() { } @Override - public Construct exec(final Target t, final Environment environment, Construct... args) throws ConfigRuntimeException { - long time = Static.getInt(args[0], t); + public Mixed exec(final Target t, final Environment environment, Mixed... args) throws ConfigRuntimeException { + long time = ArgumentValidation.getInt(args[0], t); int offset = 0; long delay = time; - if (args.length == 3) { + if(args.length == 3) { offset = 1; - delay = Static.getInt(args[1], t); + delay = ArgumentValidation.getInt(args[1], t); + if(delay < 0) { + throw new CRERangeException("Negative initial delay", t); + } + } + if(time < 0) { + throw new CRERangeException("Negative repeating delay", t); } - if (!(args[1 + offset] instanceof CClosure)) { - throw new ConfigRuntimeException(getName() + " expects a closure to be sent as the second argument", ExceptionType.CastException, t); + if(!(args[1 + offset].isInstanceOf(Callable.TYPE))) { + throw new CRECastException(getName() + " expects a Callable to be sent as the second argument", t); } - final CClosure c = (CClosure) args[1 + offset]; + final Callable c = (Callable) args[1 + offset]; final AtomicInteger ret = new AtomicInteger(-1); - ret.set(StaticLayer.SetFutureRepeater(environment.getEnv(GlobalEnv.class).GetDaemonManager(), time, delay, new Runnable() { - @Override - public void run() { - c.getEnv().getEnv(GlobalEnv.class).SetCustom("timeout-id", ret.get()); + ret.set(StaticLayer.SetFutureRepeater(environment.getEnv(StaticRuntimeEnv.class).GetDaemonManager(), time, delay, () -> { + c.getEnv().getEnv(GlobalEnv.class).SetCustom("timeout-id", ret.get()); + try { + ProfilePoint p = environment.getEnv(StaticRuntimeEnv.class).GetProfiler().start("Executing timeout" + + " with id " + ret.get() + " (defined at " + t.toString() + ")", LogLevel.ERROR); try { - ProfilePoint p = environment.getEnv(GlobalEnv.class).GetProfiler().start("Executing timeout with id " + ret.get() + " (defined at " + t.toString() + ")", LogLevel.ERROR); - try { - c.execute(); - } finally { - p.stop(); - } - } catch (ConfigRuntimeException e) { - ConfigRuntimeException.HandleUncaughtException(e, environment); - } catch (CancelCommandException e) { - //Ok - } catch (ProgramFlowManipulationException e) { - ConfigRuntimeException.DoWarning("Using a program flow manipulation construct improperly! " + e.getClass().getSimpleName()); + c.executeCallable(environment, t); + } finally { + p.stop(); } + } catch (ConfigRuntimeException e) { + ConfigRuntimeException.HandleUncaughtException(e, environment); + } catch (CancelCommandException e) { + //Ok + } catch (ProgramFlowManipulationException e) { + ConfigRuntimeException.DoWarning("Using a program flow manipulation construct improperly! " + e.getClass().getSimpleName()); } })); return new CInt(ret.get(), t); } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Basic usage", "set_interval(1000, closure(){\n" - + "\tmsg('Hello World!');\n" - + "});", ""), + + "\tmsg('Hello World!');\n" + + "});", ""), new ExampleScript("Usage with initial delay", "set_interval(1000, 5000, closure(){\n" - + "\tmsg('Hello World!');\n" - + "});", "") + + "\tmsg('Hello World!');\n" + + "});", "") }; } - + @Override + public FunctionSignatures getSignatures() { + return new SignatureBuilder(CInt.TYPE) + .param(CInt.TYPE, "timeInMS", "The delay between runs, in milliseconds.") + .param(CInt.TYPE, "initialDelayInMS", "The delay before the first execution, in milliseconds." + + " By default, the same as timeInMS.", true) + .param(Callable.TYPE, "callable", "The task to execute.") + .build(); + } } - @api(environments={GlobalEnv.class}) + @api(environments = {GlobalEnv.class}) public static class set_timeout extends AbstractFunction { @Override @@ -336,8 +368,8 @@ public String docs() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException}; + public Class[] thrown() { + return new Class[]{CRECastException.class, CRERangeException.class}; } @Override @@ -351,73 +383,81 @@ public Boolean runAsync() { } @Override - public Construct exec(final Target t, final Environment environment, Construct... args) throws ConfigRuntimeException { - final TaskManager taskManager = environment.getEnv(GlobalEnv.class).GetTaskManager(); - long time = Static.getInt(args[0], t); - if (!(args[1] instanceof CClosure)) { - throw new ConfigRuntimeException(getName() + " expects a closure to be sent as the second argument", ExceptionType.CastException, t); + public Mixed exec(final Target t, final Environment environment, Mixed... args) throws ConfigRuntimeException { + final TaskManager taskManager = environment.getEnv(StaticRuntimeEnv.class).GetTaskManager(); + long time = ArgumentValidation.getInt(args[0], t); + if(time < 0) { + throw new CRERangeException("Negative delay", t); + } + if(!(args[1].isInstanceOf(Callable.TYPE))) { + throw new CRECastException(getName() + " expects a Callable to be sent as the second argument", t); } - final CClosure c = (CClosure) args[1]; + final Callable c = (Callable) args[1]; final AtomicInteger ret = new AtomicInteger(-1); - final AtomicBoolean isRunning = new AtomicBoolean(false); - ret.set(StaticLayer.SetFutureRunnable(environment.getEnv(GlobalEnv.class).GetDaemonManager(), time, new Runnable() { - @Override - public void run() { - isRunning.set(true); - c.getEnv().getEnv(GlobalEnv.class).SetCustom("timeout-id", ret.get()); - taskManager.getTask(CoreTaskType.TIMEOUT, ret.get()).changeState(TaskState.RUNNING); + ret.set(StaticLayer.SetFutureRunnable( + environment.getEnv(StaticRuntimeEnv.class).GetDaemonManager(), time, () -> { + c.getEnv().getEnv(GlobalEnv.class).SetCustom("timeout-id", ret.get()); + TaskHandler task = taskManager.getTask(CoreTaskType.TIMEOUT, ret.get()); + if(task == null) { + // Task probably got called from Timer thread before it could be unregistered. + // Once it got to the main thread here, it was no longer in the TaskManager. + // Don't run this since it was cleared. + return; + } + task.changeState(TaskState.RUNNING); + try { + ProfilePoint p = c.getEnv().getEnv(StaticRuntimeEnv.class).GetProfiler().start("Executing timeout" + + " with id " + ret.get() + " (defined at " + t.toString() + ")", LogLevel.ERROR); try { - ProfilePoint p = environment.getEnv(GlobalEnv.class).GetProfiler().start("Executing timeout with id " + ret.get() + " (defined at " + t.toString() + ")", LogLevel.ERROR); - try { - c.execute(); - } finally { - p.stop(); - } - } catch (ConfigRuntimeException e) { - ConfigRuntimeException.HandleUncaughtException(e, environment); - } catch (CancelCommandException e) { - //Ok - } catch (ProgramFlowManipulationException e) { - ConfigRuntimeException.DoWarning("Using a program flow manipulation construct improperly! " + e.getClass().getSimpleName()); + c.executeCallable(environment, t); } finally { - taskManager.getTask(CoreTaskType.TIMEOUT, ret.get()).changeState(TaskState.FINISHED); - environment.getEnv(GlobalEnv.class).SetInterrupt(false); + p.stop(); } - } - })); - taskManager.addTask(new TimeoutTaskHandler(ret.get(), t, new Runnable() { - - @Override - public void run() { - if(isRunning.get()){ - new clear_task().exec(t, environment, new CInt(ret.get(), t)); - environment.getEnv(GlobalEnv.class).SetInterrupt(true); - taskManager.getTask(CoreTaskType.TIMEOUT, ret.get()).changeState(TaskState.KILLED); + } catch (ConfigRuntimeException e) { + ConfigRuntimeException.HandleUncaughtException(e, c.getEnv()); + } catch (CancelCommandException e) { + //Ok + } catch (ProgramFlowManipulationException e) { + ConfigRuntimeException.DoWarning("Using a program flow manipulation construct improperly! " + e.getClass().getSimpleName()); + } finally { + // If the task was somehow killed in the closure, it'll already be finished + if(!task.getState().isFinalized()) { + task.changeState(TaskState.FINISHED); } } })); + taskManager.addTask(new TimeoutTaskHandler(ret.get(), t, () -> { + StaticLayer.ClearFutureRunnable(ret.get()); + taskManager.getTask(CoreTaskType.TIMEOUT, ret.get()).changeState(TaskState.KILLED); + })); taskManager.getTask(CoreTaskType.TIMEOUT, ret.get()).changeState(TaskState.IDLE); return new CInt(ret.get(), t); } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Basic usage", "set_timeout(10000, closure(){\n" - + "\tmsg('Hello World!');\n" - + "});", "") + + "\tmsg('Hello World!');\n" + + "});", "") }; } - + @Override + public FunctionSignatures getSignatures() { + return new SignatureBuilder(CInt.TYPE) + .param(CInt.TYPE, "timeInMS", "The delay before the task runs, in milliseconds.") + .param(Callable.TYPE, "callable", "The task to execute.") + .build(); + } } - @api(environments={GlobalEnv.class}) + @api(environments = {GlobalEnv.class}) public static class clear_task extends AbstractFunction { @Override @@ -441,8 +481,9 @@ public String docs() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException, ExceptionType.InsufficientArgumentsException}; + public Class[] thrown() { + return new Class[]{CRECastException.class, CREInsufficientArgumentsException.class, + CRENullPointerException.class}; } @Override @@ -456,41 +497,60 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - if (args.length == 0 && environment.getEnv(GlobalEnv.class).GetCustom("timeout-id") != null) { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + if(args.length == 0 && environment.getEnv(GlobalEnv.class).GetCustom("timeout-id") != null) { StaticLayer.ClearFutureRunnable((Integer) environment.getEnv(GlobalEnv.class).GetCustom("timeout-id")); - } else if (args.length == 1) { - StaticLayer.ClearFutureRunnable(Static.getInt32(args[0], t)); + } else if(args.length == 1) { + if(args[0] instanceof CNull) { + throw new CRENullPointerException("Null value sent to " + getName() + + "(). Did you mean " + getName() + "(0)?", t); + } + int id = ArgumentValidation.getInt32(args[0], t); + TaskManager taskManager = environment.getEnv(StaticRuntimeEnv.class).GetTaskManager(); + TaskHandler task = taskManager.getTask(CoreTaskType.TIMEOUT, id); + if(task == null) { // may not be a timeout + StaticLayer.ClearFutureRunnable(id); + } else { + task.kill(); + } } else { - throw new ConfigRuntimeException("No id was passed to clear_task, and it's not running inside a task either.", ExceptionType.InsufficientArgumentsException, t); + throw new CREInsufficientArgumentsException("No id was passed to clear_task, and it's not running inside a task either.", t); } return CVoid.VOID; } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Use from within an interval", "set_interval(1000, closure(){\n" - + "\tif(rand(0, 10) == 9){\n" - + "\t\tclear_task();\n" - + "\t}\n" - + "\tmsg('Hello World!');\n" - + "});", ""), + + "\tif(rand(0, 10) == 9){\n" + + "\t\tclear_task();\n" + + "\t}\n" + + "\tmsg('Hello World!');\n" + + "});", ""), new ExampleScript("Using the id returned from set_timeout", "@id = set_timeout(5000, closure(){\n" - + "\tmsg('Hello World!');\n" - + "});\n" - + "clear_task(@id);", "") + + "\tmsg('Hello World!');\n" + + "});\n" + + "clear_task(@id);", "") }; } + @Override + public FunctionSignatures getSignatures() { + return new SignatureBuilder(CVoid.TYPE) + .param(CInt.TYPE, "id", "The id of the task. Optional if called from within a task.", true) + .build(); + } + } @api + @seealso(Meta.get_locales.class) public static class simple_date extends AbstractFunction { @Override @@ -500,40 +560,41 @@ public String getName() { @Override public Integer[] numArgs() { - return new Integer[]{1, 2, 3}; + return new Integer[]{1, 2, 3, 4}; } @Override public String docs() { - Map map = new HashMap(); - map.put("timezoneValues", new DocGenTemplates.Generator() { - - @Override - public String generate(String... args) { - String [] timezones = new String[0]; - try{ - timezones = TimeZone.getAvailableIDs(); - } catch(NullPointerException e){ - //This is due to a JDK bug. As you can see, the code above - //should never NPE due to our mistake, so it would only occur - //during an internal error. The solution that worked for me is here: - //https://bugs.launchpad.net/ubuntu/+source/tzdata/+bug/1053160 - //however, this appears to be an issue in Open JDK, so performance on - //other systems may vary. We will handle this error by reporting that - //list could not be retrieved, using the Join method's empty parameter. - } - //Let's sort the timezones - List tz = new ArrayList(Arrays.asList(timezones)); - Collections.sort(tz); - return StringUtils.Join(tz, ", ", " or ", " or ", "Couldn't retrieve the list of timezones!"); + Map map = new HashMap<>(); + map.put("timezoneValues", (DocGenTemplates.Generator) (String... args) -> { + String[] timezones = ArrayUtils.EMPTY_STRING_ARRAY; + try { + timezones = TimeZone.getAvailableIDs(); + } catch (NullPointerException e) { + //This is due to a JDK bug. As you can see, the code above + //should never NPE due to our mistake, so it would only occur + //during an internal error. The solution that worked for me is here: + //https://bugs.launchpad.net/ubuntu/+source/tzdata/+bug/1053160 + //however, this appears to be an issue in Open JDK, so performance on + //other systems may vary. We will handle this error by reporting that + //list could not be retrieved, using the Join method's empty parameter. } + //Let's sort the timezones + List tz = new ArrayList<>(Arrays.asList(timezones)); + Collections.sort(tz); + return StringUtils.Join(tz, ", ", " or ", " or ", "Couldn't retrieve the list of timezones!"); }); - return getBundledDocs(map); + try { + return getBundledDocs(map); + } catch (DocGenTemplates.Generator.GenerateException ex) { + Logger.getLogger(Scheduling.class.getName()).log(Level.SEVERE, null, ex); + return getBundledDocs(); + } } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException, ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CRECastException.class, CREFormatException.class}; } @Override @@ -542,8 +603,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } @Override @@ -552,20 +613,33 @@ public Boolean runAsync() { } @Override - public CString exec(Target t, Environment env, Construct... args) { + public CString exec(Target t, Environment env, Mixed... args) { Date now = new Date(); - if (args.length >= 2 && !(args[1] instanceof CNull)) { - now = new Date(Static.getInt(args[1], t)); + if(args.length >= 2 && !(args[1] instanceof CNull)) { + now = new Date(ArgumentValidation.getInt(args[1], t)); } TimeZone timezone = TimeZone.getDefault(); - if(args.length >= 3){ + if(args.length >= 3 && Construct.nval(args[2]) != null) { timezone = TimeZone.getTimeZone(args[2].val()); } + Locale locale = Locale.getDefault(); + if(args.length >= 4) { + String countryCode = Construct.nval(args[3]); + if(countryCode == null) { + locale = Locale.getDefault(); + } else { + locale = Static.GetLocale(countryCode); + } + if(locale == null) { + throw new CREFormatException("The given locale was not found on your system: " + + countryCode, t); + } + } SimpleDateFormat dateFormat; - try{ - dateFormat = new SimpleDateFormat(args[0].toString()); - } catch(IllegalArgumentException ex){ - throw new Exceptions.FormatException(ex.getMessage(), t); + try { + dateFormat = new SimpleDateFormat(args[0].toString(), locale); + } catch (IllegalArgumentException ex) { + throw new CREFormatException(ex.getMessage(), t); } dateFormat.setTimeZone(timezone); return new CString(dateFormat.format(now), t); @@ -574,19 +648,19 @@ public CString exec(Target t, Environment env, Construct... args) { @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ - /* 1 */ new ExampleScript("Basic usage", "simple_date('h:mm a')", ":11:36 AM"), - /* 2 */ new ExampleScript("Usage with quoted letters", "simple_date('yyyy.MM.dd G \\'at\\' HH:mm:ss z')", ":2013.06.13 AD at 11:36:46 CDT"), - /* 3 */ new ExampleScript("Adding a single quote", "simple_date('EEE, MMM d, \\'\\'yy')", ":Wed, Jun 5, '13"), - /* 4 */ new ExampleScript("Specifying alternate time", "simple_date('EEE, MMM d, \\'\\'yy', 0)"), - /* 5 */ new ExampleScript("With timezone", "simple_date('hh \\'o\\'\\'clock\\' a, zzzz')", ":11 o'clock AM, Central Daylight Time"), - /* 6 */ new ExampleScript("With timezone", "simple_date('hh \\'o\\'\\'clock\\' a, zzzz')", ":11 o'clock AM, Central Daylight Time"), - /* 7 */ new ExampleScript("With simple timezone", "simple_date('K:mm a, z')", ":11:42 AM, CDT"), - /* 8 */ new ExampleScript("With alternate timezone", "simple_date('K:mm a, z', time(), 'GMT')", ":4:42 PM, GMT"), - /* 9 */ new ExampleScript("With 5 digit year", "simple_date('yyyyy.MMMMM.dd GGG hh:mm aaa')", ":02013.June.05 AD 11:42 AM"), - /* 10 */ new ExampleScript("Long format", "simple_date('EEE, d MMM yyyy HH:mm:ss Z')", ":Wed, 5 Jun 2013 11:42:56 -0500"), - /* 11 */ new ExampleScript("Computer readable format", "simple_date('yyMMddHHmmssZ')", ":130605114256-0500"), - /* 12 */ new ExampleScript("With milliseconds", "simple_date('yyyy-MM-dd\\'T\\'HH:mm:ss.SSSZ')", ":2013-06-05T11:42:56.799-0500"), - }; + /* 1 */new ExampleScript("Basic usage", "simple_date('h:mm a')", ":11:36 AM"), + /* 2 */ new ExampleScript("Usage with quoted letters", "simple_date('yyyy.MM.dd G \\'at\\' HH:mm:ss z')", ":2013.06.13 AD at 11:36:46 CDT"), + /* 3 */ new ExampleScript("Adding a single quote", "simple_date('EEE, MMM d, \\'\\'yy')", ":Wed, Jun 5, '13"), + /* 4 */ new ExampleScript("Specifying alternate time", "simple_date('EEE, MMM d, \\'\\'yy', 0)"), + /* 5 */ new ExampleScript("With timezone", "simple_date('hh \\'o\\'\\'clock\\' a, zzzz')", ":11 o'clock AM, Central Daylight Time"), + /* 6 */ new ExampleScript("With timezone", "simple_date('hh \\'o\\'\\'clock\\' a, zzzz')", ":11 o'clock AM, Central Daylight Time"), + /* 7 */ new ExampleScript("With simple timezone", "simple_date('K:mm a, z')", ":11:42 AM, CDT"), + /* 8 */ new ExampleScript("With alternate timezone", "simple_date('K:mm a, z', time(), 'GMT')", ":4:42 PM, GMT"), + /* 9 */ new ExampleScript("With 5 digit year", "simple_date('yyyyy.MMMMM.dd GGG hh:mm aaa')", ":02013.June.05 AD 11:42 AM"), + /* 10 */ new ExampleScript("Long format", "simple_date('EEE, d MMM yyyy HH:mm:ss Z')", ":Wed, 5 Jun 2013 11:42:56 -0500"), + /* 10 */ new ExampleScript("Long format with alternate locale", "simple_date('EEE, d MMM yyyy HH:mm:ss Z', 1444418254496, 'CET', 'no_NO')"), + /* 11 */ new ExampleScript("Computer readable format", "simple_date('yyMMddHHmmssZ')", ":130605114256-0500"), + /* 12 */ new ExampleScript("With milliseconds", "simple_date('yyyy-MM-dd\\'T\\'HH:mm:ss.SSSZ')", ":2013-06-05T11:42:56.799-0500")}; } } @@ -595,8 +669,8 @@ public ExampleScript[] examples() throws ConfigCompileException { public static class parse_date extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CREFormatException.class}; } @Override @@ -610,14 +684,27 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { SimpleDateFormat dateFormat; - try{ - dateFormat = new SimpleDateFormat(args[0].toString()); + Locale locale = Locale.getDefault(); + if(args.length >= 3) { + String countryCode = Construct.nval(args[2]); + if(countryCode == null) { + locale = Locale.getDefault(); + } else { + locale = Static.GetLocale(countryCode); + } + if(locale == null) { + throw new CREFormatException("The given locale was not found on your system: " + + countryCode, t); + } + } + try { + dateFormat = new SimpleDateFormat(args[0].toString(), locale); Date d = dateFormat.parse(args[1].val()); return new CInt(d.getTime(), t); - } catch(IllegalArgumentException | ParseException ex){ - throw new Exceptions.FormatException(ex.getMessage(), t); + } catch (IllegalArgumentException | ParseException ex) { + throw new CREFormatException(ex.getMessage(), t); } } @@ -628,12 +715,12 @@ public String getName() { @Override public Integer[] numArgs() { - return new Integer[]{2}; + return new Integer[]{2, 3}; } @Override public String docs() { - return "int {dateFormat, dateString} Parses a date string, and returns an integer timestamp representing that time. This essentially" + return "int {dateFormat, dateString, [locale]} Parses a date string, and returns an integer timestamp representing that time. This essentially" + " works in reverse of {{function|simple_date}}. The dateFormat string is the same as simple_date, see the documentation for" + " that function to see full details on that. The dateString is the actual date to be parsed. The dateFormat should be the" + " equivalent format that was used to generate the dateString. In general, this function is fairly lenient, and will still" @@ -645,7 +732,7 @@ public String docs() { @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } @Override @@ -653,30 +740,95 @@ public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Simple example", "parse_date('yyMMddHHmmssZ', '130605114256-0500')"), new ExampleScript("Using the results of simple_date", "@format = 'EEE, d MMM yyyy HH:mm:ss Z';\n" - + "msg(parse_date(@format, simple_date(@format, 1)));") + + "msg(parse_date(@format, simple_date(@format, 1)));") }; } } + @api + public static class get_system_timezones extends AbstractFunction { + + @Override + public Class[] thrown() { + return new Class[]{}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return null; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + String[] timezones = ArrayUtils.EMPTY_STRING_ARRAY; + try { + timezones = TimeZone.getAvailableIDs(); + } catch (NullPointerException e) { + //This is due to a JDK bug. As you can see, the code above + //should never NPE due to our mistake, so it would only occur + //during an internal error. The solution that worked for me is here: + //https://bugs.launchpad.net/ubuntu/+source/tzdata/+bug/1053160 + //however, this appears to be an issue in Open JDK, so performance on + //other systems may vary. We will handle this error by reporting that + //list could not be retrieved, using the Join method's empty parameter. + } + //Let's sort the timezones + List tz = new ArrayList<>(Arrays.asList(timezones)); + Collections.sort(tz); + CArray ret = new CArray(t); + for(String s : tz) { + ret.push(new CString(s, t), t); + } + return ret; + } + + @Override + public String getName() { + return "get_system_timezones"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{0}; + } + + @Override + public String docs() { + return "array<string> {} Returns a list of time zones registered on this system."; + } + + @Override + public Version since() { + return MSVersion.V3_3_2; + } + + } + @api public static class set_cron extends AbstractFunction implements Optimizable { private static Thread cronThread = null; - private static final Object cronThreadLock = new Object(); - private static final Map cronJobs = new HashMap(); - private static final AtomicInteger jobIDs = new AtomicInteger(1); + private static final Object CRON_THREAD_LOCK = new Object(); + private static final Map CRON_JOBS = new HashMap(); + private static final AtomicInteger JOB_IDS = new AtomicInteger(1); /** - * Stops a job from running again, and returns true if the value was - * actually removed. False is returned otherwise. + * Stops a job from running again, and returns true if the value was actually removed. False is returned + * otherwise. + * * @return * @param jobID The job ID */ - public static boolean stopJob(int jobID){ - synchronized(cronJobs){ - if(cronJobs.containsKey(jobID)){ - cronJobs.remove(jobID); + public static boolean stopJob(int jobID) { + synchronized(CRON_JOBS) { + if(CRON_JOBS.containsKey(jobID)) { + CRON_JOBS.remove(jobID); return true; } else { return false; @@ -685,8 +837,8 @@ public static boolean stopJob(int jobID){ } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException, ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CRECastException.class, CREFormatException.class}; } @Override @@ -700,93 +852,80 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { //First things first, check the format of the arguments. - if(!(args[0] instanceof CString)){ - throw new Exceptions.CastException("Expected string for argument 1 in " + getName(), t); + if(!(args[0].isInstanceOf(CString.TYPE))) { + throw new CRECastException("Expected string for argument 1 in " + getName(), t); } - if(!(args[1] instanceof CClosure)){ - throw new Exceptions.CastException("Expected closure for argument 2 in " + getName(), t); + if(!(args[1].isInstanceOf(CClosure.TYPE))) { + throw new CRECastException("Expected closure for argument 2 in " + getName(), t); } CronFormat format = validateFormat(args[0].val(), t); - format.job = ((CClosure)args[1]); + format.job = ((CClosure) args[1]); //At this point, the format is complete. We need to start up the cron thread if it's not running, and //then register this job, as well as inform clear_task of this id. - synchronized(cronThreadLock){ - if(cronThread == null){ - final DaemonManager dm = environment.getEnv(GlobalEnv.class).GetDaemonManager(); + synchronized(CRON_THREAD_LOCK) { + if(cronThread == null) { + final DaemonManager dm = environment.getEnv(StaticRuntimeEnv.class).GetDaemonManager(); final MutableObject stopCron = new MutableObject<>(false); - StaticLayer.GetConvertor().addShutdownHook(new Runnable() { - - @Override - public void run() { - cronThread = null; - stopCron.setObject(true); - synchronized(cronJobs){ - cronJobs.clear(); - } - synchronized(cronThreadLock){ - cronThreadLock.notifyAll(); - } + StaticLayer.GetConvertor().addShutdownHook(() -> { + cronThread = null; + stopCron.setObject(true); + synchronized(CRON_JOBS) { + CRON_JOBS.clear(); + } + synchronized(CRON_THREAD_LOCK) { + CRON_THREAD_LOCK.notifyAll(); } }); - cronThread = new Thread(new Runnable() { - - @Override - public void run() { - long lastMinute = 0; - while(!stopCron.getObject()){ - //We want to check to make sure that we only run once per minute, even though the - //checks happen every second. This ensures that we have a fast enough sampling - //period, while ensuring that we don't repeat tasks within the same minute. - if((System.currentTimeMillis() / 1000 / 60) > lastMinute){ - //Set the lastMinute value to now - lastMinute = System.currentTimeMillis() / 1000 / 60; - //Activate - synchronized(cronJobs){ - Calendar c = Calendar.getInstance(); - for(final CronFormat f : cronJobs.values()){ - //Check to see if it is currently time to run each job - if(f.min.contains(c.get(Calendar.MINUTE)) - && f.hour.contains(c.get(Calendar.HOUR_OF_DAY)) - && f.day.contains(c.get(Calendar.DAY_OF_MONTH)) - && f.month.contains(c.get(Calendar.MONTH) + 1) - && f.dayOfWeek.contains(c.get(Calendar.DAY_OF_WEEK) - 1) - ){ - //All the fields match, so let's trigger this job - StaticLayer.GetConvertor().runOnMainThreadLater(dm, new Runnable() { - - @Override - public void run() { - try { - f.job.execute(); - } catch(ConfigRuntimeException ex){ - ConfigRuntimeException.HandleUncaughtException(ex, f.job.getEnv()); - } - } - }); - } + cronThread = new Thread(() -> { + long lastMinute = 0; + while(!stopCron.getObject()) { + //We want to check to make sure that we only run once per minute, even though the + //checks happen every second. This ensures that we have a fast enough sampling + //period, while ensuring that we don't repeat tasks within the same minute. + if((System.currentTimeMillis() / 1000 / 60) > lastMinute) { + //Set the lastMinute value to now + lastMinute = System.currentTimeMillis() / 1000 / 60; + //Activate + synchronized(CRON_JOBS) { + Calendar c = Calendar.getInstance(); + for(final CronFormat f : CRON_JOBS.values()) { + //Check to see if it is currently time to run each job + if(f.min.contains(c.get(Calendar.MINUTE)) + && f.hour.contains(c.get(Calendar.HOUR_OF_DAY)) + && f.day.contains(c.get(Calendar.DAY_OF_MONTH)) + && f.month.contains(c.get(Calendar.MONTH) + 1) + && f.dayOfWeek.contains(c.get(Calendar.DAY_OF_WEEK) - 1)) { + //All the fields match, so let's trigger this job + StaticLayer.GetConvertor().runOnMainThreadLater(dm, () -> { + try { + f.job.executeCallable(); + } catch (ConfigRuntimeException ex) { + ConfigRuntimeException.HandleUncaughtException(ex, f.job.getEnv()); + } + }); } } - } //else continue, we'll wait another second. - synchronized(cronThreadLock){ - try { - cronThreadLock.wait(1000); - } catch (InterruptedException ex) { - //Continue - } + } + } //else continue, we'll wait another second. + synchronized(CRON_THREAD_LOCK) { + try { + CRON_THREAD_LOCK.wait(1000); + } catch (InterruptedException ex) { + //Continue } } - dm.deactivateThread(cronThread); } + dm.deactivateThread(cronThread); }, Implementation.GetServerType().getBranding() + "-CronDaemon"); dm.activateThread(cronThread); cronThread.start(); } } - int jobID = jobIDs.getAndIncrement(); - synchronized(cronJobs){ - cronJobs.put(jobID, format); + int jobID = JOB_IDS.getAndIncrement(); + synchronized(CRON_JOBS) { + CRON_JOBS.put(jobID, format); format.job.getEnv().getEnv(GlobalEnv.class).SetCustom("cron-task-id", jobID); } return new CInt(jobID, t); @@ -845,39 +984,39 @@ public void run() { HOURS.put("noon", 12); } - private CronFormat validateFormat(String format, Target t){ + private CronFormat validateFormat(String format, Target t) { //Now we need to look at the format of the cron task, and convert it to a standardized format. //Our goal here is to remove all ranges, predefined names, including @hourly and January. format = format.trim(); //Changes tabs to spaces - format = format.replace("\t", " "); + format = format.replace('\t', ' '); //Change multiple spaces to one space format = format.replaceAll("( )+", " "); //Lowercase everything format = format.toLowerCase(); - if("@yearly".equals(format) || "@annually".equals(format)){ + if("@yearly".equals(format) || "@annually".equals(format)) { format = "0 0 1 1 *"; } - if("@monthly".equals(format)){ + if("@monthly".equals(format)) { format = "0 0 1 * *"; } - if("@weekly".equals(format)){ + if("@weekly".equals(format)) { format = "0 0 * * 0"; } - if("@daily".equals(format)){ + if("@daily".equals(format)) { format = "0 0 * * *"; } - if("@hourly".equals(format)){ + if("@hourly".equals(format)) { format = "0 * * * *"; } //Check for invalid characters - if(format.matches("[^a-z0-9\\*\\-,@/]")){ - throw new Exceptions.FormatException("Invalid characters found in format for " + getName() + ": \"" + format + "\". Check your format and try again.", t); + if(format.matches("[^a-z0-9\\*\\-,@/]")) { + throw new CREFormatException("Invalid characters found in format for " + getName() + ": \"" + format + "\". Check your format and try again.", t); } //Now split into the segments. String[] sformat = format.split(" "); - if(sformat.length != 5){ - throw new Exceptions.FormatException("Expected 5 segments in " + getName() + ", but " + StringUtils.PluralTemplateHelper(sformat.length, "%d was", "%d were") + " found.", t); + if(sformat.length != 5) { + throw new CREFormatException("Expected 5 segments in " + getName() + ", but " + StringUtils.PluralTemplateHelper(sformat.length, "%d was", "%d were") + " found.", t); } String min = sformat[0]; String hour = sformat[1]; @@ -886,58 +1025,58 @@ private CronFormat validateFormat(String format, Target t){ String dayOfWeek = sformat[4]; //Now replace the special shortcut names - for(String key : MONTHS.keySet()){ + for(String key : MONTHS.keySet()) { month = month.replace(key, Integer.toString(MONTHS.get(key))); } - for(String key : DAYS.keySet()){ + for(String key : DAYS.keySet()) { dayOfWeek = dayOfWeek.replace(key, Integer.toString(DAYS.get(key))); } - for(String key : HOURS.keySet()){ + for(String key : HOURS.keySet()) { hour = hour.replace(key, Integer.toString(HOURS.get(key))); } //Split on commas - List minList = new ArrayList(Arrays.asList(min.split(","))); - List hourList = new ArrayList(Arrays.asList(hour.split(","))); - List dayList = new ArrayList(Arrays.asList(day.split(","))); - List monthList = new ArrayList(Arrays.asList(month.split(","))); - List dayOfWeekList = new ArrayList(Arrays.asList(dayOfWeek.split(","))); + List minList = new ArrayList<>(Arrays.asList(min.split(","))); + List hourList = new ArrayList<>(Arrays.asList(hour.split(","))); + List dayList = new ArrayList<>(Arrays.asList(day.split(","))); + List monthList = new ArrayList<>(Arrays.asList(month.split(","))); + List dayOfWeekList = new ArrayList<>(Arrays.asList(dayOfWeek.split(","))); List> segments = Arrays.asList(minList, hourList, dayList, monthList, dayOfWeekList); //Now go through each and pull out any ranges. At this point, everything //is numbers or * - for(int i = 0; i < segments.size(); i++){ + for(int i = 0; i < segments.size(); i++) { List segment = segments.get(i); Iterator it = segment.iterator(); - List addAll = new ArrayList(); + List addAll = new ArrayList<>(); Range range = RANGES.get(i); - while(it.hasNext()){ + while(it.hasNext()) { String part = it.next(); Matcher rangeMatcher = RANGE.matcher(part); - if(rangeMatcher.find()){ + if(rangeMatcher.find()) { it.remove(); Integer minRange = Integer.parseInt(rangeMatcher.group(1)); Integer maxRange = Integer.parseInt(rangeMatcher.group(2)); Range r = new Range(minRange, maxRange); - if(!r.isAscending()){ - throw new Exceptions.FormatException("Ranges must be min to max, and not the same value in format for " + getName(), t); + if(!r.isAscending()) { + throw new CREFormatException("Ranges must be min to max, and not the same value in format for " + getName(), t); } List rr = r.getRange(); - for(int j = 0; j < rr.size(); j++){ + for(int j = 0; j < rr.size(); j++) { addAll.add(Integer.toString(rr.get(j))); } continue; } Matcher everyMatcher = EVERY.matcher(part); - if(everyMatcher.find()){ + if(everyMatcher.find()) { it.remove(); Integer every = Integer.parseInt(everyMatcher.group(1)); - for(int j = range.getMin(); j <= range.getMax(); j+=every){ + for(int j = range.getMin(); j <= range.getMax(); j += every) { addAll.add(Integer.toString(j)); } } - if("*".equals(part)){ + if("*".equals(part)) { it.remove(); - for(int j = range.getMin(); j <= range.getMax(); j++){ + for(int j = range.getMin(); j <= range.getMax(); j++) { addAll.add(Integer.toString(j)); } } @@ -947,28 +1086,28 @@ private CronFormat validateFormat(String format, Target t){ } //Everything is ints now, so parse it into a CronFormat object. CronFormat f = new CronFormat(); - for(int i = 0; i < 5; i++){ - List list = new ArrayList(); + for(int i = 0; i < 5; i++) { + List list = new ArrayList<>(); List segment = segments.get(i); Range range = RANGES.get(i); - for(String s : segment){ - try{ + for(String s : segment) { + try { list.add(Integer.parseInt(s)); - } catch (NumberFormatException ex){ + } catch (NumberFormatException ex) { //Any unexpected strings would show up here. The expected string values would have already //been replaced with a number, so this should work if there are no errors. - throw new Exceptions.FormatException("Unknown string passed in format for " + getName() + " \"" + s + "\"", t); + throw new CREFormatException("Unknown string passed in format for " + getName() + " \"" + s + "\"", t); } } Collections.sort(list); - if(!range.contains(list.get(0))){ - throw new Exceptions.FormatException("Expecting value to be within the range " + range + " in format for " + getName() + ", but the value was " + list.get(0), t); + if(!range.contains(list.get(0))) { + throw new CREFormatException("Expecting value to be within the range " + range + " in format for " + getName() + ", but the value was " + list.get(0), t); } - if(!range.contains(list.get(list.size() - 1))){ - throw new Exceptions.FormatException("Expecting value to be within the range " + range + " in format for " + getName() + ", but the value was " + list.get(list.size() - 1), t); + if(!range.contains(list.get(list.size() - 1))) { + throw new CREFormatException("Expecting value to be within the range " + range + " in format for " + getName() + ", but the value was " + list.get(list.size() - 1), t); } - Set set = new TreeSet(list); - switch(i){ + Set set = new TreeSet<>(list); + switch(i) { case 0: f.min = set; break; @@ -990,11 +1129,12 @@ private CronFormat validateFormat(String format, Target t){ } private static class CronFormat { - public Set min = new HashSet(); - public Set hour = new HashSet(); - public Set day = new HashSet(); - public Set month = new HashSet(); - public Set dayOfWeek = new HashSet(); + + public Set min = new HashSet<>(); + public Set hour = new HashSet<>(); + public Set day = new HashSet<>(); + public Set month = new HashSet<>(); + public Set dayOfWeek = new HashSet<>(); public CClosure job; @@ -1022,7 +1162,7 @@ public String docs() { @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } @Override @@ -1031,9 +1171,12 @@ public Set optimizationOptions() { } @Override - public ParseTree optimizeDynamic(Target t, List children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { - if(children.get(0).isConst()){ - if(children.get(0).getData() instanceof CString){ + public ParseTree optimizeDynamic(Target t, Environment env, + Set> envs, + List children, FileOptions fileOptions) + throws ConfigCompileException, ConfigRuntimeException { + if(children.get(0).isConst()) { + if(children.get(0).getData().isInstanceOf(CString.TYPE)) { validateFormat(children.get(0).getData().val(), t); } } @@ -1046,8 +1189,8 @@ public ParseTree optimizeDynamic(Target t, List children, FileOptions public static class clear_cron extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.RangeException, ExceptionType.CastException}; + public Class[] thrown() { + return new Class[]{CRERangeException.class, CRECastException.class}; } @Override @@ -1061,16 +1204,16 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - Integer id = (Integer)environment.getEnv(GlobalEnv.class).GetCustom("cron-task-id"); - if(args.length == 1){ - id = (int)Static.getInt(args[0], t); + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + Integer id = (Integer) environment.getEnv(GlobalEnv.class).GetCustom("cron-task-id"); + if(args.length == 1) { + id = (int) ArgumentValidation.getInt(args[0], t); } - if(id == null){ - throw new Exceptions.RangeException("No task ID provided, and not running from within a cron task.", t); + if(id == null) { + throw new CRERangeException("No task ID provided, and not running from within a cron task.", t); } - if(!set_cron.stopJob(id)){ - throw new Exceptions.RangeException("Task ID invalid", t); + if(!set_cron.stopJob(id)) { + throw new CRERangeException("Task ID invalid", t); } return CVoid.VOID; } @@ -1095,7 +1238,7 @@ public String docs() { @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } } diff --git a/src/main/java/com/laytonsmith/core/functions/Scoreboards.java b/src/main/java/com/laytonsmith/core/functions/Scoreboards.java index 7e87bd245a..a0998c38f2 100644 --- a/src/main/java/com/laytonsmith/core/functions/Scoreboards.java +++ b/src/main/java/com/laytonsmith/core/functions/Scoreboards.java @@ -1,17 +1,25 @@ package com.laytonsmith.core.functions; +import com.laytonsmith.PureUtilities.Common.StreamUtils; import com.laytonsmith.PureUtilities.Common.StringUtils; +import com.laytonsmith.PureUtilities.Version; import com.laytonsmith.abstraction.MCObjective; -import com.laytonsmith.abstraction.MCOfflinePlayer; import com.laytonsmith.abstraction.MCPlayer; import com.laytonsmith.abstraction.MCScoreboard; import com.laytonsmith.abstraction.MCTeam; +import com.laytonsmith.abstraction.enums.MCChatColor; import com.laytonsmith.abstraction.enums.MCCriteria; import com.laytonsmith.abstraction.enums.MCDisplaySlot; +import com.laytonsmith.abstraction.enums.MCOption; +import com.laytonsmith.abstraction.enums.MCOptionStatus; import com.laytonsmith.annotations.api; -import com.laytonsmith.core.CHVersion; +import com.laytonsmith.core.ArgumentValidation; +import com.laytonsmith.core.MSLog; +import com.laytonsmith.core.MSVersion; import com.laytonsmith.core.NotInitializedYetException; import com.laytonsmith.core.Static; +import com.laytonsmith.core.compiler.signature.FunctionSignatures; +import com.laytonsmith.core.compiler.signature.SignatureBuilder; import com.laytonsmith.core.constructs.CArray; import com.laytonsmith.core.constructs.CBoolean; import com.laytonsmith.core.constructs.CInt; @@ -20,9 +28,17 @@ import com.laytonsmith.core.constructs.CVoid; import com.laytonsmith.core.constructs.Construct; import com.laytonsmith.core.constructs.Target; +import com.laytonsmith.core.environments.CommandHelperEnvironment; import com.laytonsmith.core.environments.Environment; +import com.laytonsmith.core.exceptions.CRE.CREFormatException; +import com.laytonsmith.core.exceptions.CRE.CREIllegalArgumentException; +import com.laytonsmith.core.exceptions.CRE.CRELengthException; +import com.laytonsmith.core.exceptions.CRE.CRENullPointerException; +import com.laytonsmith.core.exceptions.CRE.CREPlayerOfflineException; +import com.laytonsmith.core.exceptions.CRE.CREScoreboardException; +import com.laytonsmith.core.exceptions.CRE.CREThrowable; import com.laytonsmith.core.exceptions.ConfigRuntimeException; -import com.laytonsmith.core.functions.Exceptions.ExceptionType; +import com.laytonsmith.core.natives.interfaces.Mixed; import java.util.HashMap; import java.util.Map; import java.util.Set; @@ -31,48 +47,59 @@ * * @author jb_aero */ -public class Scoreboards { +public final class Scoreboards { public static String docs() { return "A class of functions for manipulating the server scoreboard."; } + private Scoreboards() { + } + /** * The name storing the server's main scoreboard */ public static final String MAIN = "main"; private static final String DEF_MSG = "Scoreboard defaults to '" + MAIN + "' if not given."; - private static Map boards = new HashMap(); + private static final Map BOARDS = new HashMap<>(); static { - if (!isBoard(MAIN)) { - try{ + if(!isBoard(MAIN)) { + try { addBoard(MAIN, Static.getServer().getMainScoreboard(), Target.UNKNOWN); - } catch(NotInitializedYetException e){ + } catch (NotInitializedYetException e) { //This should only happen during testing or from the shell (or some other cases) so just log //it and continue on. - e.printStackTrace(System.err); + e.printStackTrace(StreamUtils.GetSystemErr()); + } catch (NoClassDefFoundError ex) { + // This will only happen in cases where we are not linked with the bukkit server. + // In that case, it means that literally no minecraft functions will work, so + // probably we are running from command line or some other tool. In that case, + // we just want to completely ignore this, because either the user knows what they + // are doing, or pretty much nothing is going to work for them anyways. } } } /** * Checks if a scoreboard is being tracked + * * @param id Name to check for * @return if there is a scoreboard with the given id */ public static boolean isBoard(String id) { - return boards.containsKey(id); + return BOARDS.containsKey(id); } /** * Checks if a scoreboard is being tracked + * * @param board Scoreboard to check for * @return if the given scoreboard is already being tracked */ public static boolean isBoard(MCScoreboard board) { - for (MCScoreboard s : boards.values()) { - if (s.equals(board)) { + for(MCScoreboard s : BOARDS.values()) { + if(s.equals(board)) { return true; } } @@ -81,92 +108,119 @@ public static boolean isBoard(MCScoreboard board) { /** * Adds a scoreboard to the cache + * * @param id The name to save the new scoreboard as - * @param board Scoreboard, either from {@link MCServer#getNewScoreboard} or {@link MCPlayer#getScoreboard()} + * @param board Scoreboard, either from {@link com.laytonsmith.abstraction.MCServer#getNewScoreboard()} or + * {@link MCPlayer#getScoreboard()} * @param t - * @throws ConfigRuntimeException if the cache already contains the board or the id + * @throws CREScoreboardException if the cache already contains the board or the id */ - public static void addBoard(String id, MCScoreboard board, Target t) { - if (isBoard(id)) { - throw new ScoreboardException("That id is already in use.", t); + public static void addBoard(String id, MCScoreboard board, Target t) throws CREScoreboardException { + if(isBoard(id)) { + throw new CREScoreboardException("That id is already in use.", t); } - if (isBoard(board)) { - throw new ScoreboardException("That Scoreboard is already added.", t); + if(isBoard(board)) { + throw new CREScoreboardException("That Scoreboard is already added.", t); } - boards.put(id, board); + BOARDS.put(id, board); } /** * Gets a scoreboard from the cache + * * @param id Name of the scoreboard to look for * @param t * @return the scoreboard with the given id - * @throws ConfigRuntimeException if the cache does not contain id + * @throws CREScoreboardException if the cache does not contain id or if the MCScoreboard object is null */ - public static MCScoreboard getBoard(String id, Target t) { - if (!isBoard(id)) { - throw new ScoreboardException("The specified scoreboard does not exist.", t); + public static MCScoreboard getBoard(String id, Target t) throws CREScoreboardException { + if(!isBoard(id)) { + throw new CREScoreboardException("The specified scoreboard does not exist.", t); } - return boards.get(id); + MCScoreboard ret = BOARDS.get(id); + if(ret == null) { + throw new CREScoreboardException("The specified scoreboard is null. Are you running from cmdline mode?", t); + } + return ret; } /** * Searches the cache for the given scoreboard, and returns the ID matching it. + * * @param board The scoreboard to find the ID for * @param t * @return the ID of the scoreboard - * @throws ConfigRuntimeException if the cache does not contain the given scoreboard + * @throws CREScoreboardException if the cache does not contain the given scoreboard */ - public static String getBoardID(MCScoreboard board, Target t) { - for (Map.Entry e : boards.entrySet()) { - if (board.equals(e.getValue())) { + public static String getBoardID(MCScoreboard board, Target t) throws CREScoreboardException { + for(Map.Entry e : BOARDS.entrySet()) { + if(board.equals(e.getValue())) { return e.getKey(); } } - throw new ScoreboardException("The given scoreboard has not been registered yet.", t); + throw new CREScoreboardException("The given scoreboard has not been registered yet.", t); } /** * Removes a scoreboard from the cache, without clearing any of its data + * * @param id The scoreboard to remove * @param t - * @throws ConfigRuntimeException if used on MAIN or if the cache does not contain id + * @throws CREScoreboardException if used on MAIN or if the cache does not contain id */ - public static void removeBoard(String id, Target t) { - if (id.equalsIgnoreCase(MAIN)) { - throw new ScoreboardException("Cannot remove the main server scoreboard.", t); + public static void removeBoard(String id, Target t) throws CREScoreboardException { + if(id.equalsIgnoreCase(MAIN)) { + throw new CREScoreboardException("Cannot remove the main server scoreboard.", t); } - if (!isBoard(id)) { - throw new ScoreboardException("The specified scoreboard does not exist.", t); + if(!isBoard(id)) { + throw new CREScoreboardException("The specified scoreboard does not exist.", t); } - boards.remove(id); + BOARDS.remove(id); } /** * A shortcut for making a scoreboard argument optional - * @param numArgsToReadName the number of arguments that will cause the function to check user input + * * @param indexOfName the index that will contain the name of the scoreboard * @param t * @param args the array of arguments passed to the function * @return the scoreboard chosen, defaulting to main if numArgsToReadName was not matched + * @throws CREScoreboardException if the specified scoreboard does not exist */ - public static MCScoreboard assignBoard(int numArgsToReadName, int indexOfName, Target t, Construct... args) { - if (args.length == numArgsToReadName) { + static MCScoreboard assignBoard(int indexOfName, Target t, Mixed... args) throws CREScoreboardException { + if(args.length == indexOfName + 1) { return getBoard(args[indexOfName].val(), t); } return getBoard(MAIN, t); } - public static class ScoreboardException extends ConfigRuntimeException { - public ScoreboardException(String msg, Target t) { - super(msg, ExceptionType.ScoreboardException, t); - } + static CArray getTeam(MCTeam team, Target t) { + CArray to = CArray.GetAssociativeArray(t); + to.set("name", new CString(team.getName(), t), t); + to.set("displayname", new CString(team.getDisplayName(), t), t); + to.set("prefix", new CString(team.getPrefix(), t), t); + to.set("suffix", new CString(team.getSuffix(), t), t); + to.set("color", new CString(team.getColor().name(), t), t); + to.set("size", new CInt(team.getSize(), t), t); + CArray ops = CArray.GetAssociativeArray(t); + ops.set("friendlyfire", CBoolean.get(team.allowFriendlyFire()), t); + ops.set("friendlyinvisibles", CBoolean.get(team.canSeeFriendlyInvisibles()), t); + ops.set("nametagvisibility", new CString(team.getOption(MCOption.NAME_TAG_VISIBILITY).name(), t), t); + ops.set("collisionrule", new CString(team.getOption(MCOption.COLLISION_RULE).name(), t), t); + ops.set("deathmessagevisibility", new CString(team.getOption(MCOption.DEATH_MESSAGE_VISIBILITY).name(), t), t); + to.set("options", ops, t); + CArray pl = new CArray(t); + for(String entry : team.getEntries()) { + pl.push(new CString(entry, t), t); + } + to.set("players", pl, t); + return to; } /** * Contains methods that should be the same for most scoreboard functions */ - public static abstract class SBFunction extends AbstractFunction { + public abstract static class SBFunction extends AbstractFunction { /** * @return true @@ -175,6 +229,7 @@ public static abstract class SBFunction extends AbstractFunction { public boolean isRestricted() { return true; } + /** * @return false */ @@ -182,33 +237,26 @@ public boolean isRestricted() { public Boolean runAsync() { return false; } + /** - * @return {@link CHVersion#V3_3_1} - */ - @Override - public CHVersion since() { - return CHVersion.V3_3_1; - } - /** - * @return Array containing only {@link ExceptionType#ScoreboardException} + * @return Array containing only {@link CREScoreboardException} */ @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.ScoreboardException}; + public Class[] thrown() { + return new Class[]{CREScoreboardException.class}; } } - @api + @api(environments = {CommandHelperEnvironment.class}) public static class get_pscoreboard extends SBFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException, ExceptionType.ScoreboardException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CREScoreboardException.class}; } @Override - public Construct exec(Target t, Environment environment, - Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCPlayer p = Static.GetPlayer(args[0], t); String ret; try { @@ -232,25 +280,29 @@ public Integer[] numArgs() { @Override public String docs() { - return "scoreboard {player} Returns the id of the scoreboard assigned to a player." + return "string {player} Returns the id of the scoreboard a player is assigned to." + " If it is not already cached, it will be added using the player's name." + " Using this method, it should be possible to import scoreboards created by other plugins."; } + + @Override + public MSVersion since() { + return MSVersion.V3_3_1; + } } - @api + @api(environments = {CommandHelperEnvironment.class}) public static class set_pscoreboard extends SBFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException, ExceptionType.ScoreboardException}; + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CREScoreboardException.class}; } @Override - public Construct exec(Target t, Environment environment, - Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCPlayer p = Static.GetPlayer(args[0], t); - p.setScoreboard(assignBoard(2, 1, t, args)); + p.setScoreboard(assignBoard(1, t, args)); return CVoid.VOID; } @@ -269,22 +321,26 @@ public String docs() { return "void {player, [scoreboard]} Sets the scoreboard to be used by a player." + " The scoreboard argument is the id of a registered scoreboard. " + DEF_MSG; } + + @Override + public MSVersion since() { + return MSVersion.V3_3_1; + } } - @api + @api(environments = {CommandHelperEnvironment.class}) public static class get_scoreboards extends SBFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{}; } @Override - public Construct exec(Target t, Environment environment, - Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { CArray ret = new CArray(t); - for (String id : boards.keySet()) { - ret.push(new CString(id, t)); + for(String id : BOARDS.keySet()) { + ret.push(new CString(id, t), t); } return ret; } @@ -302,25 +358,29 @@ public Integer[] numArgs() { @Override public String docs() { return "array {} Returns an array of the registered scoreboard ID's." - + " The special scoreboard '"+ MAIN + "' represents the server's main" + + " The special scoreboard '" + MAIN + "' represents the server's main" + " scoreboard which can be managed by the vanilla /scoreboard command."; } + + @Override + public MSVersion since() { + return MSVersion.V3_3_1; + } } - @api + @api(environments = {CommandHelperEnvironment.class}) public static class get_objectives extends SBFunction { @Override - public Construct exec(Target t, Environment environment, - Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCScoreboard s; - if (args.length == 0) { + if(args.length == 0) { s = getBoard(MAIN, t); } else { s = getBoard(args[0].val(), t); } Set os; - if (args.length == 2) { + if(args.length == 2) { MCCriteria crit; try { crit = MCCriteria.valueOf(args[1].val()); @@ -332,18 +392,18 @@ public Construct exec(Target t, Environment environment, os = s.getObjectives(); } CArray ret = new CArray(t); - for (MCObjective o : os) { + for(MCObjective o : os) { CArray obj = CArray.GetAssociativeArray(t); obj.set("name", new CString(o.getName(), t), t); obj.set("displayname", new CString(o.getDisplayName(), t), t); Construct slot = CNull.NULL; - if (o.getDisplaySlot() != null) { + if(o.getDisplaySlot() != null) { slot = new CString(o.getDisplaySlot().name(), t); } obj.set("slot", slot, t); obj.set("modifiable", CBoolean.get(o.isModifiable()), t); obj.set("criteria", new CString(o.getCriteria(), t), t); - ret.push(obj); + ret.push(obj, t); } return ret; } @@ -365,38 +425,27 @@ public String docs() { + " If criteria is given, only objectives with that criteria will be returned." + " The arrays contain the keys name, displayname, slot, modifiable, and criteria."; } + + @Override + public MSVersion since() { + return MSVersion.V3_3_1; + } } - @api + @api(environments = {CommandHelperEnvironment.class}) public static class get_teams extends SBFunction { @Override - public Construct exec(Target t, Environment environment, - Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCScoreboard s; - if (args.length == 0) { + if(args.length == 0) { s = getBoard(MAIN, t); } else { s = getBoard(args[0].val(), t); } - CArray ret = new CArray(t); - for (MCTeam team : s.getTeams()) { - CArray to = CArray.GetAssociativeArray(t); - to.set("name", new CString(team.getName(), t), t); - to.set("displayname", new CString(team.getDisplayName(), t), t); - to.set("prefix", new CString(team.getPrefix(), t), t); - to.set("suffix", new CString(team.getSuffix(), t), t); - to.set("size", new CInt(team.getSize(), t), t); - CArray ops = new CArray(t); - ops.set("friendlyfire", CBoolean.get(team.allowFriendlyFire()), t); - ops.set("friendlyinvisibles", CBoolean.get(team.canSeeFriendlyInvisibles()), t); - to.set("options", ops, t); - CArray pl = new CArray(t); - for (MCOfflinePlayer ofp : team.getPlayers()) { - pl.push(new CString(ofp.getName(), t)); - } - to.set("players", pl, t); - ret.push(to); + CArray ret = CArray.GetAssociativeArray(t); + for(MCTeam team : s.getTeams()) { + ret.set(team.getName(), getTeam(team, t), t); } return ret; } @@ -414,18 +463,34 @@ public Integer[] numArgs() { @Override public String docs() { return "array {[scoreboard]} Returns an array of arrays about the teams on the given scoreboard," - + " which defaults to '" + MAIN + "' if not given. The arrays contain the keys name," - + " displayname, prefix, suffix, size, options, and players."; + + " which defaults to '" + MAIN + "' if not given. The array keys are the team names," + + " and each value is a team array containing the keys: name, displayname, prefix, suffix, size," + + " color, options, and players."; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_1; } } - @api + @api(environments = {CommandHelperEnvironment.class}) public static class create_scoreboard extends SBFunction { @Override - public Construct exec(Target t, Environment environment, - Construct... args) throws ConfigRuntimeException { - addBoard(args[0].val(), Static.getServer().getNewScoreboard(), t); + public Class[] thrown() { + return new Class[]{CRENullPointerException.class, CREScoreboardException.class}; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCScoreboard newBoard = Static.getServer().getNewScoreboard(); + if(newBoard == null) { + throw new CRENullPointerException( + "Could not create scoreboard, the server returned a null scoreboard" + + " (Are you running in cmdline mode?)", t); + } + addBoard(args[0].val(), newBoard, t); return CVoid.VOID; } @@ -441,29 +506,25 @@ public Integer[] numArgs() { @Override public String docs() { - return "void {name} Creates a new scoreboard identified by the given name," - + " and stores it internally for later use. Throws an exception if the name is already in use."; + return "void {name} Creates a new scoreboard identified by the given name, and stores it internally" + + " for later use. Throws a ScoreboardException if the name is already in use."; } - } - - @api - public static class create_objective extends SBFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.LengthException, ExceptionType.ScoreboardException}; + public MSVersion since() { + return MSVersion.V3_3_1; } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class create_objective extends SBFunction { @Override - public Construct exec(Target t, Environment environment, - Construct... args) throws ConfigRuntimeException { - MCScoreboard s = assignBoard(3, 2, t, args); + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCScoreboard s = assignBoard(2, t, args); String name = args[0].val(); - if (name.length() > 16) { - throw new Exceptions.LengthException("Objective names should be no more than 16 characters", t); - } MCCriteria criteria = MCCriteria.DUMMY; - if (args.length == 2) { + if(args.length > 1) { try { criteria = MCCriteria.valueOf(args[1].val().toUpperCase()); } catch (IllegalArgumentException iae) { @@ -473,7 +534,7 @@ public Construct exec(Target t, Environment environment, try { s.registerNewObjective(name, criteria.getCriteria()); } catch (IllegalArgumentException iae) { - throw new ScoreboardException("An objective by that name already exists.", t); + throw new CREScoreboardException(iae.getMessage(), t); } return CVoid.VOID; } @@ -490,35 +551,30 @@ public Integer[] numArgs() { @Override public String docs() { - return "void {name, [criteria, [scoreboard]]} Adds a new objective to the scoreboard," - + " throwing a ScoreboardException if the name is already in use. The vanilla criteria names are " - + StringUtils.Join(MCCriteria.values(), ", ", ", and ") + ". You can put anything," - + " but if none of the other values match, 'dummy' will be used." - + " Those values which are not 'dummy' are server-managed." - + " Throws a LengthException if the name is more than 16 characters. " + DEF_MSG; + return "void {name, [criteria, [scoreboard]]} Adds a new objective to the scoreboard, throwing a" + + " CREScoreboardException if the name is already in use or is too long. The vanilla criteria names" + + " are " + StringUtils.Join(MCCriteria.values(), ", ", ", and ") + ". You can put" + + " anything, but if none of the other values match, 'dummy' will be used." + + " Those values which are not 'dummy' are server-managed." + DEF_MSG; } - } - - @api - public static class create_team extends SBFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.LengthException, ExceptionType.ScoreboardException}; + public MSVersion since() { + return MSVersion.V3_3_1; } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class create_team extends SBFunction { @Override - public Construct exec(Target t, Environment environment, - Construct... args) throws ConfigRuntimeException { - MCScoreboard s = assignBoard(2, 1, t, args); + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCScoreboard s = assignBoard(1, t, args); String name = args[0].val(); - if (name.length() > 16) { - throw new Exceptions.LengthException("Team names should be no more than 16 characters.", t); - } try { s.registerNewTeam(name); } catch (IllegalArgumentException iae) { - throw new ScoreboardException("A team by that name already exists.", t); + throw new CREScoreboardException(iae.getMessage(), t); } return CVoid.VOID; } @@ -535,60 +591,63 @@ public Integer[] numArgs() { @Override public String docs() { - return "void {name, [scoreboard]} Adds a new team to the scoreboard," - + " throws a ScoreboardException if a team already exists with the given name." - + " Throws a LengthException if the team name is more than 16 characters. " + DEF_MSG; + return "void {name, [scoreboard]} Adds a new team to the scoreboard." + + " Throws a ScoreboardException if that team name already exists or is too long." + + DEF_MSG; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_1; } } - @api + @api(environments = {CommandHelperEnvironment.class}) public static class set_objective_display extends SBFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.FormatException, ExceptionType.LengthException, - ExceptionType.ScoreboardException}; + public Class[] thrown() { + return new Class[]{CREFormatException.class, CRELengthException.class, CREScoreboardException.class}; } @Override - public Construct exec(Target t, Environment environment, - Construct... args) throws ConfigRuntimeException { - MCScoreboard s = assignBoard(3, 2, t, args); + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCScoreboard s = assignBoard(2, t, args); MCObjective o = s.getObjective(args[0].val()); - if (o == null) { - throw new ScoreboardException("No objective by that name exists.", t); + if(o == null) { + throw new CREScoreboardException("No objective by that name exists.", t); } - CArray dis = new CArray(t); - if (args[1] instanceof CArray) { + CArray dis = CArray.GetAssociativeArray(t); + if(args[1].isInstanceOf(CArray.TYPE)) { dis = (CArray) args[1]; } else { dis.set("displayname", args[1], t); } - if (dis.containsKey("slot")) { + if(dis.containsKey("slot")) { MCDisplaySlot slot; - if (dis.get("slot", t) instanceof CNull) { + if(dis.get("slot", t) instanceof CNull) { slot = null; } else { try { slot = MCDisplaySlot.valueOf(dis.get("slot", t).val().toUpperCase()); } catch (IllegalArgumentException iae) { - throw new Exceptions.FormatException("Unknown displayslot: " + dis.get("slot", t).val(), t); + throw new CREFormatException("Unknown displayslot: " + dis.get("slot", t).val(), t); } } o.setDisplaySlot(slot); } - if (dis.containsKey("displayname")) { + if(dis.containsKey("displayname")) { String dname; - if (dis.get("displayname", t) instanceof CNull) { + if(dis.get("displayname", t) instanceof CNull) { dname = o.getName(); } else { dname = dis.get("displayname", t).val(); } - if (dname.length() > 32) { - throw new Exceptions.LengthException("Displayname can only be 32 characters but was " - + dname.length(), t); + try { + o.setDisplayName(dname); + } catch (IllegalArgumentException ex) { + throw new CRELengthException(ex.getMessage(), t); } - o.setDisplayName(dname); } return CVoid.VOID; } @@ -609,72 +668,92 @@ public String docs() { + " Sets the display name and/or slot of the given objective. If arg 2 is not an array," + " it is assumed to be the displayname, otherwise arg 2 should be an array" + " with keys 'displayname' and/or 'slot', affecting their respective properties." - + " Null name resets it to the actual name, and null slot removes it from" + + " A null displayname resets it to the actual name, and a null slot removes it from" + " all displays. Slot can be one of: " + StringUtils.Join(MCDisplaySlot.values(), ", ", ", or ") - + ". Displayname can be a max of 32 characters, otherwise it throws a LengthException. " + DEF_MSG; + + " ---- If the displayname is too long, a LengthException will be thrown." + + " The max length will differ based on server version, but this limit was removed in 1.20.1." + + " Sidebar display slots for teams was added to Spigot in 1.19.2 and Paper in 1.17.1." + + DEF_MSG; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_1; } } - @api + @api(environments = {CommandHelperEnvironment.class}) public static class set_team_display extends SBFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.LengthException, ExceptionType.ScoreboardException}; + public Class[] thrown() { + return new Class[]{CRELengthException.class, CREScoreboardException.class, + CREIllegalArgumentException.class}; } @Override - public Construct exec(Target t, Environment environment, - Construct... args) throws ConfigRuntimeException { - MCScoreboard s = assignBoard(3, 2, t, args); + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCScoreboard s = assignBoard(2, t, args); MCTeam o = s.getTeam(args[0].val()); - if (o == null) { - throw new ScoreboardException("No team by that name exists.", t); + if(o == null) { + throw new CREScoreboardException("No team by that name exists.", t); } - CArray dis = new CArray(t); - if (args[1] instanceof CArray) { + CArray dis = CArray.GetAssociativeArray(t); + if(args[1].isInstanceOf(CArray.TYPE)) { dis = (CArray) args[1]; } else { dis.set("displayname", args[1], t); } - if (dis.containsKey("displayname")) { + if(dis.containsKey("displayname")) { String dname; - if (dis.get("displayname", t) instanceof CNull) { + if(dis.get("displayname", t) instanceof CNull) { dname = o.getName(); } else { dname = dis.get("displayname", t).val(); } - if (dname.length() > 32) { - throw new Exceptions.LengthException("Displayname can only be 32 characters but was " - + dname.length(), t); + try { + o.setDisplayName(dname); + } catch (IllegalArgumentException ex) { + // defined by the server api, not by minecraft + throw new CRELengthException(ex.getMessage(), t); } - o.setDisplayName(dname); } - if (dis.containsKey("prefix")) { + if(dis.containsKey("prefix")) { String prefix; - if (dis.get("prefix", t) instanceof CNull) { + if(dis.get("prefix", t) instanceof CNull) { prefix = ""; } else { prefix = dis.get("prefix", t).val(); } - if (prefix.length() > 16) { - throw new Exceptions.LengthException("Prefix can only be 16 characters but was " - + prefix.length(), t); + try { + o.setPrefix(prefix); + } catch (IllegalArgumentException ex) { + // defined by the server api, not by minecraft + throw new CRELengthException(ex.getMessage(), t); } - o.setPrefix(prefix); } - if (dis.containsKey("suffix")) { + if(dis.containsKey("suffix")) { String suffix; - if (dis.get("suffix", t) instanceof CNull) { + if(dis.get("suffix", t) instanceof CNull) { suffix = ""; } else { suffix = dis.get("suffix", t).val(); } - if (suffix.length() > 16) { - throw new Exceptions.LengthException("Suffix can only be 16 characters but was " - + suffix.length(), t); + try { + o.setSuffix(suffix); + } catch (IllegalArgumentException ex) { + // defined by the server api, not by minecraft + throw new CRELengthException(ex.getMessage(), t); + } + } + if(dis.containsKey("color")) { + try { + MCChatColor color = MCChatColor.valueOf(dis.get("color", t).val().toUpperCase()); + o.setColor(color); + } catch (IllegalArgumentException ex) { + throw new CREIllegalArgumentException("Invalid chat color: \"" + + dis.get("color", t).val() + "\"", t); } - o.setSuffix(suffix); } return CVoid.VOID; } @@ -691,37 +770,40 @@ public Integer[] numArgs() { @Override public String docs() { + MCChatColor[] values = MCChatColor.values(); + String[] colors = new String[values.length]; + for(int i = 0; i < colors.length; i++) { + colors[i] = values[i].name(); + } return "void {teamName, array, [scoreboard] | teamName, displayname, [scoreboard]}" - + " Sets the display name, prefix, and/or suffix of the given team." + + " Sets the display name, color, prefix, and/or suffix of the given team." + " If arg 2 is not an array, it is assumed to be the displayname," - + " otherwise arg 2 should be an array with keys 'displayname', 'prefix'," + + " otherwise arg 2 should be an array with keys 'displayname', 'color', 'prefix'," + " and/or 'suffix', affecting their respective properties." - + " Null name resets it to the actual name, and null prefix or suffix removes it from" - + " all displays. Displayname can be a max of 32 characters," - + " prefix and suffix can only be 16, otherwise a LengthException is thrown. " + DEF_MSG; + + " A null displayname resets it to the actual name, and a null prefix or suffix removes it from" + + " all displays. Color can be one of " + StringUtils.Join(colors, ", ", " or ") + "." + + " ---- If the prefix, suffix, or displayname is too long, a LengthException will be thrown." + + " The max length will differ based on server version, but these limits were removed in 1.20.1." + + DEF_MSG; } - } - - @api - public static class team_add_player extends SBFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.LengthException, ExceptionType.ScoreboardException}; + public MSVersion since() { + return MSVersion.V3_3_1; } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class team_add_player extends SBFunction { @Override - public Construct exec(Target t, Environment environment, - Construct... args) throws ConfigRuntimeException { - MCScoreboard s = assignBoard(3, 2, t, args); + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCScoreboard s = assignBoard(2, t, args); MCTeam team = s.getTeam(args[0].val()); - if (team == null) { - throw new ScoreboardException("No team by that name exists.", t); - } - if (args[1].val().length() > 16) { - throw new Exceptions.LengthException("Player names can only be 16 characters.", t); + if(team == null) { + throw new CREScoreboardException("No team by that name exists.", t); } - team.addPlayer(Static.getServer().getOfflinePlayer(args[1].val())); + team.addEntry(args[1].val()); return CVoid.VOID; } @@ -737,25 +819,27 @@ public Integer[] numArgs() { @Override public String docs() { - return "void {teamName, player, [scoreboard]} Adds a player to a team, given the team exists." - + " Offline players can be added, so the name must be exact. Alternatively," - + " this allows you to add fake players, but names can still only be 16 characters." - + " The player will be removed from any other team on the same scoreboard. " + DEF_MSG; + return "void {teamName, entry, [scoreboard]} Adds a player (or entity UUID) to a team, given the team exists." + + " It will be removed from any other team on the same scoreboard. " + DEF_MSG; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_1; } } - @api + @api(environments = {CommandHelperEnvironment.class}) public static class team_remove_player extends SBFunction { @Override - public Construct exec(Target t, Environment environment, - Construct... args) throws ConfigRuntimeException { - MCScoreboard s = assignBoard(3, 2, t, args); + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCScoreboard s = assignBoard(2, t, args); MCTeam team = s.getTeam(args[0].val()); - if (team == null) { - throw new ScoreboardException("No team by that name exists.", t); + if(team == null) { + throw new CREScoreboardException("No team by that name exists.", t); } - return CBoolean.get(team.removePlayer(Static.getServer().getOfflinePlayer(args[1].val()))); + return CBoolean.get(team.removeEntry(args[1].val())); } @Override @@ -770,36 +854,76 @@ public Integer[] numArgs() { @Override public String docs() { - return "boolean {teamname, player, [scoreboard]} Attempts to remove a player from a team," - + " and returns true if successful, for false if the player was not part of the team." + DEF_MSG; + return "boolean {teamname, entry, [scoreboard]} Attempts to remove a player (or entity UUID) from a team," + + " and returns true if successful, for false if it was not part of the team." + DEF_MSG; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_1; } } - @api + @api(environments = {CommandHelperEnvironment.class}) + public static class get_pteam extends SBFunction { + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCScoreboard s = assignBoard(1, t, args); + MCTeam team = s.getPlayerTeam(args[0].val()); + if(team == null) { + return CNull.NULL; + } + return getTeam(team, t); + } + + @Override + public String getName() { + return "get_pteam"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1, 2}; + } + + @Override + public String docs() { + return "array {player, [scoreboard]} Returns a team array for this player, or null if not in a team." + + " Contains the keys name, displayname, color, prefix, suffix, size, options, and players." + + DEF_MSG; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_2; + } + } + + @api(environments = {CommandHelperEnvironment.class}) public static class remove_scoreboard extends SBFunction { @Override - public Construct exec(Target t, Environment environment, - Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { String id = args[0].val(); boolean nullify = true; - if (args.length == 2) { - nullify = Static.getBoolean(args[1]); + if(args.length == 2) { + nullify = ArgumentValidation.getBoolean(args[1], t); } - if (nullify) { + if(nullify) { MCScoreboard s = getBoard(id, t); - for (String p : s.getEntries()) { - s.resetScores(p); - try { - Static.GetPlayer(p, t).setScoreboard(getBoard(MAIN, t)); - } catch (ConfigRuntimeException ex){ - // Not an exception in this case + for(String e : s.getEntries()) { + s.resetScores(e); + } + for(MCPlayer p : Static.getServer().getOnlinePlayers()) { + if(s.equals(p.getScoreboard())) { + p.setScoreboard(getBoard(MAIN, t)); } } - for (MCTeam g : s.getTeams()) { + for(MCTeam g : s.getTeams()) { g.unregister(); } - for (MCObjective o : s.getObjectives()) { + for(MCObjective o : s.getObjectives()) { o.unregister(); } } @@ -825,22 +949,26 @@ public String docs() { + " and all tracked players currently online will be switched to the main scoreboard," + " essentially removing all references to the board so it can be garbage-collected."; } + + @Override + public MSVersion since() { + return MSVersion.V3_3_1; + } } - @api + @api(environments = {CommandHelperEnvironment.class}) public static class remove_objective extends SBFunction { @Override - public Construct exec(Target t, Environment environment, - Construct... args) throws ConfigRuntimeException { - MCScoreboard s = assignBoard(2, 1, t, args); + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCScoreboard s = assignBoard(1, t, args); MCObjective o = s.getObjective(args[0].val()); try { o.unregister(); } catch (NullPointerException npe) { - throw new ScoreboardException("The objective does not exist.", t); + throw new CREScoreboardException("The objective does not exist.", t); } catch (IllegalStateException ise) { - throw new ScoreboardException("The objective has already been unregistered.", t); + throw new CREScoreboardException("The objective has already been unregistered.", t); } return CVoid.VOID; } @@ -859,22 +987,26 @@ public Integer[] numArgs() { public String docs() { return "void {objectivename, [scoreboard]} Unregisters an objective from the scoreboard. " + DEF_MSG; } + + @Override + public MSVersion since() { + return MSVersion.V3_3_1; + } } - @api + @api(environments = {CommandHelperEnvironment.class}) public static class remove_team extends SBFunction { @Override - public Construct exec(Target t, Environment environment, - Construct... args) throws ConfigRuntimeException { - MCScoreboard s = assignBoard(2, 1, t, args); + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCScoreboard s = assignBoard(1, t, args); MCTeam team = s.getTeam(args[0].val()); try { team.unregister(); } catch (NullPointerException npe) { - throw new ScoreboardException("The team does not exist.", t); + throw new CREScoreboardException("The team does not exist.", t); } catch (IllegalStateException ise) { - throw new ScoreboardException("The team has already been unregistered.", t); + throw new CREScoreboardException("The team has already been unregistered.", t); } return CVoid.VOID; } @@ -893,20 +1025,33 @@ public Integer[] numArgs() { public String docs() { return "void {teamname, [scoreboard]} Unregisters a team from the scoreboard. " + DEF_MSG; } + + @Override + public MSVersion since() { + return MSVersion.V3_3_1; + } } - @api + @api(environments = {CommandHelperEnvironment.class}) public static class get_pscore extends SBFunction { @Override - public Construct exec(Target t, Environment environment, - Construct... args) throws ConfigRuntimeException { - MCScoreboard s = assignBoard(3, 2, t, args); + public Class[] thrown() { + return new Class[]{CRELengthException.class, CREScoreboardException.class}; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCScoreboard s = assignBoard(2, t, args); MCObjective o = s.getObjective(args[0].val()); - if (o == null) { - throw new ScoreboardException("The given objective does not exist.", t); + if(o == null) { + throw new CREScoreboardException("The given objective does not exist.", t); + } + try { + return new CInt(o.getScore(args[1].val()).getScore(), t); + } catch (IllegalArgumentException ex) { + throw new CRELengthException(ex.getMessage(), t); } - return new CInt(o.getScore(args[1].val()).getScore(), t); } @Override @@ -921,31 +1066,37 @@ public Integer[] numArgs() { @Override public String docs() { - return "int {objectiveName, player, [scoreboard]} Returns the player's score for the given objective." - + " Works for offline players, so the name must be exact. " + DEF_MSG; + return "int {objectiveName, name, [scoreboard]} Returns the player's score for the given objective." + + " A LengthException is thrown if the name is too long." + + DEF_MSG; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_1; } } - @api + @api(environments = {CommandHelperEnvironment.class}) public static class set_pscore extends SBFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.LengthException, ExceptionType.ScoreboardException}; + public Class[] thrown() { + return new Class[]{CRELengthException.class, CREScoreboardException.class}; } @Override - public Construct exec(Target t, Environment environment, - Construct... args) throws ConfigRuntimeException { - MCScoreboard s = assignBoard(4, 3, t, args); + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCScoreboard s = assignBoard(3, t, args); MCObjective o = s.getObjective(args[0].val()); - if (o == null) { - throw new ScoreboardException("The given objective does not exist.", t); + if(o == null) { + throw new CREScoreboardException("The given objective does not exist.", t); } - if (args[1].val().length() > 16) { - throw new Exceptions.LengthException("Player names can only be 16 characters.", t); + try { + o.getScore(args[1].val()).setScore(ArgumentValidation.getInt32(args[2], t)); + } catch (IllegalArgumentException ex) { + throw new CRELengthException(ex.getMessage(), t); } - o.getScore(args[1].val()).setScore(Static.getInt32(args[2], t)); return CVoid.VOID; } @@ -961,19 +1112,23 @@ public Integer[] numArgs() { @Override public String docs() { - return "void {objectiveName, name, int, [scoreboard]} Sets the player's score for the given objective," - + " or an arbitrary name if not a valid player name." - + " You can set scores for fake players to create custom displays," - + " but the 16 character name limit still applies. " + DEF_MSG; + return "void {objectiveName, name, int, [scoreboard]} Sets the player's score for the given objective." + + " The name can be anything, not just player names. A LengthException is thrown if it's too long." + + DEF_MSG; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_1; } } - @api + @api(environments = {CommandHelperEnvironment.class}) public static class reset_all_pscores extends SBFunction { @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - assignBoard(2, 1, t, args).resetScores(args[0].val()); + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + assignBoard(1, t, args).resetScores(args[0].val()); return CVoid.VOID; } @@ -992,34 +1147,80 @@ public String docs() { return "void {player, [scoreboard]} Resets all scores for a player tracked by the given scoreboard." + " This means they will not be show up on any displays. " + DEF_MSG; } + + @Override + public MSVersion since() { + return MSVersion.V3_3_1; + } } - @api + @api(environments = {CommandHelperEnvironment.class}) public static class set_team_options extends SBFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.ScoreboardException, ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CREScoreboardException.class, CREFormatException.class}; } @Override - public Construct exec(Target t, Environment environment, - Construct... args) throws ConfigRuntimeException { - MCScoreboard s = assignBoard(3, 2, t, args); + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCScoreboard s = assignBoard(2, t, args); MCTeam team = s.getTeam(args[0].val()); - if (team == null) { - throw new ScoreboardException("No team by that name exists.", t); + if(team == null) { + throw new CREScoreboardException("No team by that name exists.", t); } - if (args[1] instanceof CArray) { + if(args[1].isInstanceOf(CArray.TYPE)) { CArray options = (CArray) args[1]; - if (options.containsKey("friendlyfire")) { - team.setAllowFriendlyFire(Static.getBoolean(options.get("friendlyfire", t))); + if(options.containsKey("friendlyfire")) { + team.setAllowFriendlyFire(ArgumentValidation.getBoolean(options.get("friendlyfire", t), t)); + } + if(options.containsKey("friendlyinvisibles")) { + team.setCanSeeFriendlyInvisibles(ArgumentValidation.getBoolean(options.get("friendlyinvisibles", t), t)); + } + if(options.containsKey("nametagvisibility")) { + MCOptionStatus namevisibility; + try { + namevisibility = MCOptionStatus.valueOf(options.get("nametagvisibility", t).val().toUpperCase()); + } catch (IllegalArgumentException iae) { + String name = options.get("nametagvisibility", t).val().toUpperCase(); + if(name.startsWith("HIDE_")) { + name = name.substring(5); + try { + namevisibility = MCOptionStatus.valueOf(name); + MSLog.GetLogger().w(MSLog.Tags.DEPRECATION, "Found old value for NameTagVisibility: \"" + + "HIDE_" + name + "\". This should be: \"" + name + "\"", t); + } catch (IllegalArgumentException ex) { + throw new CREFormatException("Unknown nametagvisibility: " + + options.get("nametagvisibility", t).val(), t); + } + } else { + throw new CREFormatException("Unknown nametagvisibility: " + name, t); + } + } + team.setOption(MCOption.NAME_TAG_VISIBILITY, namevisibility); } - if (options.containsKey("friendlyinvisibles")) { - team.setCanSeeFriendlyInvisibles(Static.getBoolean(options.get("friendlyinvisibles", t))); + if(options.containsKey("collisionrule")) { + MCOptionStatus collision; + try { + collision = MCOptionStatus.valueOf(options.get("collisionrule", t).val().toUpperCase()); + } catch (IllegalArgumentException iae) { + throw new CREFormatException("Unknown collisionrule: " + + options.get("collisionrule", t).val(), t); + } + team.setOption(MCOption.COLLISION_RULE, collision); + } + if(options.containsKey("deathmessagevisibility")) { + MCOptionStatus deathvisibility; + try { + deathvisibility = MCOptionStatus.valueOf(options.get("deathmessagevisibility", t).val().toUpperCase()); + } catch (IllegalArgumentException iae) { + throw new CREFormatException("Unknown deathmessagevisibility: " + + options.get("deathmessagevisibility", t).val(), t); + } + team.setOption(MCOption.DEATH_MESSAGE_VISIBILITY, deathvisibility); } } else { - throw new Exceptions.FormatException("Expected arg 2 to be an array.", t); + throw new CREFormatException("Expected arg 2 to be an array.", t); } return CVoid.VOID; } @@ -1036,8 +1237,59 @@ public Integer[] numArgs() { @Override public String docs() { - return "void {teamName, array, [scoreboard]} Sets various options about the team from an array," - + " checking for keys 'friendlyfire' and 'friendlyinvisibles'. " + DEF_MSG; + return "void {teamName, array, [scoreboard]} Sets various options about the team from an array. The keys" + + " 'friendlyfire' and 'friendlyinvisibles' must be booleans. The keys 'collisionrule', " + + " 'nametagvisibility', and 'deathmessagevisibility' must be one of " + + StringUtils.Join(MCOptionStatus.values(), ", ", ", or ") + + "." + DEF_MSG; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_1; } } + + @api + public static class get_scoreboard_entries extends SBFunction { + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCScoreboard s = assignBoard(0, t, args); + Set entries = s.getEntries(); + CArray ret = new CArray(t, entries.size()); + for(String r : entries) { + ret.push(new CString(r, t), t); + } + return ret; + } + + @Override + public String getName() { + return "get_scoreboard_entries"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{0, 1}; + } + + @Override + public String docs() { + return "array {[scoreboardName]} Gets a list of all the entries in the given scoreboard."; + } + + @Override + public Version since() { + return MSVersion.V3_3_5; + } + + @Override + public FunctionSignatures getSignatures() { + return new SignatureBuilder(CArray.TYPE) + .param(CString.TYPE, "scoreboardName", "The name of the scoreboard", true) + .build(); + } + + } } diff --git a/src/main/java/com/laytonsmith/core/functions/Statistics.java b/src/main/java/com/laytonsmith/core/functions/Statistics.java new file mode 100644 index 0000000000..53484ad166 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/functions/Statistics.java @@ -0,0 +1,402 @@ +package com.laytonsmith.core.functions; + +import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.annotations.api; +import com.laytonsmith.annotations.core; +import com.laytonsmith.core.ArgumentValidation; +import com.laytonsmith.core.MSVersion; +import com.laytonsmith.core.Optimizable; +import com.laytonsmith.core.constructs.CArray; +import com.laytonsmith.core.constructs.CDouble; +import com.laytonsmith.core.constructs.CNumber; +import com.laytonsmith.core.constructs.Target; +import com.laytonsmith.core.environments.Environment; +import com.laytonsmith.core.exceptions.CRE.CRECastException; +import com.laytonsmith.core.exceptions.CRE.CREIndexOverflowException; +import com.laytonsmith.core.exceptions.CRE.CRERangeException; +import com.laytonsmith.core.exceptions.CRE.CREThrowable; +import com.laytonsmith.core.exceptions.ConfigCompileException; +import com.laytonsmith.core.exceptions.ConfigRuntimeException; +import com.laytonsmith.core.natives.interfaces.Mixed; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * + */ +@core +public class Statistics { + + public static String docs() { + return "Provides a set of functions that deal with statistical analysis of numbers."; + } + + public abstract static class StatisticsFunction extends AbstractFunction implements Optimizable { + + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class, CREIndexOverflowException.class}; + } + + @Override + public boolean isRestricted() { + return false; + } + + @Override + public Boolean runAsync() { + return null; + } + + @Override + public Set optimizationOptions() { + return EnumSet.of( + OptimizationOption.CONSTANT_OFFLINE, + OptimizationOption.NO_SIDE_EFFECTS, + OptimizationOption.CACHE_RETURN); + } + + } + + @api + public static class average extends StatisticsFunction { + + @Override + public CNumber exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + long count; + if(args.length == 1 && args[0].isInstanceOf(CArray.TYPE)) { + CArray c = ArgumentValidation.getArray(args[0], t); + count = c.size(); + } else { + count = args.length; + } + double sum = new sum().exec(t, environment, args).getNumber(); + return new CDouble(sum / count, t); + } + + @Override + public String getName() { + return "average"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{Integer.MAX_VALUE}; + } + + @Override + public String docs() { + return "number {array | number input...} Returns the average (also known as the arithmetic mean) across all" + + " the numbers in the set. The input may be an array of numbers, or individual numbers as" + + " arguments. The average of a set of numbers is the result of adding all the numbers in the" + + " set, and dividing it by the number of values in the set." + + "\n\nIf an empty array is provided, a IndexOverflowException is thrown."; + } + + @Override + public Version since() { + return MSVersion.V3_3_4; + } + + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("With single number", "average(1)"), + new ExampleScript("Two arguments", "average(5, 10)"), + new ExampleScript("As an array", "average(array(1, 2, 3, 4))") + }; + } + } + + @api + public static class sum extends StatisticsFunction { + + @Override + public CNumber exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + List values = new ArrayList<>(); + if(args.length == 1 && args[0].isInstanceOf(CArray.TYPE)) { + CArray c = ArgumentValidation.getArray(args[0], t); + for(Mixed m : c.asList()) { + values.add(ArgumentValidation.getDouble(m, t)); + } + } else { + for(Mixed m : args) { + values.add(ArgumentValidation.getDouble(m, t)); + } + } + double value = 0; + for(Double d : values) { + value += d; + } + return new CDouble(value, t); + } + + @Override + public String getName() { + return "sum"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{Integer.MAX_VALUE}; + } + + @Override + public String docs() { + return "number {array | number input...} Returns the sum across all" + + " the numbers in the set. The input may be an array of numbers, or individual numbers as" + + " arguments. The sum is the result of adding all the numbers in the set together." + + "\n\nIf an empty array is provided, a IndexOverflowException is thrown."; + } + + @Override + public Version since() { + return MSVersion.V3_3_4; + } + + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("With single number", "sum(1)"), + new ExampleScript("Two arguments", "sum(5, 10)"), + new ExampleScript("As an array", "sum(array(1, 2, 3, 4))") + }; + } + } + + @api + public static class median extends StatisticsFunction { + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + List values = new ArrayList<>(); + if(args.length == 1 && args[0].isInstanceOf(CArray.TYPE)) { + CArray c = ArgumentValidation.getArray(args[0], t); + for(Mixed m : c.asList()) { + values.add(ArgumentValidation.getDouble(m, t)); + } + } else { + for(Mixed m : args) { + values.add(ArgumentValidation.getDouble(m, t)); + } + } + Collections.sort(values); + double median; + if(values.size() % 2 == 0) { + // Even number, harder logic + median = (values.get(values.size() / 2) + values.get(values.size() / 2 - 1)) / 2; + } else { + median = values.get(values.size() / 2); + } + return new CDouble(median, t); + } + + @Override + public String getName() { + return "median"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{Integer.MAX_VALUE}; + } + + @Override + public String docs() { + return "number {array | number input...} Returns the median across all" + + " the numbers in the set. The input may be an array of numbers, or individual numbers as" + + " arguments. The median is the number that is in the center of set, once the values in the" + + " set are ordered from least to greatest. That is, in the set [1, 2, 3], 2 is the median. If" + + " there is an even number of value in the set, the middle two values are averaged, and that" + + " value is returned." + + "\n\nIf an empty array is provided, a IndexOverflowException is thrown."; + } + + @Override + public Version since() { + return MSVersion.V3_3_4; + } + + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("With single number", "median(1)"), + new ExampleScript("Three arguments", "median(1, 2, 3)"), + new ExampleScript("As an array", "median(array(1, 2, 3))"), + new ExampleScript("With an even number of values", "median(1, 2, 3, 4)") + }; + } + } + + @api + public static class mode extends StatisticsFunction { + + @Override + public CArray exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + List values = new ArrayList<>(); + if(args.length == 1 && args[0].isInstanceOf(CArray.TYPE)) { + CArray c = ArgumentValidation.getArray(args[0], t); + for(Mixed m : c.asList()) { + values.add(ArgumentValidation.getDouble(m, t)); + } + } else { + for(Mixed m : args) { + values.add(ArgumentValidation.getDouble(m, t)); + } + } + List returns = mode(values); + CArray ret = new CArray(t, returns.size()); + for(Double d : returns) { + ret.push(new CDouble(d, t), t); + } + return ret; + } + + public static List mode(List array) { + Map counts = new HashMap(); + int max = 1; + + for(int i = 0; i < array.size(); i++) { + if(counts.get(array.get(i)) != null) { + + int count = counts.get(array.get(i)); + count++; + counts.put(array.get(i), count); + + if(count > max) { + max = count; + } + } else { + counts.put(array.get(i), 1); + } + } + + List ret = new ArrayList<>(); + for(Map.Entry e : counts.entrySet()) { + if(e.getValue() == max) { + ret.add(e.getKey()); + } + } + return ret; + } + + @Override + public String getName() { + return "mode"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{Integer.MAX_VALUE}; + } + + @Override + public String docs() { + return "array {array | number input...} Returns the mode across all" + + " the numbers in the set. The input may be an array of numbers, or individual numbers as" + + " arguments. The mode of a set of numbers is the values that occur most in the set. This function" + + " supports bimodal (and generally n-modal sets), as well as fully unique sets, by returning an" + + " array. If the set is fully unique, i.e. [1, 2, 3], then the original set will be returned all" + + " values occur once). If there are more than one modes, i.e. [1, 1, 2, 3, 3], then an array of" + + " both 1 and 3 will be returned. The values will not necessarily be returned in any particular" + + " order." + + "\n\nIf an empty array is provided, a IndexOverflowException is thrown."; + } + + @Override + public Version since() { + return MSVersion.V3_3_4; + } + + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("With single number", "mode(1)"), + new ExampleScript("Multiple arguments", "mode(1, 1, 2, 3, 4)"), + new ExampleScript("As an array", "mode(array(1, 1, 2, 3, 4))"), + new ExampleScript("Bimodal set", "mode(1, 1, 2, 3, 3)"), + new ExampleScript("n-modal set (n=3)", "mode(1, 1, 2, 3, 3, 4, 5, 6, 6)"), + new ExampleScript("unsorted n-modal set (n=3)", "mode(6, 4, 2, 6, 5, 2, 1, 3, 4)"), + new ExampleScript("unique set", "mode(1, 2, 3, 4, 5)"), + }; + } + } + + @api + public static class percentile extends StatisticsFunction { + + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class, CREIndexOverflowException.class, CRERangeException.class}; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + double percentile = ArgumentValidation.getDouble(args[0], t); + List values = new ArrayList<>(); + if(args.length == 2 && args[1].isInstanceOf(CArray.TYPE)) { + CArray c = ArgumentValidation.getArray(args[1], t); + for(Mixed m : c.asList()) { + values.add(ArgumentValidation.getNumber(m, t)); + } + } else { + boolean first = true; + for(Mixed m : args) { + if(!first) { + values.add(ArgumentValidation.getNumber(m, t)); + } + first = false; + } + } + + return new CDouble(percentile(values, percentile), t); + } + + public static double percentile(List values, double percentile) { + Collections.sort(values); + int index = (int) java.lang.Math.ceil(percentile * values.size()); + return values.get(index - 1); + } + + @Override + public String getName() { + return "percentile"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{Integer.MAX_VALUE}; + } + + @Override + public String docs() { + return "number {number percentile, array | number percentile, number input...}" + + " Returns the nth-percentile across all" + + " the numbers in the set. The input may be an array of numbers, or individual numbers as" + + " arguments. A percentile is a measure indicating the value below which a given percentage of" + + " observations in a group of observations falls. For example, the 20th percentile is the value" + + " (or score) below which 20% of the observations may be found." + + "\n\nIf an empty array is provided, a IndexOverflowException is thrown. If the percentile is" + + " not within the range of 0 or 1, a RangeException is thrown."; + } + + @Override + public Version since() { + return MSVersion.V3_3_4; + } + + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("With single number (50th percentile)", "percentile(0.5, 1)"), + new ExampleScript("40th percentile of 1-10", "percentile(0.4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)"), + new ExampleScript("As an array", "percentile(0.5, array(1, 2, 3, 4))") + }; + } + } +} diff --git a/src/main/java/com/laytonsmith/core/functions/StringHandling.java b/src/main/java/com/laytonsmith/core/functions/StringHandling.java index f0394e4728..cc9e8b0fbe 100644 --- a/src/main/java/com/laytonsmith/core/functions/StringHandling.java +++ b/src/main/java/com/laytonsmith/core/functions/StringHandling.java @@ -4,32 +4,51 @@ import com.laytonsmith.PureUtilities.Common.ReflectionUtils.ReflectionException; import com.laytonsmith.PureUtilities.Common.StringUtils; import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.annotations.MEnum; +import com.laytonsmith.annotations.OperatorPreferred; import com.laytonsmith.annotations.api; import com.laytonsmith.annotations.core; +import com.laytonsmith.annotations.noboilerplate; import com.laytonsmith.annotations.noprofile; import com.laytonsmith.annotations.seealso; -import com.laytonsmith.core.CHVersion; +import com.laytonsmith.core.ArgumentValidation; +import com.laytonsmith.core.MSVersion; import com.laytonsmith.core.Optimizable; import com.laytonsmith.core.ParseTree; -import com.laytonsmith.core.Script; import com.laytonsmith.core.Static; import com.laytonsmith.core.compiler.FileOptions; import com.laytonsmith.core.compiler.OptimizationUtilities; import com.laytonsmith.core.constructs.CArray; +import com.laytonsmith.core.constructs.CBoolean; import com.laytonsmith.core.constructs.CByteArray; import com.laytonsmith.core.constructs.CFunction; import com.laytonsmith.core.constructs.CInt; +import com.laytonsmith.core.constructs.CKeyword; +import com.laytonsmith.core.constructs.CLabel; +import com.laytonsmith.core.constructs.CNull; import com.laytonsmith.core.constructs.CResource; +import com.laytonsmith.core.constructs.CSecureString; import com.laytonsmith.core.constructs.CString; import com.laytonsmith.core.constructs.CVoid; import com.laytonsmith.core.constructs.Construct; +import com.laytonsmith.core.constructs.InstanceofUtil; import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.environments.Environment; +import com.laytonsmith.core.exceptions.CRE.CRECastException; +import com.laytonsmith.core.exceptions.CRE.CREError; +import com.laytonsmith.core.exceptions.CRE.CREFormatException; +import com.laytonsmith.core.exceptions.CRE.CREInsufficientArgumentsException; +import com.laytonsmith.core.exceptions.CRE.CRENullPointerException; +import com.laytonsmith.core.exceptions.CRE.CRERangeException; +import com.laytonsmith.core.exceptions.CRE.CREThrowable; +import com.laytonsmith.core.functions.Compiler.p; +import com.laytonsmith.core.functions.DataHandling._string; +import com.laytonsmith.core.functions.DataHandling.array; +import com.laytonsmith.core.functions.DataHandling.g; import com.laytonsmith.core.exceptions.CancelCommandException; import com.laytonsmith.core.exceptions.ConfigCompileException; import com.laytonsmith.core.exceptions.ConfigRuntimeException; -import com.laytonsmith.core.functions.Exceptions.ExceptionType; -import com.laytonsmith.core.natives.interfaces.Sizable; +import com.laytonsmith.core.natives.interfaces.Mixed; import java.io.UnsupportedEncodingException; import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; @@ -40,7 +59,11 @@ import java.util.IllegalFormatException; import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.Set; +import com.laytonsmith.core.natives.interfaces.Sizeable; +import java.lang.reflect.InaccessibleObjectException; +import java.util.UUID; /** * @@ -53,98 +76,14 @@ public static String docs() { } @api - public static class cc extends AbstractFunction { - - @Override - public String getName() { - return "cc"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{Integer.MAX_VALUE}; - } - - @Override - public String docs() { - return "string {args...} The cousin to concat, this function does some magic under the covers" - + " to remove the auto-concatenation effect in bare strings. Take the following example: cc(bare string) -> barestring"; - } - - @Override - public ExceptionType[] thrown() { - return null; - } - - @Override - public boolean isRestricted() { - return false; - } - - @Override - public boolean preResolveVariables() { - return false; - } - - @Override - public CHVersion since() { - return CHVersion.V3_3_1; - } - - @Override - public Boolean runAsync() { - return null; - } - - @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - return CVoid.VOID; - } - - @Override - public Construct execs(Target t, Environment env, Script parent, ParseTree... nodes) { - //if any of the nodes are sconcat, move their children up a level - List list = new ArrayList(); - for (ParseTree node : nodes) { - if (node.getData().val().equals("sconcat")) { - for (ParseTree sub : node.getChildren()) { - list.add(sub); - } - } else { - list.add(node); - } - } - - StringBuilder b = new StringBuilder(); - for (ParseTree node : list) { - Construct c = parent.seval(node, env); - b.append(c.val()); - } - return new CString(b.toString(), t); - } - - @Override - public boolean useSpecialExec() { - return true; - } - - public ParseTree optimizeSpecial(Target target, List children) { - throw new UnsupportedOperationException("Not yet implemented"); - } - - @Override - public ExampleScript[] examples() throws ConfigCompileException { - return new ExampleScript[]{ - new ExampleScript("", "cc('These' 'normally' 'have' 'spaces' 'between' 'them')"),}; - } - } - - @api + @OperatorPreferred(".") public static class concat extends AbstractFunction implements Optimizable { + public static final String NAME = "concat"; + @Override public String getName() { - return "concat"; + return NAME; } @Override @@ -153,14 +92,14 @@ public Integer[] numArgs() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{}; } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws CancelCommandException, ConfigRuntimeException { StringBuilder b = new StringBuilder(); - for (int i = 0; i < args.length; i++) { + for(int i = 0; i < args.length; i++) { b.append(args[i].val()); } return new CString(b.toString(), t); @@ -177,8 +116,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_0_1; + public MSVersion since() { + return MSVersion.V3_0_1; } @Override @@ -187,16 +126,24 @@ public Boolean runAsync() { } @Override - public ParseTree optimizeDynamic(Target t, List children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { + public ParseTree optimizeDynamic(Target t, Environment env, + Set> envs, + List children, FileOptions fileOptions) + throws ConfigCompileException, ConfigRuntimeException { OptimizationUtilities.pullUpLikeFunctions(children, this.getName()); + for(ParseTree child : children) { + if(child.getData() instanceof CLabel) { + throw new ConfigCompileException("Invalid use of concatenation with label", child.getTarget()); + } + } return null; } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ - new ExampleScript("Functional usage", "concat('1', '2', '3', '4')"), - new ExampleScript("Symbolic usage", "'1' . '2' . '3' . '4'"),}; + new ExampleScript("Functional usage", "concat('1', '2', '3', '4')"), + new ExampleScript("Symbolic usage", "'1' . '2' . '3' . '4'")}; } @Override @@ -212,12 +159,11 @@ public Set optimizationOptions() { @noprofile public static class sconcat extends AbstractFunction implements Optimizable { - private static final String g = new DataHandling.g().getName(); - private static final String p = new Compiler.p().getName(); + public static final String NAME = "sconcat"; @Override public String getName() { - return "sconcat"; + return NAME; } @Override @@ -226,67 +172,25 @@ public Integer[] numArgs() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws CancelCommandException, ConfigRuntimeException { StringBuilder b = new StringBuilder(); - for (int i = 0; i < args.length; i++) { - if (i > 0) { + for(int i = 0; i < args.length; i++) { + if(i > 0) { b.append(" "); } - b.append(args[i] == null?"":args[i].val()); + b.append(args[i] == null ? "" : args[i].val()); } return new CString(b.toString(), t); } -// @Override -// public Construct execs(Target t, Environment env, Script parent, ParseTree... nodes) { -// StringBuilder b = new StringBuilder(); -// boolean centry = false; -// Construct key = null; -// for (int i = 0; i < nodes.length; i++) { -// Construct c = parent.seval(nodes[i], env); -// if (i == 0) { -// if (c instanceof CLabel) { -// key = c; -// centry = true; -// break; -// } -// } -// if (!centry) { -// if (i > 1 || i > 0 && !centry) { -// b.append(" "); -// } -// b.append(c.val()); -// } -// } -// if (centry) { -// Construct value; -// if (nodes.length > 2) { -// //it's a string -// StringBuilder c = new StringBuilder(); -// for (int i = 1; i < nodes.length; i++) { -// Construct d = parent.seval(nodes[i], env); -// if (i > 1) { -// c.append(" "); -// } -// c.append(d.val()); -// } -// value = new CString(c.toString(), t); -// } else { -// value = parent.seval(nodes[1], env); -// } -// return new CEntry(key, value, t); -// } else { -// return new CString(b.toString(), t); -// } -// } @Override public String docs() { return "string {var1, [var2...]} Concatenates any number of arguments together, but puts a space between elements"; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{}; } @Override @@ -295,8 +199,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_0_1; + public MSVersion since() { + return MSVersion.V3_0_1; } @Override @@ -305,22 +209,63 @@ public Boolean runAsync() { } @Override - public ParseTree optimizeDynamic(Target t, List children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { + public ParseTree optimizeDynamic(Target t, Environment env, + Set> envs, + List children, FileOptions fileOptions) + throws ConfigCompileException, ConfigRuntimeException { OptimizationUtilities.pullUpLikeFunctions(children, this.getName()); + for(ParseTree child : children) { + if(child.getData() instanceof CLabel) { + throw new ConfigCompileException("Invalid use of concatenation with label", child.getTarget()); + } + } //Remove empty g or p children Iterator it = children.iterator(); - while(it.hasNext()){ + while(it.hasNext()) { ParseTree n = it.next(); - if(n.getData() instanceof CFunction && - (g.equals(n.getData().val()) - || p.equals(n.getData().val())) - && !n.hasChildren()){ + if(n.getData() instanceof CFunction + && (g.NAME.equals(n.getData().val()) + || p.NAME.equals(n.getData().val())) + && !n.hasChildren()) { it.remove(); } } - //If we don't have any children, remove us as well - if(children.size() == 1){ - return children.get(0); + // We have to turn off constant optimization because sconcat is a strange construct that does have some special + // compiler significance, especially if we end up being optimized out. So here, we will check to see if we are fully + // constant, and combine the constant values, but taking care not to do so with CKeywords or CLabels, which are otherwise constant. + // We start at 1, because if we only have one child, we want to skip ahead. + for(int i = 1; i < children.size(); i++) { + ParseTree child = children.get(i); + if(child.isConst() && !(child.getData() instanceof CKeyword) && !(child.getData() instanceof CLabel)) { + if(children.get(i - 1).isConst() && !(children.get(i - 1).getData() instanceof CKeyword)) { + // Combine these two into one, and replace i - 1, and remove i + String s1 = children.get(i - 1).getData().val(); + String s2 = child.getData().val(); + children.set(i - 1, new ParseTree(new CString(s1 + " " + s2, t), fileOptions)); + children.remove(i); + i--; + } + } + } + //If we don't have any children, remove us as well, though we still have to check if it's a keword. + if(children.size() == 1) { + ParseTree child = children.get(0); + if(child.getData() instanceof CKeyword || child.getData() instanceof CLabel) { + return child; + } else { + // sconcat only returns a string (except in the special case above) so we need to + // return the string value if it's not already a string + try { + if(InstanceofUtil.isInstanceof(child.getData(), CString.TYPE, env)) { + return child; + } + } catch (IllegalArgumentException ex) { + // Ignored, we'll just toString it, because it's an unknown type. + } + ParseTree node = new ParseTree(new CFunction(_string.NAME, t), fileOptions); + node.addChild(child); + return node; + } } return null; } @@ -328,14 +273,13 @@ public ParseTree optimizeDynamic(Target t, List children, FileOptions @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ - new ExampleScript("Functional usage", "sconcat('1', '2', '3', '4')"), - new ExampleScript("Implied usage, due to no operators", "'1' '2' '3' '4'"),}; + new ExampleScript("Functional usage", "sconcat('1', '2', '3', '4')"), + new ExampleScript("Implied usage, due to no operators", "'1' '2' '3' '4'")}; } @Override public Set optimizationOptions() { return EnumSet.of( - OptimizationOption.CONSTANT_OFFLINE, OptimizationOption.CACHE_RETURN, OptimizationOption.OPTIMIZE_DYNAMIC); } @@ -344,9 +288,11 @@ public Set optimizationOptions() { @api public static class replace extends AbstractFunction implements Optimizable { + public static final String NAME = "replace"; + @Override public String getName() { - return "replace"; + return NAME; } @Override @@ -355,7 +301,7 @@ public Integer[] numArgs() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws CancelCommandException, ConfigRuntimeException { String thing = args[0].val(); String what = args[1].val(); String that = args[2].val(); @@ -368,8 +314,8 @@ public String docs() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{}; } @Override @@ -378,8 +324,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_0_1; + public MSVersion since() { + return MSVersion.V3_0_1; } @Override @@ -390,8 +336,8 @@ public Boolean runAsync() { @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ - new ExampleScript("", "replace('Where in the world is Carmen Sandiego?', 'Carmen Sandiego', 'Waldo')"), - new ExampleScript("No match found", "replace('The same thing', 'not found', '404')"),}; + new ExampleScript("", "replace('Where in the world is Carmen Sandiego?', 'Carmen Sandiego', 'Waldo')"), + new ExampleScript("No match found", "replace('The same thing', 'not found', '404')")}; } @Override @@ -416,27 +362,27 @@ public Integer[] numArgs() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws CancelCommandException, ConfigRuntimeException { String string = args[0].val(); boolean useAdvanced = false; - if(args.length >= 2){ - useAdvanced = Static.getBoolean(args[1]); + if(args.length >= 2) { + useAdvanced = ArgumentValidation.getBoolean(args[1], t); } - List a = new ArrayList<>(); - if(!useAdvanced){ + List a = new ArrayList<>(); + if(!useAdvanced) { String[] sa = string.split(" "); - for (String s : sa) { - if (!s.trim().isEmpty()) { + for(String s : sa) { + if(!s.trim().isEmpty()) { a.add(new CString(s.trim(), t)); } } } else { - for(String s : StringUtils.ArgParser(string)){ + for(String s : StringUtils.ArgParser(string)) { a.add(new CString(s.trim(), t)); } } - Construct[] csa = new Construct[a.size()]; - for (int i = 0; i < a.size(); i++) { + Mixed[] csa = new Mixed[a.size()]; + for(int i = 0; i < a.size(); i++) { csa[i] = a.get(i); } return new CArray(t, csa); @@ -451,8 +397,8 @@ public String docs() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{}; } @Override @@ -461,8 +407,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_0_1; + public MSVersion since() { + return MSVersion.V3_0_1; } @Override @@ -473,9 +419,12 @@ public Boolean runAsync() { @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ - new ExampleScript("Demonstrates basic usage", "parse_args('This turns into 5 arguments')"), - new ExampleScript("Demonstrates usage with extra spaces", "parse_args('This trims extra spaces')") - }; + new ExampleScript("Demonstrates basic usage", "parse_args('This turns into 5 arguments')"), + new ExampleScript("Demonstrates usage with extra spaces", + "parse_args('This trims extra spaces')"), + new ExampleScript("With the advanced mode (escapes are also supported with \\, for instance \\'", + "parse_args('This supports \"quoted arguments\"', true)") + }; } @Override @@ -505,8 +454,8 @@ public String docs() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{}; } @Override @@ -515,12 +464,12 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_0_1; + public MSVersion since() { + return MSVersion.V3_0_1; } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws CancelCommandException, ConfigRuntimeException { return new CString(args[0].val().trim(), args[0].getTarget()); } @@ -532,7 +481,7 @@ public Boolean runAsync() { @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ - new ExampleScript("", "'->' . trim(' <- spaces -> ') . '<-'"),}; + new ExampleScript("", "'->' . trim(' <- spaces -> ') . '<-'")}; } @Override @@ -562,8 +511,8 @@ public String docs() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{}; } @Override @@ -572,12 +521,12 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws CancelCommandException, ConfigRuntimeException { return new CString(StringUtils.trimRight(args[0].val()), args[0].getTarget()); } @@ -589,7 +538,7 @@ public Boolean runAsync() { @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ - new ExampleScript("", "'->' . trimr(' <- spaces -> ') . '<-'"),}; + new ExampleScript("", "'->' . trimr(' <- spaces -> ') . '<-'")}; } @Override @@ -619,8 +568,8 @@ public String docs() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{}; } @Override @@ -629,12 +578,12 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws CancelCommandException, ConfigRuntimeException { return new CString(StringUtils.trimLeft(args[0].val()), t); } @@ -646,7 +595,7 @@ public Boolean runAsync() { @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ - new ExampleScript("", "'->' . triml(' <- spaces -> ') . '<-'"),}; + new ExampleScript("", "'->' . triml(' <- spaces -> ') . '<-'")}; } @Override @@ -672,12 +621,13 @@ public Integer[] numArgs() { @Override public String docs() { - return "int {str | array} Returns the character length of str, if the value is castable to a string, or the length of the array, if an array is given"; + return "int {str | Sizeable} Returns the character length of str, if the value is castable to a string, or" + + " the length of the ms.lang.Sizeable object, if an array or other Sizeable object is given."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{}; } @Override @@ -686,8 +636,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_1_2; + public MSVersion since() { + return MSVersion.V3_1_2; } @Override @@ -696,9 +646,9 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { - if (args[0] instanceof Sizable) { - return new CInt(((Sizable) args[0]).size(), t); + public Mixed exec(Target t, Environment env, Mixed... args) throws CancelCommandException, ConfigRuntimeException { + if(args[0].isInstanceOf(Sizeable.TYPE)) { + return new CInt(((Sizeable) args[0]).size(), t); } else { return new CInt(args[0].val().length(), t); } @@ -707,8 +657,8 @@ public Construct exec(Target t, Environment env, Construct... args) throws Cance @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ - new ExampleScript("Strings", "length('this is a string')"), - new ExampleScript("Arrays", "length(array('1', 2, '3', 4))"),}; + new ExampleScript("Strings", "length('this is a string')"), + new ExampleScript("Arrays", "length(array('1', 2, '3', 4))")}; } @Override @@ -738,8 +688,8 @@ public String docs() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{CREFormatException.class}; } @Override @@ -748,8 +698,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_1_2; + public MSVersion since() { + return MSVersion.V3_1_2; } @Override @@ -758,16 +708,20 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws CancelCommandException, ConfigRuntimeException { + if(!(args[0].isInstanceOf(CString.TYPE))) { + throw new CREFormatException(this.getName() + " expects a string as first argument, but type " + + args[0].typeof() + " was found.", t); + } return new CString(args[0].val().toUpperCase(), t); } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ - new ExampleScript("", "to_upper('uppercase')"), - new ExampleScript("", "to_upper('MiXeD cAsE')"), - new ExampleScript("", "to_upper('Numbers (and SYMBOLS: 25)')"),}; + new ExampleScript("", "to_upper('uppercase')"), + new ExampleScript("", "to_upper('MiXeD cAsE')"), + new ExampleScript("", "to_upper('Numbers (and SYMBOLS: 25)')")}; } @Override @@ -797,8 +751,8 @@ public String docs() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{CREFormatException.class}; } @Override @@ -807,8 +761,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_1_2; + public MSVersion since() { + return MSVersion.V3_1_2; } @Override @@ -817,16 +771,20 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws CancelCommandException, ConfigRuntimeException { + if(!(args[0].isInstanceOf(CString.TYPE))) { + throw new CREFormatException(this.getName() + " expects a string as first argument, but type " + + args[0].typeof() + " was found.", t); + } return new CString(args[0].val().toLowerCase(), t); } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ - new ExampleScript("", "to_lower('LOWERCASE')"), - new ExampleScript("", "to_lower('MiXeD cAsE')"), - new ExampleScript("", "to_lower('Numbers (and SYMBOLS: 25)')"),}; + new ExampleScript("", "to_lower('LOWERCASE')"), + new ExampleScript("", "to_lower('MiXeD cAsE')"), + new ExampleScript("", "to_lower('Numbers (and SYMBOLS: 25)')")}; } @Override @@ -859,8 +817,8 @@ public String docs() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.RangeException, ExceptionType.CastException}; + public Class[] thrown() { + return new Class[]{CRERangeException.class, CRECastException.class}; } @Override @@ -869,8 +827,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_1_2; + public MSVersion since() { + return MSVersion.V3_1_2; } @Override @@ -879,30 +837,29 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws CancelCommandException, ConfigRuntimeException { try { String s = args[0].val(); - int begin = Static.getInt32(args[1], t); + int begin = ArgumentValidation.getInt32(args[1], t); int end; - if (args.length == 3) { - end = Static.getInt32(args[2], t); + if(args.length == 3) { + end = ArgumentValidation.getInt32(args[2], t); } else { end = s.length(); } return new CString(s.substring(begin, end), t); } catch (IndexOutOfBoundsException e) { - throw new ConfigRuntimeException("The indices given are not valid for string '" + args[0].val() + "'", - ExceptionType.RangeException, t); + throw new CRERangeException("The indices given are not valid for string '" + args[0].val() + "'", t); } } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ - new ExampleScript("", "substr('hamburger', 4, 8)"), - new ExampleScript("", "substr('smiles', 1, 5)"), - new ExampleScript("", "substr('lightning', 5)"), - new ExampleScript("If the indexes are too large", "assign(@big, 25)\nsubstr('small', @big)"),}; + new ExampleScript("", "substr('hamburger', 4, 8)"), + new ExampleScript("", "substr('smiles', 1, 5)"), + new ExampleScript("", "substr('lightning', 5)"), + new ExampleScript("If the indexes are too large", "assign(@big, 25)\nsubstr('small', @big)")}; } @Override @@ -914,38 +871,58 @@ public Set optimizationOptions() { } @api - public static class string_position extends AbstractFunction implements Optimizable { + @seealso({StringHandling.string_ends_with.class}) + public static class string_starts_with extends AbstractFunction implements Optimizable { @Override - public String getName() { - return "string_position"; + public Class[] thrown() { + return new Class[]{CRENullPointerException.class}; } @Override - public Integer[] numArgs() { - return new Integer[]{2}; + public String getName() { + return "string_starts_with"; } @Override public String docs() { - return "int {haystack, needle} Finds the numeric position of the first occurence of needle in haystack. haystack is the string" - + " to search in, and needle is the string to search with. Returns the position of the needle (starting with 0) or -1 if" - + " the string is not found at all."; + return "boolean {teststring, keyword} Determines if the provided teststring starts with the provided keyword. This could be used to find" + + " the prefix of a line, for instance. Note that this will cast both arguments to strings. This means that the boolean" + + " true will match the string 'true' or the integer 1 will match the string '1'. If an empty string is provided for" + + " the keyword, it will always return true."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.NullPointerException}; + public Mixed exec(Target t, Environment env, Mixed... args) throws CancelCommandException, ConfigRuntimeException { + Static.AssertNonCNull(t, args); + + String teststring = args[0].val(); + String keyword = args[1].val(); + boolean ret = teststring.startsWith(keyword); + + return CBoolean.get(ret); } @Override - public boolean isRestricted() { - return false; + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("Basic usage", "string_starts_with('[ERROR] Bad message here!', '[ERROR]')"), + new ExampleScript("Basic usage", "string_starts_with('Potato with cheese', 'cheese')")}; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{2}; } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_2; + } + + @Override + public boolean isRestricted() { + return false; } @Override @@ -954,54 +931,182 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { - String haystack = args[0].nval(); - String needle = args[1].nval(); + public Set optimizationOptions() { + return EnumSet.of( + OptimizationOption.CONSTANT_OFFLINE, + OptimizationOption.CACHE_RETURN, + OptimizationOption.NO_SIDE_EFFECTS); + } + } + + @api + @seealso({StringHandling.string_starts_with.class}) + public static class string_ends_with extends AbstractFunction implements Optimizable { + + @Override + public Class[] thrown() { + return new Class[]{CRENullPointerException.class}; + } + + @Override + public String getName() { + return "string_ends_with"; + } + + @Override + public String docs() { + return "boolean {teststring, keyword} Determines if the provided teststring ends with the provided keyword. Note that this will" + + " cast both arguments to strings. This means that the boolean true will match the string 'true' or the integer 1 will" + + " match the string '1'. If an empty string is provided for the keyword, it will always return true."; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws CancelCommandException, ConfigRuntimeException { Static.AssertNonCNull(t, args); - return new CInt(haystack.indexOf(needle), t); + + String teststring = args[0].val(); + String keyword = args[1].val(); + boolean ret = teststring.endsWith(keyword); + + return CBoolean.get(ret); } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ - new ExampleScript("Basic usage", "string_position('this is the string', 'string')"), - new ExampleScript("String not found", "string_position('Where\\'s Waldo?', 'Dunno.')"),}; + new ExampleScript("Basic usage", "string_ends_with('[ERROR] Bad message here!!', '!')"), + new ExampleScript("Basic usage", "string_ends_with('Spaghetti and cheese', 'Spaghetti')")}; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{2}; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_2; + } + + @Override + public boolean isRestricted() { + return false; + } + + @Override + public Boolean runAsync() { + return null; } @Override public Set optimizationOptions() { return EnumSet.of( OptimizationOption.CONSTANT_OFFLINE, - OptimizationOption.CACHE_RETURN); + OptimizationOption.CACHE_RETURN, + OptimizationOption.NO_SIDE_EFFECTS); } } @api - @seealso({ArrayHandling.array_implode.class}) - public static class split extends AbstractFunction implements Optimizable { + public static class char_is_uppercase extends AbstractFunction implements Optimizable { + + @Override + public Class[] thrown() { + return new Class[]{CREFormatException.class, CRECastException.class}; + } @Override public String getName() { - return "split"; + return "char_is_uppercase"; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws CancelCommandException, ConfigRuntimeException { + if(args[0] instanceof CNull) { + throw new CRECastException(this.getName() + " expects a string as first argument, but found null.", t); + } + String text = args[0].val(); + // Enforce the fact we are only taking the first character here + // Do not let the user pass an entire string (or an empty string, d'oh). Only a single character. + if(text.length() != 1) { + throw new CREFormatException("Got \"" + text + "\" instead of expected character.", t); + } + + char check = text.charAt(0); + + if(!Character.isLetter(check)) { + throw new CREFormatException("Got \"" + text + "\" instead of alphabetical character.", t); + } + + return CBoolean.get(Character.isUpperCase(check)); + } + + @Override + public Set optimizationOptions() { + return EnumSet.of( + OptimizationOption.CONSTANT_OFFLINE, + OptimizationOption.CACHE_RETURN, + OptimizationOption.NO_SIDE_EFFECTS); } @Override public Integer[] numArgs() { - return new Integer[]{2, 3}; + return new Integer[]{1}; } @Override public String docs() { - return "array {split, string, [limit]} Splits a string into parts, using the split as the index. Though it can be used in every single case" - + " you would use reg_split, this does not use regex," - + " and therefore can take a literal split expression instead of needing an escaped regex, and *may* perform better than the" - + " regex versions, as it uses an optimized tokenizer split, instead of Java regex. Limit defaults to infinity, but if set, only" - + " that number of splits will occur."; + return "boolean {char} Determines if the character provided is uppercase or not. The string must be exactly 1 character long and" + + " a letter, otherwise a FormatException is thrown."; + } + + @Override + public boolean isRestricted() { + return false; + } + + @Override + public Boolean runAsync() { + return null; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_2; + } + + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("Basic usage", "char_is_uppercase('a')"), + new ExampleScript("Basic usage", "char_is_uppercase('D')")}; + } + } + + @api + @seealso({string_contains.class}) + public static class string_position extends AbstractFunction implements Optimizable { + + @Override + public String getName() { + return "string_position"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{2}; + } + + @Override + public String docs() { + return "int {haystack, needle} Finds the numeric position of the first occurrence of needle in haystack. haystack is the string" + + " to search in, and needle is the string to search with. Returns the position of the needle (starting with 0) or -1 if" + + " the string is not found at all."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException}; + public Class[] thrown() { + return new Class[]{CRENullPointerException.class}; } @Override @@ -1010,8 +1115,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } @Override @@ -1020,69 +1125,35 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { - //http://stackoverflow.com/questions/2667015/is-regex-too-slow-real-life-examples-where-simple-non-regex-alternative-is-bett - //According to this, regex isn't necessarily slower, but we do want to escape the pattern either way, since the main advantage - //of this function is convenience (not speed) however, if we can eek out a little extra speed too, excellent. - CArray array = new CArray(t); - String split = args[0].val(); - String string = args[1].val(); - int limit = Integer.MAX_VALUE; - if(args.length >= 3){ - limit = Static.getInt32(args[2], t); - } - int sp = 0; - if(split.length() == 0){ - //Empty string, so special case. - for(int i = 0; i < string.length(); i++){ - array.push(new CString(string.charAt(i), t)); - } - return array; - } - int splitsFound = 0; - for (int i = 0; i < string.length() - split.length() && splitsFound < limit; i++) { - if (string.substring(i, i + split.length()).equals(split)) { - //Split point found - splitsFound++; - array.push(new CString(string.substring(sp, i), t)); - sp = i + split.length(); - i += split.length() - 1; - } - } - if (sp != 0) { - array.push(new CString(string.substring(sp, string.length()), t)); - } else { - //It was not found anywhere, so put the whole string in - array.push(args[1]); - } - return array; + public Mixed exec(Target t, Environment env, Mixed... args) throws CancelCommandException, ConfigRuntimeException { + Static.AssertNonCNull(t, args); + String haystack = args[0].val(); + String needle = args[1].val(); + return new CInt(haystack.indexOf(needle), t); } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ - new ExampleScript("Simple split on one character. Note that unlike reg_split, no escaping is needed on the period.", "split('.', '1.2.3.4.5')"), - new ExampleScript("Split with multiple characters", "split('ab', 'aaabaaabaaabaa')"), - new ExampleScript("Split all characters", "split('', 'abcdefg')"), - new ExampleScript("Split with limit", "split('|', 'this|is|a|limit', 1)") - }; + new ExampleScript("Basic usage", "string_position('this is the string', 'string')"), + new ExampleScript("String not found", "string_position('Where\\'s Waldo?', 'Dunno.')")}; } @Override public Set optimizationOptions() { return EnumSet.of( + OptimizationOption.CONSTANT_OFFLINE, OptimizationOption.CACHE_RETURN); } } @api - public static class sprintf extends AbstractFunction implements Optimizable { + @seealso({string_position.class, string_contains_ic.class}) + public static class string_contains extends CompositeFunction implements Optimizable { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.FormatException, - ExceptionType.InsufficientArgumentsException, - ExceptionType.CastException}; + public Class[] thrown() { + return new Class[]{CRENullPointerException.class}; } @Override @@ -1096,73 +1167,316 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - if (args.length == 0) { - throw new ConfigRuntimeException(getName() + " expects 1 or more argument", ExceptionType.InsufficientArgumentsException, t); - } - String formatString = args[0].val(); - Object[] params = new Object[args.length - 1]; - List parsed; - try{ - parsed = parse(formatString, t); - } catch(IllegalFormatException e){ - throw new ConfigRuntimeException(e.getMessage(), ExceptionType.FormatException, t); - } - if (requiredArgs(parsed) != args.length - 1) { - throw new ConfigRuntimeException("The specified format string: \"" + formatString + "\"" - + " expects " + requiredArgs(parsed) + " argument, but " + (args.length - 1) + " were provided.", ExceptionType.InsufficientArgumentsException, t); - } + public String getName() { + return "string_contains"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{2}; + } + + @Override + public String docs() { + return "boolean {haystack, needle} Returns true if the string needle is found anywhere within the string haystack." + + " This is functionally equivalent to string_position(@haystack, @needle) != -1, but is generally clearer."; + } + + @Override + public Version since() { + return MSVersion.V3_3_2; + } + + @Override + public Set optimizationOptions() { + return EnumSet.of(OptimizationOption.CONSTANT_OFFLINE, + OptimizationOption.CACHE_RETURN, + OptimizationOption.NO_SIDE_EFFECTS); + } + + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("Basic usage", "string_contains('haystack', 'hay');"), + new ExampleScript("Not found", "string_contains('abcd', 'wxyz');") + }; + } + + @Override + protected String script() { + return getBundledCode(); + } + + } + + @api + @seealso({string_contains.class}) + public static class string_contains_ic extends CompositeFunction implements Optimizable { + + @Override + public Class[] thrown() { + return new Class[]{CRENullPointerException.class, CREFormatException.class}; + } + + @Override + public boolean isRestricted() { + return false; + } + + @Override + public Boolean runAsync() { + return null; + } + + @Override + public String getName() { + return "string_contains_ic"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{2}; + } + + @Override + public String docs() { + return "boolean {haystack, needle} Returns true if the string needle is found anywhere within the string haystack (while ignoring case)." + + " This is functionally equivalent to string_position(to_lower(@haystack), to_lower(@needle)) != -1, but is generally clearer."; + } + + @Override + public Version since() { + return MSVersion.V3_3_4; + } + + @Override + public Set optimizationOptions() { + return EnumSet.of(OptimizationOption.CONSTANT_OFFLINE, + OptimizationOption.CACHE_RETURN, + OptimizationOption.NO_SIDE_EFFECTS); + } + + @Override + protected String script() { + return getBundledCode(); + } + + } + + @api + @seealso({ArrayHandling.array_implode.class}) + public static class split extends AbstractFunction implements Optimizable { + + public static final String NAME = "split"; + + @Override + public String getName() { + return NAME; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{2, 3}; + } + + @Override + public String docs() { + return "array {split, string, [limit]} Splits a string into parts, using the split as the index. Though it can be used in every single case" + + " you would use reg_split, this does not use regex," + + " and therefore can take a literal split expression instead of needing an escaped regex, and *may* perform better than the" + + " regex versions, as it uses an optimized tokenizer split, instead of Java regex. Limit defaults to infinity, but if set, only" + + " that number of splits will occur."; + } + + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class}; + } + + @Override + public boolean isRestricted() { + return false; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_1; + } + + @Override + public Boolean runAsync() { + return null; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws CancelCommandException, ConfigRuntimeException { + //http://stackoverflow.com/questions/2667015/is-regex-too-slow-real-life-examples-where-simple-non-regex-alternative-is-bett + //According to this, regex isn't necessarily slower, but we do want to escape the pattern either way, since the main advantage + //of this function is convenience (not speed) however, if we can eek out a little extra speed too, excellent. + CArray array = new CArray(t); + String split = args[0].val(); + String string = args[1].val(); + int limit = Integer.MAX_VALUE; + if(args.length >= 3) { + limit = ArgumentValidation.getInt32(args[2], t); + } + int sp = 0; + if(split.length() == 0) { + //Empty string, so special case. + for(int i = 0; i < string.length(); i++) { + array.push(new CString(string.charAt(i), t), t); + } + return array; + } + int splitsFound = 0; + for(int i = 0; i <= string.length() - split.length() && splitsFound < limit; i++) { + if(string.substring(i, i + split.length()).equals(split)) { + //Split point found + splitsFound++; + array.push(new CString(string.substring(sp, i), t), t); + sp = i + split.length(); + i += split.length() - 1; + } + } + if(sp != 0) { + array.push(new CString(string.substring(sp, string.length()), t), t); + } else { + //It was not found anywhere, so put the whole string in + array.push(args[1], t); + } + return array; + } + + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("Simple split on one character. Note that unlike reg_split, no escaping is needed on the period.", "split('.', '1.2.3.4.5')"), + new ExampleScript("Split with multiple characters", "split('ab', 'aaabaaabaaabaa')"), + new ExampleScript("Split all characters", "split('', 'abcdefg')"), + new ExampleScript("Split with limit", "split('|', 'this|is|a|limit', 1)") + }; + } + + @Override + public Set optimizationOptions() { + return EnumSet.of( + OptimizationOption.CACHE_RETURN); + } + } + + @api + @seealso({sprintf.class, Meta.get_locales.class}) + public static class lsprintf extends AbstractFunction implements Optimizable { + + @Override + public Class[] thrown() { + return new Class[]{CREFormatException.class, + CREInsufficientArgumentsException.class, + CRECastException.class}; + } + + @Override + public boolean isRestricted() { + return false; + } + + @Override + public Boolean runAsync() { + return null; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + if(args.length < 2) { + throw new CREInsufficientArgumentsException(getName() + " expects 2 or more arguments", t); + } + int numArgs = args.length; + + // Get the Locale. + Locale locale = null; + String countryCode = Construct.nval(args[0]); + if(countryCode == null) { + locale = Locale.getDefault(); + } else { + locale = Static.GetLocale(countryCode); + } + if(locale == null) { + throw new CREFormatException("The given locale was not found on your system: " + + countryCode, t); + } + + // Handle the formatting. + String formatString = args[1].val(); + List parsed; + try { + try { + parsed = parse(formatString, t); + } catch (InaccessibleObjectException ex) { + throw new CREError(ADD_OPENS_MESSAGE, t); + } + } catch (IllegalFormatException e) { + throw new CREFormatException(e.getMessage(), t); + } + + List flattenedArgs = new ArrayList<>(); + if(numArgs == 3 && args[2].isInstanceOf(CArray.TYPE)) { + if(((CArray) args[2]).inAssociativeMode()) { + throw new CRECastException("If the second argument to " + getName() + " is an array, it may not be associative.", t); + } else { + for(int i = 0; i < ((CArray) args[2]).size(); i++) { + flattenedArgs.add(((CArray) args[2]).get(i, t)); + } + } + } else { + for(int i = 2; i < numArgs; i++) { + flattenedArgs.add(args[i]); + } + } + + int requiredArgs = requiredArgs(parsed); + if(requiredArgs != flattenedArgs.size()) { + throw new CREInsufficientArgumentsException("The specified format string: \"" + formatString + "\"" + + " expects " + requiredArgs + " argument(s)," + + " but " + flattenedArgs.size() + " were provided.", t); + } - List flattenedArgs = new ArrayList(); - if (args.length == 2 && args[1] instanceof CArray) { - if (((CArray) args[1]).inAssociativeMode()) { - throw new ConfigRuntimeException("If the second argument to " + getName() + " is an array, it may not be associative.", ExceptionType.CastException, t); - } else { - for (int i = 0; i < ((CArray) args[1]).size(); i++) { - flattenedArgs.add(((CArray) args[1]).get(i, t)); - } - } - } else { - for (int i = 1; i < args.length; i++) { - flattenedArgs.add(args[i]); - } - } //Now figure out how to cast things, now that we know our argument numbers will match up - for (int i = 0; i < requiredArgs(parsed); i++) { - Construct arg = flattenedArgs.get(i); + Object[] params = new Object[flattenedArgs.size()]; + for(int i = 0; i < requiredArgs; i++) { + Mixed arg = flattenedArgs.get(i); FormatString fs = parsed.get(i); Character c = fs.getExpectedType(); params[i] = convertArgument(arg, c, i, t); } //Ok, done. - return new CString(String.format(formatString, params), t); + return new CString(String.format(locale, formatString, params), t); } - private Object convertArgument(Construct arg, Character c, int i, Target t) { + private Object convertArgument(Mixed arg, Character c, int i, Target t) { Object o; - if (Conversion.isValid(c)) { - if (c == 't' || c == 'T') { + if(Conversion.isValid(c)) { + if(c == 't' || c == 'T') { //Datetime, parse as long - o = Static.getInt(arg, t); - } else if (Conversion.isCharacter(c)) { + o = ArgumentValidation.getInt(arg, t); + } else if(Conversion.isCharacter(c)) { //Character, parse as string, and verify it's of length 1 String s = arg.val(); - if (s.length() > 1) { - throw new Exceptions.FormatException("Expecting a string of length one in argument " + (i + 1) + " in " + getName() - + "but \"" + s + "\" was found instead.", t); + if(s.length() > 1) { + throw new CREFormatException("Expecting a string of length one in argument " + (i + 1) + + " in " + this.getName() + "but \"" + s + "\" was found instead.", t); } o = s.charAt(0); - } else if (Conversion.isFloat(c)) { + } else if(Conversion.isFloat(c)) { //Float, parse as double - o = Static.getDouble(arg, t); - } else if (Conversion.isInteger(c)) { + o = ArgumentValidation.getDouble(arg, t); + } else if(Conversion.isInteger(c)) { //Integer, parse as long - o = Static.getInt(arg, t); + o = ArgumentValidation.getInt(arg, t); } else { //Further processing is needed - if (c == Conversion.BOOLEAN || c == Conversion.BOOLEAN_UPPER) { + if(c == Conversion.BOOLEAN || c == Conversion.BOOLEAN_UPPER) { //Boolean, parse as such - o = Static.getBoolean(arg); + o = ArgumentValidation.getBoolean(arg, t); } else { //Else it's either a string or a hash code, in which case //we will treat it as a string anyways @@ -1200,11 +1514,11 @@ private static class Conversion { // java.util.Date, java.util.Calendar, long static final char DATE_TIME = 't'; static final char DATE_TIME_UPPER = 'T'; - // if (arg.TYPE != boolean) return boolean - // if (arg != null) return true; else return false; + // if(arg.TYPE != boolean) return boolean + // if(arg != null) return true; else return false; static final char BOOLEAN = 'b'; static final char BOOLEAN_UPPER = 'B'; - // if (arg instanceof Formattable) arg.formatTo() + // if(arg instanceof Formattable) arg.formatTo() // else arg.toString(); static final char STRING = 's'; static final char STRING_UPPER = 'S'; @@ -1221,7 +1535,7 @@ static boolean isValid(char c) { // Returns true iff the Conversion is applicable to all objects. static boolean isGeneral(char c) { - switch (c) { + switch(c) { case BOOLEAN: case BOOLEAN_UPPER: case STRING: @@ -1236,7 +1550,7 @@ static boolean isGeneral(char c) { // Returns true iff the Conversion is applicable to character. static boolean isCharacter(char c) { - switch (c) { + switch(c) { case CHARACTER: case CHARACTER_UPPER: return true; @@ -1247,7 +1561,7 @@ static boolean isCharacter(char c) { // Returns true iff the Conversion is an integer type. static boolean isInteger(char c) { - switch (c) { + switch(c) { case DECIMAL_INTEGER: case OCTAL_INTEGER: case HEXADECIMAL_INTEGER: @@ -1260,7 +1574,7 @@ static boolean isInteger(char c) { // Returns true iff the Conversion is a floating-point type. static boolean isFloat(char c) { - switch (c) { + switch(c) { case SCIENTIFIC: case SCIENTIFIC_UPPER: case GENERAL: @@ -1276,7 +1590,7 @@ static boolean isFloat(char c) { // Returns true iff the Conversion does not require an argument static boolean isText(char c) { - switch (c) { + switch(c) { case LINE_SEPARATOR: case PERCENT_SIGN: return true; @@ -1286,48 +1600,69 @@ static boolean isText(char c) { } } + private static final String ADD_OPENS_MESSAGE = "An error occured when running this script, due to misconfigured" + + " Java startup parameters. Please add the output of the 'cmdline-args' to your java startup command" + + " to enable full functionality. Please see the installation guide for more details."; + @Override - public ParseTree optimizeDynamic(Target t, List children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { - if (children.isEmpty()) { - throw new ConfigCompileException(getName() + " expects 1 or more argument", t); - } else if (children.get(0).isConst()) { - ParseTree me = new ParseTree(new CFunction(getName(), t), children.get(0).getFileOptions()); + public ParseTree optimizeDynamic(Target t, Environment env, + Set> envs, + List children, FileOptions fileOptions) + throws ConfigCompileException, ConfigRuntimeException { + if(children.size() < 2) { + throw new ConfigCompileException(getName() + " expects 2 or more argument", t); + } + if(children.get(0).isConst()) { + String locale = Construct.nval(children.get(0).getData()); + if(locale != null && Static.GetLocale(locale) == null) { + throw new ConfigCompileException("The locale " + locale + " could not be found on this system", t); + } + } + if(children.get(1).isConst()) { + ParseTree me = new ParseTree(new CFunction(getName(), t), children.get(1).getFileOptions()); me.setChildren(children); me.setOptimized(true); //After this run, we will be, anyways. - if(children.size() == 2 && children.get(1).getData() instanceof CFunction && ((CFunction)children.get(1).getData()).getFunction().getName().equals(new DataHandling.array().getName())){ + if(children.size() == 3 && children.get(2).getData() instanceof CFunction + && ((CFunction) children.get(2).getData()).hasFunction() + && ((CFunction) children.get(2).getData()).getFunction().getName().equals(array.NAME)) { //Normally we can't do anything with a hardcoded array, it's considered dynamic. But in this case, we can at least pull up the arguments, //because the array's size is constant, even if the arguments in it aren't. - ParseTree array = children.get(1); - children.remove(1); + ParseTree array = children.get(2); + children.remove(2); boolean allIndexesStatic = true; - for(int i = 0; i < array.numberOfChildren(); i++){ + for(int i = 0; i < array.numberOfChildren(); i++) { ParseTree child = array.getChildAt(i); - if(child.isDynamic()){ + if(child.isDynamic()) { allIndexesStatic = false; } children.add(child); } - if(allIndexesStatic){ + if(allIndexesStatic) { me.hasBeenMadeStatic(true); } } //We can check the format string and make sure it doesn't throw an IllegalFormatException. try { - List parsed = parse(children.get(0).getData().val(), t); - if (requiredArgs(parsed) != children.size() - 1) { - throw new ConfigRuntimeException("The specified format string: \"" + children.get(0).getData().val() + "\"" - + " expects " + requiredArgs(parsed) + " argument, but " + (children.size() - 1) + " were provided.", ExceptionType.InsufficientArgumentsException, t); + List parsed; + try { + parsed = parse(children.get(1).getData().val(), t); + } catch (InaccessibleObjectException ex) { + throw new CREError(ADD_OPENS_MESSAGE, t); + } + if(requiredArgs(parsed) != children.size() - 2) { + throw new CREInsufficientArgumentsException("The specified format string: \"" + children.get(1).getData().val() + "\"" + + " expects " + requiredArgs(parsed) + " argument(s), but " + (children.size() - 2) + " were provided.", t); } //If the arguments are constant, we can actually check them too - for(int i = 1; i < children.size(); i++){ + for(int i = 2; i < children.size(); i++) { //We skip the dynamic ones, but the constant ones we can know for sure //if they are convertable or not. - if(children.get(i).isConst()){ - convertArgument(children.get(i).getData(), parsed.get(i - 1).getExpectedType(), i + 1, t); + if(children.get(i).isConst()) { + convertArgument(children.get(i).getData(), parsed.get(i - 2).getExpectedType(), i, t); } } } catch (IllegalFormatException e) { - throw new ConfigRuntimeException(e.getMessage(), ExceptionType.FormatException, t); + throw new CREFormatException(e.getMessage(), t); } return me; } else { @@ -1338,61 +1673,61 @@ public ParseTree optimizeDynamic(Target t, List children, FileOptions private static class FormatString { private Object ref; - private static final Class FormatString; - private static final Class FixedString; - private static final Class FormatSpecifier; + private static final Class FORMAT_STRING; + private static final Class FIXED_STRING; + private static final Class FORMAT_SPECIFIER; static { Class tFormatString = null; Class tFixedString = null; Class tFormatSpecifier = null; - for (Class c : Formatter.class.getDeclaredClasses()) { - if (c.getSimpleName().equals("FormatString")) { + for(Class c : Formatter.class.getDeclaredClasses()) { + if(c.getSimpleName().equals("FormatString")) { tFormatString = c; continue; } - if (c.getSimpleName().equals("FixedString")) { + if(c.getSimpleName().equals("FixedString")) { tFixedString = c; continue; } - if (c.getSimpleName().equals("FormatSpecifier")) { + if(c.getSimpleName().equals("FormatSpecifier")) { tFormatSpecifier = c; continue; } } - FormatString = tFormatString; - FixedString = tFixedString; - FormatSpecifier = tFormatSpecifier; + FORMAT_STRING = tFormatString; + FIXED_STRING = tFixedString; + FORMAT_SPECIFIER = tFormatSpecifier; } public FormatString(Object ref) { - if (ref == null) { + if(ref == null) { throw new NullPointerException(); } - if (!FormatString.isAssignableFrom(ref.getClass())) { - throw new RuntimeException("Unexpected class type. Was expecting ref to be an instance of " + FormatString.getName() + " but was " + ref.getClass().getName()); + if(!FORMAT_STRING.isAssignableFrom(ref.getClass())) { + throw new RuntimeException("Unexpected class type. Was expecting ref to be an instance of " + FORMAT_STRING.getName() + " but was " + ref.getClass().getName()); } this.ref = ref; } public Character getExpectedType() { - if (ref.getClass() == FixedString) { + if(ref.getClass() == FIXED_STRING) { return null; - } else if (ref.getClass() == FormatSpecifier) { - if(((Boolean)ReflectionUtils.get(FormatSpecifier, ref, "dt"))){ + } else if(ref.getClass() == FORMAT_SPECIFIER) { + if(((Boolean) ReflectionUtils.get(FORMAT_SPECIFIER, ref, "dt"))) { return 't'; } - return ((Character) ReflectionUtils.get(FormatSpecifier, ref, "c")); + return ((Character) ReflectionUtils.get(FORMAT_SPECIFIER, ref, "c")); } else { throw new RuntimeException("Unknown type: " + ref.getClass()); } } - public int getArgIndex(){ - return ((Integer)ReflectionUtils.get(FormatSpecifier, ref, "index")); + public int getArgIndex() { + return ((Integer) ReflectionUtils.get(FORMAT_SPECIFIER, ref, "index")); } - public boolean isFixed(){ + public boolean isFixed() { return getExpectedType() == '%' || getExpectedType() == 'n'; } } @@ -1400,36 +1735,62 @@ public boolean isFixed(){ private List parse(String format, Target t) { List list = new ArrayList(); Object parse; - try{ + try { parse = ReflectionUtils.invokeMethod(Formatter.class, new Formatter(), "parse", new Class[]{String.class}, new Object[]{format}); - } catch(ReflectionException e){ - if(e.getCause() instanceof InvocationTargetException){ + } catch (ReflectionException e) { + if(e.getCause() instanceof InvocationTargetException) { Throwable th = e.getCause().getCause(); - throw new ConfigRuntimeException("A format exception was thrown for the argument \"" + format + "\": " + th.getClass().getSimpleName() + ": " + th.getMessage(), ExceptionType.FormatException, t); + throw new CREFormatException("A format exception was thrown for the argument \"" + format + "\": " + th.getClass().getSimpleName() + ": " + th.getMessage(), t); } else { //This is unexpected throw ConfigRuntimeException.CreateUncatchableException(e.getMessage(), t); } } - int length = Array.getLength(parse); - for (int i = 0; i < length; i++) { - FormatString s = new FormatString(Array.get(parse, i)); - if (s.getExpectedType() != null) { + Arrayable a = new Arrayable(parse); + int length = a.length(); + for(int i = 0; i < length; i++) { + FormatString s = new FormatString(a.get(i)); + if(s.getExpectedType() != null) { list.add(s); } } return list; } - private int requiredArgs(List list){ + // In java 8, it's an array, in java 9+, it's an ArrayList. This class + // abstracts away the differences + private static class Arrayable { + private final Object t; + public Arrayable(Object o) { + this.t = o; + } + + public int length() { + if(t instanceof List) { + return ((List) t).size(); + } else { + return Array.getLength(t); + } + } + + public Object get(int i) { + if(t instanceof List) { + return ((List) t).get(i); + } else { + return Array.get(t, i); + } + } + } + + private int requiredArgs(List list) { Set knownIndexes = new HashSet(); int count = 0; - for(FormatString s : list){ - if(s.isFixed()){ + for(FormatString s : list) { + if(s.isFixed()) { continue; } int index = s.getArgIndex(); - if(index == 0){ + if(index == 0) { count++; } else { knownIndexes.add(index); @@ -1441,7 +1802,7 @@ private int requiredArgs(List list){ @Override public String getName() { - return "sprintf"; + return "lsprintf"; } @Override @@ -1451,17 +1812,20 @@ public Integer[] numArgs() { @Override public String docs() { - return "string {formatString, parameters... | formatString, array(parameters...)} Returns a string formatted to the" - + " given formatString specification, using the parameters passed in. The formatString should be formatted" - + " according to [http://docs.oracle.com/javase/6/docs/api/java/util/Formatter.html#syntax this standard]," + return "string {locale, formatString, parameters... | locale, formatString, array(parameters...)} Returns a string formatted to the" + + " given formatString specification, using the parameters passed in. Locale should be a string in format," + + " for instance, en_US, nl_NL, no_NO... Which locales are available depends on your system. Use" + + " null to use the system's locale." + + " The formatString should be formatted according to" + + " [http://docs.oracle.com/javase/6/docs/api/java/util/Formatter.html#syntax this standard]," + " with the caveat that the parameter types are automatically cast to the appropriate type, if possible." + " Calendar/time specifiers, (t and T) expect an integer which represents unix time, but are otherwise" + " valid. All format specifiers in the documentation are valid."; } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } @Override @@ -1469,6 +1833,67 @@ public Set optimizationOptions() { return EnumSet.of(OptimizationOption.CONSTANT_OFFLINE, OptimizationOption.OPTIMIZE_DYNAMIC); } + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("Basic usage", "lsprintf('en_US', '%d', 1)"), + new ExampleScript("Multiple arguments", "lsprintf('en_US', '%d%d', 1, '2')"), + new ExampleScript("Multiple arguments in an array", "lsprintf('en_US', '%d%d', array(1, 2))"), + new ExampleScript("Compile error, missing parameters", "lsprintf('en_US', '%d')", true), + new ExampleScript("Other formatting: float with precision (using integer)", "lsprintf('en_US', '%07.3f', 4)"), + new ExampleScript("Other formatting: float with precision (with rounding)", "lsprintf('en_US', '%07.3f', 3.4567)"), + new ExampleScript("Other formatting: float with precision in a different locale (with rounding)", "lsprintf('nl_NL', '%07.3f', 3.4567)"), + new ExampleScript("Other formatting: time", "lsprintf('en_US', '%1$tm %1$te,%1$tY', time())", ":06 13,2013"), + new ExampleScript("Literal percent sign", "lsprintf('en_US', '%'.'%') // The concatenation is to prevent the website's template system from overriding. It is not needed.", "%"), + new ExampleScript("Hexidecimal formatting", "lsprintf('en_US', '%x', 15)"), + new ExampleScript("Other formatting: character", "lsprintf('en_US', '%c', 's')"), + new ExampleScript("Other formatting: character (with capitalization)", "lsprintf('en_US', '%C', 's')"), + new ExampleScript("Other formatting: scientific notation", "lsprintf('en_US', '%e', '2345')"), + new ExampleScript("Other formatting: plain string", "lsprintf('en_US', '%s', 'plain string')"), + new ExampleScript("Other formatting: boolean", "lsprintf('en_US', '%b', 1)"), + new ExampleScript("Other formatting: boolean (with capitalization)", "lsprintf('en_US', '%B', 0)"), + new ExampleScript("Other formatting: hash code", "lsprintf('en_US', '%h', 'will be hashed')")}; + } + + } + + @api + @seealso(lsprintf.class) + public static class sprintf extends lsprintf implements Optimizable { + + @Override + public String getName() { + return "sprintf"; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_1; + } + + @Override + public String docs() { + return "string {formatString, parameters... | formatString, array(parameters...)} Returns a string formatted to the" + + " given formatString specification, using the parameters passed in. The formatString should be formatted" + + " according to [http://docs.oracle.com/javase/6/docs/api/java/util/Formatter.html#syntax this standard]," + + " with the caveat that the parameter types are automatically cast to the appropriate type, if possible." + + " Calendar/time specifiers, (t and T) expect an integer which represents unix time, but are otherwise" + + " valid. All format specifiers in the documentation are valid. This works the same as lsprintf with the" + + " locale set to \"DEFAULT\"."; + } + + @Override + public ParseTree optimizeDynamic(Target t, Environment env, + Set> envs, + List children, FileOptions fileOptions) + throws ConfigCompileException, ConfigRuntimeException { + if(children.size() < 1) { + throw new ConfigCompileException(getName() + " expects at least 1 argument", t); + } + children.add(0, new ParseTree(CNull.NULL, fileOptions)); // Add a default locale to the arguments. + return super.optimizeDynamic(t, env, envs, children, fileOptions); + } + @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ @@ -1479,7 +1904,7 @@ public ExampleScript[] examples() throws ConfigCompileException { new ExampleScript("Other formatting: float with precision (using integer)", "sprintf('%07.3f', 4)"), new ExampleScript("Other formatting: float with precision (with rounding)", "sprintf('%07.3f', 3.4567)"), new ExampleScript("Other formatting: time", "sprintf('%1$tm %1$te,%1$tY', time())", ":06 13,2013"), - new ExampleScript("Literal percent sign", "sprintf('%%')"), + new ExampleScript("Literal percent sign", "sprintf('%'.'%') // The concatenation is to prevent the website's template system from overriding. It is not needed.", "%"), new ExampleScript("Hexidecimal formatting", "sprintf('%x', 15)"), new ExampleScript("Other formatting: character", "sprintf('%c', 's')"), new ExampleScript("Other formatting: character (with capitalization)", "sprintf('%C', 's')"), @@ -1487,8 +1912,7 @@ public ExampleScript[] examples() throws ConfigCompileException { new ExampleScript("Other formatting: plain string", "sprintf('%s', 'plain string')"), new ExampleScript("Other formatting: boolean", "sprintf('%b', 1)"), new ExampleScript("Other formatting: boolean (with capitalization)", "sprintf('%B', 0)"), - new ExampleScript("Other formatting: hash code", "sprintf('%h', 'will be hashed')"), - }; + new ExampleScript("Other formatting: hash code", "sprintf('%h', 'will be hashed')")}; } } @@ -1497,8 +1921,8 @@ public ExampleScript[] examples() throws ConfigCompileException { public static class string_get_bytes extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException, ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CRECastException.class, CREFormatException.class}; } @Override @@ -1512,16 +1936,16 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { String val = args[0].val(); String encoding = "UTF-8"; - if(args.length == 2){ + if(args.length == 2) { encoding = args[1].val(); } try { return CByteArray.wrap(val.getBytes(encoding), t); } catch (UnsupportedEncodingException ex) { - throw new Exceptions.FormatException("Unknown encoding type \"" + encoding + "\"", t); + throw new CREFormatException("Unknown encoding type \"" + encoding + "\"", t); } } @@ -1543,8 +1967,8 @@ public String docs() { } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } } @@ -1553,8 +1977,8 @@ public CHVersion since() { public static class string_from_bytes extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException, ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CRECastException.class, CREFormatException.class}; } @Override @@ -1568,16 +1992,16 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - CByteArray ba = Static.getByteArray(args[0], t); + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + CByteArray ba = ArgumentValidation.getByteArray(args[0], t); String encoding = "UTF-8"; - if(args.length == 2){ + if(args.length == 2) { encoding = args[1].val(); } try { return new CString(new String(ba.asByteArrayCopy(), encoding), t); } catch (UnsupportedEncodingException ex) { - throw new Exceptions.FormatException("Unknown encoding type \"" + encoding + "\"", t); + throw new CREFormatException("Unknown encoding type \"" + encoding + "\"", t); } } @@ -1598,8 +2022,8 @@ public String docs() { } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } } @@ -1608,8 +2032,8 @@ public CHVersion since() { public static class string_append extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException}; + public Class[] thrown() { + return new Class[]{CRECastException.class}; } @Override @@ -1623,10 +2047,13 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + if(args.length < 2) { + throw new CREInsufficientArgumentsException(getName() + " must have 2 arguments at minimum", t); + } CResource m = (CResource) args[0]; StringBuffer buf = ResourceManager.GetResource(m, StringBuffer.class, t); - for(int i = 1; i < args.length; i++){ + for(int i = 1; i < args.length; i++) { buf.append(args[i].val()); } return CVoid.VOID; @@ -1644,7 +2071,7 @@ public Integer[] numArgs() { @Override public String docs() { - return "void {resource, toAppend...} Appends any number of values to the underlying" + return "void {Resource, toAppend...} Appends any number of values to the underlying" + " string builder. This is much more efficient than doing normal concatenation" + " with a string when building a string in a loop. The underlying resource may" + " be converted to a string via a cast, string(@res)."; @@ -1652,49 +2079,50 @@ public String docs() { @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Basic usage", "@res = res_create_resource('STRING_BUILDER')\n" - + "foreach(1..50, @i,\n" - + "\tstring_append(@res, @i, '.')\n" - + ")\n" - + "@string = string(@res)\n" - + "res_free_resource(@res) #This line is super important!\n" - + "msg(@string)" - + ""), + + "foreach(1..50, @i,\n" + + "\tstring_append(@res, @i, '.')\n" + + ")\n" + + "@string = string(@res)\n" + + "res_free_resource(@res) #This line is super important!\n" + + "msg(@string)" + + ""), new ExampleScript("Basic usage, showing performance benefits", - "@to = 100000\n" - + "@t1 = time()\n" - + "@res = res_create_resource('STRING_BUILDER')\n" - + "foreach(range(0, @to), @i,\n" - + "\tstring_append(@res, @i, '.')\n" - + ")\n" - + "res_free_resource(@res)\n" - + "@t2 = time()\n" - + "@t3 = time()\n" - + "@str = ''\n" - + "foreach(range(0, @to), @i,\n" - + "\t@str .= @i . '.'\n" - + ")\n" - + "@t4 = time()\n" - + "msg('Task 1 took '.(@t2 - @t1).'ms under '.@to.' iterations')\n" - + "msg('Task 2 took '.(@t4 - @t3).'ms under '.@to.' iterations')", - "Task 1 took 542ms under 100000 iterations\n" - + "Task 2 took 28305ms under 100000 iterations\n") + "@to = 100000\n" + + "@t1 = time()\n" + + "@res = res_create_resource('STRING_BUILDER')\n" + + "foreach(range(0, @to), @i,\n" + + "\tstring_append(@res, @i, '.')\n" + + ")\n" + + "res_free_resource(@res)\n" + + "@t2 = time()\n" + + "@t3 = time()\n" + + "@str = ''\n" + + "foreach(range(0, @to), @i,\n" + + "\t@str .= @i . '.'\n" + + ")\n" + + "@t4 = time()\n" + + "msg('Task 1 took '.(@t2 - @t1).'ms under '.@to.' iterations')\n" + + "msg('Task 2 took '.(@t4 - @t3).'ms under '.@to.' iterations')", + "Task 1 took 542ms under 100000 iterations\n" + + "Task 2 took 28305ms under 100000 iterations\n") }; } } - @api public static class char_from_unicode extends AbstractFunction implements Optimizable { + @api + public static class char_from_unicode extends AbstractFunction implements Optimizable { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException, ExceptionType.RangeException}; + public Class[] thrown() { + return new Class[]{CRECastException.class, CRERangeException.class}; } @Override @@ -1708,11 +2136,11 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - try{ - return new CString(new String(Character.toChars(Static.getInt32(args[0], t))), t); - } catch(IllegalArgumentException ex){ - throw new Exceptions.RangeException("Code point out of range: " + args[0].val(), t); + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + try { + return new CString(new String(Character.toChars(ArgumentValidation.getInt32(args[0], t))), t); + } catch (IllegalArgumentException ex) { + throw new CRERangeException("Code point out of range: " + args[0].val(), t); } } @@ -1731,14 +2159,16 @@ public String docs() { return "string {unicode} Returns the unicode character for a given unicode value. This is meant" + " for dynamic input that needs converting to a unicode character, if you're hardcoding" + " it, you should just use '\\u1234' syntax instead, however, this is the dynamic equivalent" - + " of the \\u string escape, so '\\u1234' == char_from_unicode(parse_int('1234', 16)). Despite the name," + + " of the \\u string escape, so" + + " '\\u1234' == char_from_unicode(parse_int('1234', 16)) == char_from_unicode(0x1234)." + + " Despite the name," + " certain unicode escapes may return multiple characters, so there is no guarantee that" + " length(char_from_unicode(@val)) will equal 1."; } @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } @Override @@ -1755,11 +2185,12 @@ public Set optimizationOptions() { } - @api public static class unicode_from_char extends AbstractFunction { + @api + public static class unicode_from_char extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException, ExceptionType.RangeException}; + public Class[] thrown() { + return new Class[]{CRECastException.class, CRERangeException.class}; } @Override @@ -1773,9 +2204,9 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - if(args[0].val().toCharArray().length == 0){ - throw new Exceptions.RangeException("Empty string cannot be converted to unicode.", t); + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + if(args[0].val().toCharArray().length == 0) { + throw new CRERangeException("Empty string cannot be converted to unicode.", t); } int i = Character.codePointAt(args[0].val().toCharArray(), 0); return new CInt(i, t); @@ -1799,7 +2230,7 @@ public String docs() { @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } @Override @@ -1811,11 +2242,13 @@ public ExampleScript[] examples() throws ConfigCompileException { } - @api public static class levenshtein extends AbstractFunction { + @api + public static class levenshtein extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + @SuppressWarnings("unchecked") + public Class[] thrown() { + return new Class[]{}; } @Override @@ -1829,7 +2262,7 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { return new CInt(StringUtils.LevenshteinDistance(args[0].val(), args[1].val()), t); } @@ -1846,13 +2279,13 @@ public Integer[] numArgs() { @Override public String docs() { return "int {string1, string2} Returns the levenshtein distance of two character sequences. For" - + " instance, \"123\" and \"133\" would have a string distance of 1, while \"123\"" - + " and \"123\" would be 0, since they are the same string."; + + " instance, \"123\" and \"133\" would have a string distance of 1, while \"123\"" + + " and \"123\" would be 0, since they are the same string."; } @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } @Override @@ -1865,4 +2298,471 @@ public ExampleScript[] examples() throws ConfigCompileException { } } + + @api + public static class string_compare extends AbstractFunction { + + @Override + @SuppressWarnings("unchecked") + public Class[] thrown() { + return new Class[]{}; + } + + @Override + public boolean isRestricted() { + return false; + } + + @Override + public Boolean runAsync() { + return null; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + return new CInt(args[0].val().compareTo(args[1].val()), t); + } + + @Override + public String getName() { + return "string_compare"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{2}; + } + + @Override + public String docs() { + return "int {string s1, string s2} Compares two strings lexicographically." + + " The comparison is based on the Unicode value of each character in the strings." + + " Returns 0 if s1 is equal to s2, a negative value if s1 is lexographically less than s2," + + " and a positive value if s1 is lexigraphically greater than s2." + + " The magnitude of non-zero return values is the difference between the char values at the first" + + " index at which a different char was found in both strings." + + " If all chars match but the strings differ in length, then the magnitude is this difference."; + } + + @Override + public Version since() { + return MSVersion.V3_3_5; + } + + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("", "string_compare('axx', 'bxx')"), + new ExampleScript("", "string_compare('xbx', 'xax')"), + new ExampleScript("", "string_compare('abc', 'abc')"), + new ExampleScript("", "string_compare('abc', 'abcde')") + }; + } + } + + @api + public static class string_compare_ic extends string_compare { + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + return new CInt(args[0].val().compareToIgnoreCase(args[1].val()), t); + } + + @Override + public String getName() { + return "string_compare_ic"; + } + + @Override + public String docs() { + return "int {string s1, string s2} Compares two strings lexicographically ignoring casing." + + " The comparison is based on the Unicode value of each character in the strings." + + " Returns 0 if s1 is equal to s2, a negative value if s1 is lexographically less than s2," + + " and a positive value if s1 is lexigraphically greater than s2." + + " The magnitude of non-zero return values is the difference between the char values at the first" + + " index at which a different char was found in both strings." + + " If all chars match but the strings differ in length, then the magnitude is this difference."; + } + + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("", "string_compare_ic('axx', 'bxx')"), + new ExampleScript("", "string_compare_ic('Axx', 'bxx')"), + new ExampleScript("", "string_compare_ic('axx', 'Bxx')"), + new ExampleScript("", "string_compare_ic('xbx', 'xax')"), + new ExampleScript("", "string_compare_ic('abc', 'abc')"), + new ExampleScript("", "string_compare_ic('abc', 'abcde')") + }; + } + } + + @api + public static class string_multiply extends AbstractFunction { + + @Override + @SuppressWarnings("unchecked") + public Class[] thrown() { + return new Class[]{CRERangeException.class, CRECastException.class}; + } + + @Override + public boolean isRestricted() { + return false; + } + + @Override + public Boolean runAsync() { + return null; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + if(args[0] instanceof CNull) { + return CNull.NULL; + } + String string = args[0].val(); + int times = ArgumentValidation.getInt32(args[1], t); + if(times < 0) { + throw new CRERangeException("Expecting a value >= 0, but " + times + " was found.", t); + } + if(times == 0 || string.isEmpty()) { + return new CString("", t); + } + if(string.length() * times < 0) { + throw new CRERangeException("Maximum string size for return value exceeded. Max size: 2147483647," + + " required size: " + (times * (long) string.length()) + ".", t); + } + String s = repeat(string, times); + return new CString(s, t); + } + + // Code taken from Apache Commons, and modified. + private static String repeat(String str, int repeat) { + int inputLength = str.length(); + if(repeat == 1 || inputLength == 0) { + return str; + } + if(inputLength == 1) { + char ch = str.charAt(0); + final char[] buf = new char[repeat]; + for(int i = 0; i < repeat; i++) { + buf[i] = ch; + } + return new String(buf); + } + int outputLength = inputLength * repeat; + if(inputLength == 2) { + char ch0 = str.charAt(0); + char ch1 = str.charAt(1); + char[] output2 = new char[outputLength]; + for(int i = repeat * 2 - 2; i >= 0; i--, i--) { + output2[i] = ch0; + output2[i + 1] = ch1; + } + return new String(output2); + } + StringBuilder buf = new StringBuilder(outputLength); + for(int i = 0; i < repeat; i++) { + buf.append(str); + } + return buf.toString(); + } + + @Override + public String getName() { + return "string_multiply"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{2}; + } + + @Override + public String docs() { + return "string {string, times} Multiplies a string the given number of times." + + " For instance, string_multiply('a', 3) returns 'aaa'. If the string" + + " is empty, an empty string is returned. If the string is null, null" + + " is returned. If times is 0, an empty string is returned." + + " All other string values are multiplied accordingly." + + " Providing a value less than 0 for times results in a RangeException." + + " When the maximum string size of 2147483647 characters would be exceeded," + + " a RangeException is thrown."; + } + + @Override + public Version since() { + return MSVersion.V3_3_2; + } + + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("Basic usage", "string_multiply('a', 4)") + }; + } + + } + + @api + @seealso(decrypt_secure_string.class) + @noboilerplate // A boilerplate test on this function is very hard on Travis CI, sometimes resulting in a timeout. + public static class secure_string extends AbstractFunction { + + @Override + public Class[] thrown() { + return new Class[]{CREFormatException.class}; + } + + @Override + public boolean isRestricted() { + return false; + } + + @Override + public Boolean runAsync() { + return null; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + if(args[0].isInstanceOf(CArray.TYPE)) { + CArray array = ArgumentValidation.getArray(args[0], t); + return new CSecureString(array, t); + } else { + String s = args[0].val(); + return new CSecureString(s.toCharArray(), t); + } + } + + @Override + public String getName() { + return "secure_string"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1}; + } + + @Override + public String docs() { + return "secure_string {charArray|string} Constructs a secure_string from a given char array or string." + + " ---- A secure_string is a string which cannot normally be toString'd, and whose underlying representation" + + " is encrypted in memory. This should be used for storing passwords or other sensitive data which" + + " should in no cases be stored in plain text. Since this extends string, it can generally be used in" + + " place of a string, and when done so, cannot accidentally be exposed (via logs or exception messages," + + " or other accidental exposure) unless it is specifically instructed to decrypt and switch to a char" + + " array. While this cannot by itself ensure security of the value, it can help prevent most accidental" + + " exposures of data by intermediate code. When exported as a string (or imported as a string) other" + + " code must be written to ensure safety of those systems. It is recommended that a secure value never" + + " be stored as a string, however, this method accepts a string for compatibility reasons."; + } + + @Override + public Version since() { + return MSVersion.V3_3_2; + } + + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("Demonstrates secure value", "msg(secure_string(\"test\"));"), + new ExampleScript("Demonstrates common usage", "secure_string @secure = secure_string(array('p','a','s','s'));\n" + + "msg(@secure); // Won't print the actual password to screen\n" + + "msg(decrypt_secure_string(@secure)); // Prints the actual password (as a char array)"), + new ExampleScript("Demonstrates compatibility with other functions", "@profile = array(\n" + + "\tuser: 'username',\n\tpassword: secure_string('password')\n" + + ");\n" + + "msg(@profile);"), + new ExampleScript("Demonstrates compatibility with string class", "string @sec = secure_string('password');" + + " // Not an error, because secure_string extends string\n" + + "msg(decrypt_secure_string(@sec));") + }; + } + } + + @api + @seealso(secure_string.class) + public static class decrypt_secure_string extends AbstractFunction { + + @Override + @SuppressWarnings("unchecked") + public Class[] thrown() { + return new Class[]{CRECastException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return null; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + if(args[0].isInstanceOf(CSecureString.TYPE)) { + CSecureString secure = ArgumentValidation.getObject(args[0], t, CSecureString.class); + return secure.getDecryptedCharCArray(); + } else if(args[0].isInstanceOf(CString.TYPE)) { + CArray array = new CArray(Target.UNKNOWN, args[0].val().length()); + for(char c : args[0].val().toCharArray()) { + array.push(new CString(c, t), t); + } + return array; + } else { + throw new CRECastException("Can only accept strings in " + getName(), t); + } + } + + @Override + public String getName() { + return "decrypt_secure_string"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1}; + } + + @Override + public String docs() { + return "array {string} Decrypts a secure_string into a char array. To keep backwards compatibility with" + + " strings in general, this function also accepts normal strings, which are not decrypted, but" + + " instead simply returned in the same format as if it were a secure_string." + + " See the examples in {{function|secure_string}}."; + } + + @Override + public Version since() { + return MSVersion.V3_3_2; + } + + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("Use of secure_string and string", "string @secure = secure_string('secure');\n" + + "string @insecure = 'insecure';\n" + + "msg(@secure);\n" + + "msg(@insecure);\n" + + "msg(decrypt_secure_string(@secure));\n" + + "msg(decrypt_secure_string(@insecure));\n" + + "msg(typeof(@secure));\n" + + "msg(typeof(@insecure));\n") + }; + } + } + + @api + public static class uuid extends AbstractFunction { + + @MEnum("ms.lang.UUIDType") + public static enum UUIDType { + // TODO: May wish to instead look into https://github.com/cowtowncoder/java-uuid-generator instead + // of rolling our own here. Most people probably only will use RANDOM anyways +// MAC("Returns UUID representing the datetime and MAC address of this device. The system's current time" +// + " is used if the argument is null, but you may instead send your own value to use" +// + " instead", 1, +// (input) -> { +// +// }), +// MAC_DCE("Returns the UUID representing the datetime and MAC address of this device, but is modified with" +// + " the local domain parameter passed in", 1), +// NAMESPACE_MD5("", 1), + RANDOM("Returns a random UUID", 0, + (input) -> UUID.randomUUID()), +// NAMESPACE_SHA1("", 1), + NIL("Returns the nil UUID, that is 00000000-0000-0000-0000-000000000000", 0, (input) -> new UUID(0, 0)); + + private static interface Generate { + UUID g(String input); + } + + private final String description; + private final int parameters; + private final Generate generator; + + UUIDType(String description, int parameters, Generate g) { + this.description = description; + this.parameters = parameters; + this.generator = g; + } + + public UUID generate(String input) { + return generator.g(input); + } + } + + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class}; + } + + @Override + public boolean isRestricted() { + return false; + } + + @Override + public Boolean runAsync() { + return null; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + UUIDType type = UUIDType.RANDOM; + String input = null; + if(args.length > 0) { + type = ArgumentValidation.getEnum(args[0], UUIDType.class, t); + } + if(args.length > 1) { + input = Construct.nval(args[1]); + } + return new CString(type.generate(input).toString(), t); + } + + @Override + public String getName() { + return "uuid"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{0, 1, 2}; + } + + @Override + public String docs() { + return "string {type, arg} Returns a UUID (also known as a GUID). Different types of UUIDs can be" + + " generated, by default, if no parameters are provided, a random uuid is returned (version 4)." + + " For full details on what exactly a uuid is, and what the different versions are, see" + + " https://en.wikipedia.org/wiki/Universally_unique_identifier. The arg varies depending on the" + + " type, some types do not require an argument, in which case, this parameter will be ignored." + + " type may be one of:\n- " + StringUtils.Join(UUIDType.values(), "\n- ", "\n- ", + "\n- ", "", + (UUIDType type) -> { + return type.name() + " - " + type.description + ". This type takes " + + StringUtils.PluralTemplateHelper(type.parameters, "%d argument.", + "%d arguments."); + }); + } + + @Override + public Version since() { + return MSVersion.V3_3_3; + } + + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("Typical usage", "uuid()", "46fa3d0e-0178-4384-8a9c-2f0df1cada2b"), + new ExampleScript("Explicit RANDOM uuid", "uuid('RANDOM')", "fb9f9a7b-76c2-40e3-ba20-8ab23553b9d6"), + new ExampleScript("NIL uuid", "uuid('NIL')") + }; + } + } } diff --git a/src/main/java/com/laytonsmith/core/functions/TaskHandling.java b/src/main/java/com/laytonsmith/core/functions/TaskHandling.java index 6c67660a91..d058dcb761 100644 --- a/src/main/java/com/laytonsmith/core/functions/TaskHandling.java +++ b/src/main/java/com/laytonsmith/core/functions/TaskHandling.java @@ -4,17 +4,18 @@ import com.laytonsmith.PureUtilities.Version; import com.laytonsmith.annotations.api; import com.laytonsmith.annotations.hide; -import com.laytonsmith.core.CHVersion; -import com.laytonsmith.core.Static; +import com.laytonsmith.core.ArgumentValidation; +import com.laytonsmith.core.MSVersion; import com.laytonsmith.core.constructs.CArray; import com.laytonsmith.core.constructs.CInt; import com.laytonsmith.core.constructs.CVoid; -import com.laytonsmith.core.constructs.Construct; import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.environments.Environment; -import com.laytonsmith.core.environments.GlobalEnv; +import com.laytonsmith.core.environments.StaticRuntimeEnv; +import com.laytonsmith.core.exceptions.CRE.CRECastException; +import com.laytonsmith.core.exceptions.CRE.CREThrowable; import com.laytonsmith.core.exceptions.ConfigRuntimeException; -import com.laytonsmith.core.functions.Exceptions.ExceptionType; +import com.laytonsmith.core.natives.interfaces.Mixed; import com.laytonsmith.core.taskmanager.CoreTaskType; import com.laytonsmith.core.taskmanager.TaskHandler; import com.laytonsmith.core.taskmanager.TaskManager; @@ -24,7 +25,8 @@ * */ public class TaskHandling { - public static String docs(){ + + public static String docs() { return "This class is used to manage various tasks throughout MethodScript. It is a task manager of sorts."; } @@ -33,8 +35,8 @@ public static String docs(){ public static class tm_get_tasks extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{}; } @Override @@ -48,21 +50,21 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - TaskManager tm = environment.getEnv(GlobalEnv.class).GetTaskManager(); + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + TaskManager tm = environment.getEnv(StaticRuntimeEnv.class).GetTaskManager(); CArray ret = new CArray(t); - for(TaskHandler task : tm.getTasks()){ - CArray tt = new CArray(t); + for(TaskHandler task : tm.getTasks()) { + CArray tt = CArray.GetAssociativeArray(t); tt.set("id", new CInt(task.getID(), t), t); tt.set("type", task.getType().name()); tt.set("state", task.getState().name()); tt.set("target", task.getDefinedAt().toString()); CArray properties = CArray.GetAssociativeArray(t); - for(String prop : task.getProperties()){ + for(String prop : task.getProperties()) { properties.set(prop, task.getPropertyData().get(prop).toString()); } tt.set("properties", properties, t); - ret.push(tt); + ret.push(tt, t); } return ret; } @@ -107,7 +109,7 @@ public String docs() { @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } } @@ -117,8 +119,8 @@ public Version since() { public static class tm_kill_task extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException}; + public Class[] thrown() { + return new Class[]{CRECastException.class}; } @Override @@ -132,10 +134,10 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { String type = args[0].val(); - int id = Static.getInt32(args[1], t); - TaskManager tm = environment.getEnv(GlobalEnv.class).GetTaskManager(); + int id = ArgumentValidation.getInt32(args[1], t); + TaskManager tm = environment.getEnv(StaticRuntimeEnv.class).GetTaskManager(); tm.killTask(type, id); return CVoid.VOID; } @@ -158,7 +160,7 @@ public String docs() { @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } } diff --git a/src/main/java/com/laytonsmith/core/functions/Threading.java b/src/main/java/com/laytonsmith/core/functions/Threading.java index 531930a813..7b00b4757d 100644 --- a/src/main/java/com/laytonsmith/core/functions/Threading.java +++ b/src/main/java/com/laytonsmith/core/functions/Threading.java @@ -1,7 +1,7 @@ - package com.laytonsmith.core.functions; import com.laytonsmith.PureUtilities.DaemonManager; +import com.laytonsmith.PureUtilities.Triplet; import com.laytonsmith.PureUtilities.Version; import com.laytonsmith.abstraction.Implementation; import com.laytonsmith.abstraction.StaticLayer; @@ -9,44 +9,68 @@ import com.laytonsmith.annotations.core; import com.laytonsmith.annotations.noboilerplate; import com.laytonsmith.annotations.seealso; -import com.laytonsmith.core.CHVersion; -import com.laytonsmith.core.Static; +import com.laytonsmith.core.ArgumentValidation; +import com.laytonsmith.core.MSVersion; +import com.laytonsmith.core.ParseTree; +import com.laytonsmith.core.Script; +import com.laytonsmith.core.compiler.BranchStatement; +import com.laytonsmith.core.compiler.SelfStatement; +import com.laytonsmith.core.compiler.VariableScope; +import com.laytonsmith.core.compiler.signature.FunctionSignatures; +import com.laytonsmith.core.compiler.signature.SignatureBuilder; import com.laytonsmith.core.constructs.CBoolean; -import com.laytonsmith.core.constructs.CClosure; import com.laytonsmith.core.constructs.CNull; import com.laytonsmith.core.constructs.CString; import com.laytonsmith.core.constructs.CVoid; -import com.laytonsmith.core.constructs.Construct; import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.environments.Environment; -import com.laytonsmith.core.environments.GlobalEnv; +import com.laytonsmith.core.environments.StaticRuntimeEnv; +import com.laytonsmith.core.exceptions.CRE.CRECastException; +import com.laytonsmith.core.exceptions.CRE.CREIllegalArgumentException; +import com.laytonsmith.core.exceptions.CRE.CREInterruptedException; +import com.laytonsmith.core.exceptions.CRE.CRENullPointerException; +import com.laytonsmith.core.exceptions.CRE.CREThrowable; import com.laytonsmith.core.exceptions.CancelCommandException; import com.laytonsmith.core.exceptions.ConfigCompileException; import com.laytonsmith.core.exceptions.ConfigRuntimeException; -import com.laytonsmith.core.exceptions.FunctionReturnException; import com.laytonsmith.core.exceptions.LoopManipulationException; import com.laytonsmith.core.exceptions.ProgramFlowManipulationException; -import com.laytonsmith.core.functions.Exceptions.ExceptionType; +import com.laytonsmith.core.natives.interfaces.Mixed; +import com.laytonsmith.core.natives.interfaces.ValueType; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.Random; import java.util.concurrent.Callable; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; /** * */ @core -public class Threading { - public static String docs(){ +public final class Threading { + + private Threading() {} + + public static String docs() { return "This experimental and private API is subject to removal, or incompatible changes, and should not" + " be yet heavily relied on in normal development."; } + public static final Map THREAD_ID_MAP = new HashMap<>(); + @api @noboilerplate @seealso({x_run_on_main_thread_later.class, x_run_on_main_thread_now.class}) public static class x_new_thread extends AbstractFunction { @Override - public Exceptions.ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException}; + public Class[] thrown() { + return new Class[]{CRECastException.class}; } @Override @@ -60,36 +84,40 @@ public Boolean runAsync() { } @Override - public Construct exec(final Target t, final Environment environment, Construct... args) throws ConfigRuntimeException { - String id = args[0].val(); - if(!(args[1] instanceof CClosure)){ - throw new Exceptions.CastException("Expected closure for arg 2", t); - } - final CClosure closure = (CClosure) args[1]; - new Thread(new Runnable() { - + public Mixed exec(final Target t, final Environment env, Mixed... args) throws ConfigRuntimeException { + final String threadId = args[0].val(); + final com.laytonsmith.core.natives.interfaces.Callable closure + = ArgumentValidation.getObject(args[1], t, com.laytonsmith.core.natives.interfaces.Callable.class); + Thread th = new Thread("(" + Implementation.GetServerType().getBranding() + ") " + threadId) { @Override public void run() { - DaemonManager dm = environment.getEnv(GlobalEnv.class).GetDaemonManager(); + DaemonManager dm = env.getEnv(StaticRuntimeEnv.class).GetDaemonManager(); dm.activateThread(Thread.currentThread()); try { - closure.execute(); - } catch(FunctionReturnException ex){ - // Do nothing - } catch(LoopManipulationException ex){ + closure.executeCallable(env, t); + } catch (LoopManipulationException ex) { ConfigRuntimeException.HandleUncaughtException(ConfigRuntimeException.CreateUncatchableException("Unexpected loop manipulation" - + " operation was triggered inside the closure.", t), environment); - } catch(ConfigRuntimeException ex){ - ConfigRuntimeException.HandleUncaughtException(ex, environment); - } catch(CancelCommandException ex){ - if(ex.getMessage() != null){ - new Echoes.console().exec(t, environment, new CString(ex.getMessage(), t), CBoolean.FALSE); + + " operation was triggered inside the closure.", t), env); + } catch (ConfigRuntimeException ex) { + ConfigRuntimeException.HandleUncaughtException(ex, env); + } catch (CancelCommandException ex) { + if(ex.getMessage() != null) { + new Echoes.console().exec(t, env, new CString(ex.getMessage(), t), CBoolean.FALSE); } } finally { dm.deactivateThread(Thread.currentThread()); + synchronized(THREAD_ID_MAP) { + if(THREAD_ID_MAP.get(threadId) == this) { + THREAD_ID_MAP.remove(threadId); + } + } } } - }, "(" + Implementation.GetServerType().getBranding() + ") " + id).start(); + }; + th.start(); + synchronized(THREAD_ID_MAP) { + THREAD_ID_MAP.put(threadId, th); + } return CVoid.VOID; } @@ -114,7 +142,7 @@ public String docs() { @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } @Override @@ -123,14 +151,14 @@ public ExampleScript[] examples() throws ConfigCompileException { String new_thread = this.getName(); return new ExampleScript[]{ new ExampleScript("Basic usage", "msg(" + get_current_thread + "());\n" - + new_thread + "('myThread', closure(){\n" - + "\tsleep(5); // Sleep here, to allow the main thread to get well past us, for demonstration purposes\n" - + "\tmsg(" + get_current_thread + "());\n" - + "});\n" - + "msg('End of main thread');", - "MainThread\n" - + "End of main thread\n" - + "myThread") + + new_thread + "('myThread', closure(){\n" + + "\tsleep(5); // Sleep here, to allow the main thread to get well past us, for demonstration purposes\n" + + "\tmsg(" + get_current_thread + "());\n" + + "});\n" + + "msg('End of main thread');", + "MainThread\n" + + "End of main thread\n" + + "myThread") }; } @@ -140,8 +168,8 @@ public ExampleScript[] examples() throws ConfigCompileException { public static class x_get_current_thread extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{}; } @Override @@ -155,7 +183,7 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { return new CString(Thread.currentThread().getName(), t); } @@ -176,7 +204,7 @@ public String docs() { @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } @Override @@ -194,8 +222,8 @@ public ExampleScript[] examples() throws ConfigCompileException { public static class x_run_on_main_thread_later extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{}; } @Override @@ -209,17 +237,19 @@ public Boolean runAsync() { } @Override - public Construct exec(final Target t, final Environment environment, Construct... args) throws ConfigRuntimeException { - final CClosure closure = Static.getObject(args[0], t, CClosure.class); - StaticLayer.GetConvertor().runOnMainThreadLater(environment.getEnv(GlobalEnv.class).GetDaemonManager(), new Runnable() { + public Mixed exec(final Target t, final Environment env, Mixed... args) throws ConfigRuntimeException { + final com.laytonsmith.core.natives.interfaces.Callable closure + = ArgumentValidation.getObject(args[0], t, com.laytonsmith.core.natives.interfaces.Callable.class); + StaticLayer.GetConvertor().runOnMainThreadLater( + env.getEnv(StaticRuntimeEnv.class).GetDaemonManager(), new Runnable() { @Override public void run() { try { - closure.execute(); - } catch(ConfigRuntimeException e){ - ConfigRuntimeException.HandleUncaughtException(e, environment); - } catch(ProgramFlowManipulationException e){ + closure.executeCallable(env, t); + } catch (ConfigRuntimeException e) { + ConfigRuntimeException.HandleUncaughtException(e, env); + } catch (ProgramFlowManipulationException e) { // Ignored } } @@ -246,7 +276,7 @@ public String docs() { @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } } @@ -257,8 +287,8 @@ public Version since() { public static class x_run_on_main_thread_now extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{}; } @Override @@ -272,8 +302,9 @@ public Boolean runAsync() { } @Override - public Construct exec(final Target t, final Environment environment, Construct... args) throws ConfigRuntimeException { - final CClosure closure = Static.getObject(args[0], t, CClosure.class); + public Mixed exec(final Target t, final Environment env, Mixed... args) throws ConfigRuntimeException { + final com.laytonsmith.core.natives.interfaces.Callable closure = ArgumentValidation.getObject(args[0], t, + com.laytonsmith.core.natives.interfaces.Callable.class); Object ret; try { ret = StaticLayer.GetConvertor().runOnMainThreadAndWait(new Callable() { @@ -281,24 +312,20 @@ public Construct exec(final Target t, final Environment environment, Construct.. @Override public Object call() throws Exception { try { - closure.execute(); - } catch(FunctionReturnException e){ - return e.getReturn(); - } catch(ConfigRuntimeException | ProgramFlowManipulationException e){ + return closure.executeCallable(env, t); + } catch (ConfigRuntimeException | ProgramFlowManipulationException e) { return e; } - return CNull.NULL; } }); - } catch (Exception ex) { throw new RuntimeException(ex); } - if(ret instanceof RuntimeException){ - throw (RuntimeException)ret; + if(ret instanceof RuntimeException runtimeException) { + throw runtimeException; } else { - return (Construct) ret; + return (Mixed) ret; } } @@ -323,9 +350,642 @@ public String docs() { @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; + } + + } + + /** + * Contains the queue of callables that are waiting on the lock. + */ + private static final Map>> SYNC_OBJECT_QUEUE = new HashMap<>(); + /** + * Contains a mapping from Lock object, to references to the lock. For internal synchronization purposes, + * this value should always be used for synchronization. + */ + private static final Map SYNC_OBJECT_MAP = new HashMap<>(); + /** + * Contains a mapping from code object to lock. + */ + private static final Map SYNC_OBJECT_LOCKS = new HashMap<>(); + + private static Lock getSyncObject(Mixed cSyncObject, Function f, Target t) { + + if(cSyncObject instanceof CNull || cSyncObject == null) { + throw new CRENullPointerException("Synchronization object may not be null in " + f.getName() + "().", t); + } + Object syncObject = cSyncObject; + if(cSyncObject.isInstanceOf(ValueType.TYPE)) { + if(!(cSyncObject instanceof CString)) { + throw new CREIllegalArgumentException("Only strings and non-value types can be used for synchronization.", t); + } + syncObject = cSyncObject.val(); + } + + // Add String sync objects to the map to be able to synchronize by value. + synchronized(SYNC_OBJECT_MAP) { + Lock lock = SYNC_OBJECT_LOCKS.computeIfAbsent(syncObject, (x) -> { + return new ReentrantLock(); + }); + int currentCount = SYNC_OBJECT_MAP.computeIfAbsent(lock, x -> 0); + SYNC_OBJECT_MAP.put(lock, currentCount + 1); + return lock; + } + } + + private static void cleanupSync(Lock syncObject) { + // Remove 1 from the call count or remove the sync object from the map if it was a sync-by-value. + synchronized(SYNC_OBJECT_MAP) { + int count = SYNC_OBJECT_MAP.get(syncObject); // This should never return null. + if(count <= 1) { + SYNC_OBJECT_MAP.remove(syncObject); + SYNC_OBJECT_LOCKS.remove(syncObject); + SYNC_OBJECT_QUEUE.remove(syncObject); + } else { + SYNC_OBJECT_MAP.put(syncObject, count - 1); + } + + } + } + + static { + StaticLayer.GetConvertor().addPersistentShutdownHook(() -> { + SYNC_OBJECT_QUEUE.clear(); + }); + } + + private static void PumpQueue(Lock syncObject, DaemonManager dm) { + synchronized(SYNC_OBJECT_MAP) { + if(!SYNC_OBJECT_MAP.containsKey(syncObject)) { + // Lock was disposed of, we have an extra Pump on a now expired object. + // This can happen if x_get_lock is called inside x_get_lock, for instance, + // though it could also happen due to bugs in our code. + return; + } + } + dm.activateThread(Thread.currentThread()); + try { + if(syncObject.tryLock()) { + try { + Triplet triplet; + synchronized(SYNC_OBJECT_MAP) { + triplet = SYNC_OBJECT_QUEUE.get(syncObject).poll(); + } + triplet.getFirst().executeCallable(triplet.getSecond(), triplet.getThird()); + synchronized(SYNC_OBJECT_MAP) { + if(!SYNC_OBJECT_QUEUE.get(syncObject).isEmpty()) { + StaticLayer.SetFutureRunnable(dm, 1, () -> PumpQueue(syncObject, dm)); + } + } + } finally { + cleanupSync(syncObject); + syncObject.unlock(); + } + } else { + int backoff = java.lang.Math.abs(new Random().nextInt()) % 100; + StaticLayer.SetFutureRunnable(dm, + backoff, () -> { + PumpQueue(syncObject, dm); + }); + } + } finally { + dm.deactivateThread(Thread.currentThread()); + } + } + + @api + @noboilerplate + @seealso({x_new_thread.class, x_get_lock.class}) + @SelfStatement + public static class _synchronized extends AbstractFunction implements VariableScope, BranchStatement { + + + + @Override + public Class[] thrown() { + return new Class[]{CRENullPointerException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public boolean preResolveVariables() { + return false; + } + + @Override + public boolean useSpecialExec() { + return true; + } + + @Override + public Mixed execs(Target t, Environment env, Script parent, ParseTree... nodes) { + + // Get the sync object tree and the code to synchronize. + ParseTree syncObjectTree = nodes[0]; + ParseTree code = nodes[1]; + + // Get the sync object (CArray or String value of the Mixed). + Mixed cSyncObject = parent.seval(syncObjectTree, env); + Lock syncObject = getSyncObject(cSyncObject, this, t); + + // Evaluate the code, synchronized by the passed sync object. + try { + syncObject.lock(); + parent.eval(code, env); + } finally { + syncObject.unlock(); + cleanupSync(syncObject); + } + return CVoid.VOID; + } + + @Override + public Mixed exec(final Target t, final Environment env, Mixed... args) throws ConfigRuntimeException { + return CVoid.VOID; + } + + @Override + public String getName() { + return "synchronized"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{2}; + } + + @Override + public String docs() { + return "void {syncObject, code} Synchronizes access to the code block for all calls (from different" + + " threads) with the same syncObject argument." + + " This means that if two threads will call " + getName() + "('example', <code>), the second" + + " call will hang the thread until the passed code of the first call has finished executing." + + " If you call this function from within this function on the same thread using the same" + + " syncObject, the code will simply be executed. Note that this uses the same pool of lock" + + " objects as x_get_lock, except this is preferred to be used off the main thread," + + " whereas x_get_lock is" + + " preferred on the main thread. Generally speaking, it is almost always incorrect to use this" + + " on the main thread, though there is no technical restriction to doing so. Other than strings," + + " ValueTypes cannot be used as the lock object, and reference types such as an array or other" + + " object is required." + + " For more information about synchronization, see:" + + " https://en.wikipedia.org/wiki/Synchronization_(computer_science)"; + } + + @Override + public Version since() { + return MSVersion.V3_3_2; + } + + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("Demonstrates two threads possibly overwriting each other", "" + + "export('log', '');\n" + + "x_new_thread('Thread1', closure() {\n" + + "\t@log = import('log');\n" + + "\t@log = @log.'Some new log message from Thread1.\n'\n" + + "\texport('log', @log);\n" + + "});\n" + + "x_new_thread('Thread2', closure() {\n" + + "\t@log = import('log');\n" + + "\t@log = @log.'Some new log message from Thread2.\n'\n" + + "\texport('log', @log);\n" + + "});\n" + + "sleep(0.1);\n" + + "msg(import('log'));", + "Some new log message from Thread1.\n" + + "\nOR\nSome new log message from Thread2.\n" + + "\nOR\nSome new log message from Thread1.\nSome new log message from Thread2.\n" + + "\nOR\nSome new log message from Thread2.\nSome new log message from Thread1.\n"), + new ExampleScript("Demonstrates two threads modifying the same variable without the possibility of" + + " overwriting each other because they are synchronized.", "" + + "export('log', '');\n" + + "x_new_thread('Thread1', closure() {\n" + + "\tsynchronized('syncLog') {\n" + + "\t\t@log = import('log');\n" + + "\t\t@log = @log.'Some new log message from Thread1.\n'\n" + + "\t\texport('log', @log);\n" + + "\t}\n" + + "});\n" + + "x_new_thread('Thread2', closure() {\n" + + "\tsynchronized('syncLog') {\n" + + "\t\t@log = import('log');\n" + + "\t\t@log = @log.'Some new log message from Thread2.\n'\n" + + "\t\texport('log', @log);\n" + + "\t}\n" + + "});\n" + + "sleep(0.1);\n" + + "msg(import('log'));", + "Some new log message from Thread1.\nSome new log message from Thread2.\n" + + "\nOR\nSome new log message from Thread2.\nSome new log message from Thread1.\n") + }; } + @Override + public List isScope(List children) { + List ret = new ArrayList<>(2); + ret.add(false); + ret.add(true); + return ret; + } + + @Override + public List isBranch(List children) { + List ret = new ArrayList<>(2); + ret.add(false); + ret.add(true); + return ret; + } } + @api + @noboilerplate + @seealso({_synchronized.class, x_get_lock.class}) + public static class x_get_lock extends AbstractFunction { + + @Override + public Class[] thrown() { + return new Class[]{CRENullPointerException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + // Get the sync object tree and the code to synchronize. + Mixed cSyncObject = args[0]; + com.laytonsmith.core.natives.interfaces.Callable callable + = ArgumentValidation.getObject(args[1], t, com.laytonsmith.core.natives.interfaces.Callable.class); + + // Get the sync object (CArray or String value of the Mixed). + Lock syncObject = getSyncObject(cSyncObject, this, t); + + // Evaluate the code, synchronized by the passed sync object. + DaemonManager dm = env.getEnv(StaticRuntimeEnv.class).GetDaemonManager(); + Triplet triplet + = new Triplet<>(callable, env, t); + synchronized(SYNC_OBJECT_MAP) { + SYNC_OBJECT_QUEUE.computeIfAbsent(syncObject, k -> new LinkedList<>()) + .add(triplet); + } + StaticLayer.SetFutureRunnable(dm, 1, () -> PumpQueue(syncObject, dm)); + return CVoid.VOID; + } + + + + @Override + public String getName() { + return "x_get_lock"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{2}; + } + + @Override + public String docs() { + return getBundledDocs(); + } + + @Override + public Version since() { + return MSVersion.V3_3_5; + } + + @Override + public FunctionSignatures getSignatures() { + return new SignatureBuilder(CVoid.TYPE) + .param(Mixed.TYPE, "syncObject", "The object to sync on. This uses the same object pool as synchronized().") + .param(com.laytonsmith.core.natives.interfaces.Callable.TYPE, "action", "The action to run once the lock is obtained.") + .build(); + } + + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[] { + new ExampleScript("Usage across the main and off thread.", + "// Create a new thread\n" + + "x_new_thread('offthread', closure() {\n" + + "\twhile(true) {\n" + + "\t\tsynchronized('lock') {\n" + + "\t\t\tsleep(10); // Simulate a long running, off thread activity\n" + + "\t\t}\n" + + "\t}\n" + + "});\n\n" + + "// Main thread\n" + + "sleep(1); // Simulate other main thread activity\n" + + "// Note that if we used synchronized() here, this call would block the main thread\n" + + "// for 10 seconds, while the off thread performed its activities.\n" + + "x_get_lock('lock', closure() {\n" + + "\tmsg('Main thread activity');\n" + + "});\n", + "") + }; + } + + } + + @api + public static class x_thread_join extends AbstractFunction { + + @Override + public Class[] thrown() { + return new Class[]{CREInterruptedException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + String threadId = args[0].val(); + Thread th; + synchronized(THREAD_ID_MAP) { + th = THREAD_ID_MAP.get(threadId); + } + if(th == null) { + return CVoid.VOID; + } + int wait = 0; + if(args.length > 1) { + wait = ArgumentValidation.getInt32(args[1], t); + } + try { + th.join(wait); + } catch (InterruptedException ex) { + throw new CREInterruptedException(ex, t); + } + return CVoid.VOID; + } + + @Override + public String getName() { + return "x_thread_join"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1, 2}; + } + + @Override + public String docs() { + return "void {string id, [int maxWait]} Waits for the thread with the given id to exit. By default, we wait" + + " potentially forever, but if maxWait is specified, we will only wait that many milliseconds." + + " (Sending 0 for this value causes an infinite wait.) If the timeout occurs or thread interrupted, an" + + " InterruptedException is thrown. If the id is unknown, this function will directly return"; + } + + @Override + public Version since() { + return MSVersion.V3_3_4; + } + + } + + @api + public static class x_thread_is_alive extends AbstractFunction { + + @Override + public Class[] thrown() { + return null; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return null; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + String threadId = args[0].val(); + Thread th; + synchronized(THREAD_ID_MAP) { + th = THREAD_ID_MAP.get(threadId); + } + if(th == null) { + return CBoolean.FALSE; + } + return CBoolean.get(th.isAlive()); + } + + @Override + public Version since() { + return MSVersion.V3_3_4; + } + + @Override + public String getName() { + return "x_thread_is_alive"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1}; + } + + @Override + public String docs() { + return "boolean {string id} Tests if this thread with the given id is alive. A thread is alive if it has been started and has not yet died."; + } + } + + @api + @seealso({x_is_interrupted.class, x_clear_interrupt.class}) + public static class x_interrupt extends AbstractFunction { + + @Override + public Class[] thrown() { + return null; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return null; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + Thread th; + if(args.length == 1) { + String threadId = args[0].val(); + synchronized(THREAD_ID_MAP) { + th = THREAD_ID_MAP.get(threadId); + } + if(th != null) { + th.interrupt(); + } + } else { + Thread.currentThread().interrupt(); + } + return CVoid.VOID; + } + + @Override + public Version since() { + return MSVersion.V3_3_4; + } + + @Override + public String getName() { + return "x_interrupt"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{0, 1}; + } + + @Override + public String docs() { + return "void {[string id]} Interrupts the thread with the given id, or the current thread if no id is given." + + " When a thread is interrupted, its interrupt status is set to true." + + " This status can be checked using the " + new x_is_interrupted().getName() + " and " + new x_clear_interrupt().getName() + " function." + + " Note that some blocking functions will throw an InterruptedException when the thread on which they are executed is interrupted."; + } + } + + @api + @seealso({x_interrupt.class, x_clear_interrupt.class}) + public static class x_is_interrupted extends AbstractFunction { + + @Override + public Class[] thrown() { + return null; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return null; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + if(args.length == 1) { + String threadId = args[0].val(); + Thread th; + synchronized(THREAD_ID_MAP) { + th = THREAD_ID_MAP.get(threadId); + } + if(th != null) { + return CBoolean.get(th.isInterrupted()); + } else { + return CBoolean.FALSE; + } + } else { + return CBoolean.get(Thread.currentThread().isInterrupted()); + } + } + + @Override + public Version since() { + return MSVersion.V3_3_4; + } + + @Override + public String getName() { + return "x_is_interrupted"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{0, 1}; + } + + @Override + public String docs() { + return "boolean {[string id]} Tests whether the thread with the given id (or the current thread if none is provided)" + + " is interrupted via " + new x_interrupt().getName() + ". If the thread doesn't exist, or is not alive, false is returned."; + } + } + + @api + @seealso({x_interrupt.class, x_is_interrupted.class}) + public static class x_clear_interrupt extends AbstractFunction { + + @Override + public Class[] thrown() { + return null; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return null; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + return CBoolean.get(Thread.interrupted()); + } + + @Override + public Version since() { + return MSVersion.V3_3_4; + } + + @Override + public String getName() { + return "x_clear_interrupt"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{0}; + } + + @Override + public String docs() { + return "boolean {} Clears the interrupt status of the current thread. If this call actually cleared the interrupt status," + + " true is returned, if the thread was already not interrupted, false is returned."; + } + } } diff --git a/src/main/java/com/laytonsmith/core/functions/Trades.java b/src/main/java/com/laytonsmith/core/functions/Trades.java new file mode 100644 index 0000000000..8493ca3b57 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/functions/Trades.java @@ -0,0 +1,453 @@ +package com.laytonsmith.core.functions; + +import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.abstraction.MCEntity; +import com.laytonsmith.abstraction.MCItemStack; +import com.laytonsmith.abstraction.MCMerchant; +import com.laytonsmith.abstraction.MCMerchantRecipe; +import com.laytonsmith.abstraction.MCPlayer; +import com.laytonsmith.abstraction.StaticLayer; +import com.laytonsmith.abstraction.entities.MCTrader; +import com.laytonsmith.abstraction.enums.MCRecipeType; +import com.laytonsmith.annotations.api; +import com.laytonsmith.core.ArgumentValidation; +import com.laytonsmith.core.MSVersion; +import com.laytonsmith.core.ObjectGenerator; +import com.laytonsmith.core.Static; +import com.laytonsmith.core.constructs.CArray; +import com.laytonsmith.core.constructs.CBoolean; +import com.laytonsmith.core.constructs.CInt; +import com.laytonsmith.core.constructs.CNull; +import com.laytonsmith.core.constructs.CString; +import com.laytonsmith.core.constructs.CVoid; +import com.laytonsmith.core.constructs.Target; +import com.laytonsmith.core.environments.CommandHelperEnvironment; +import com.laytonsmith.core.environments.Environment; +import com.laytonsmith.core.exceptions.CRE.CREBadEntityException; +import com.laytonsmith.core.exceptions.CRE.CRECastException; +import com.laytonsmith.core.exceptions.CRE.CREFormatException; +import com.laytonsmith.core.exceptions.CRE.CREIllegalArgumentException; +import com.laytonsmith.core.exceptions.CRE.CRELengthException; +import com.laytonsmith.core.exceptions.CRE.CRENotFoundException; +import com.laytonsmith.core.exceptions.CRE.CREPlayerOfflineException; +import com.laytonsmith.core.exceptions.CRE.CRERangeException; +import com.laytonsmith.core.exceptions.CRE.CREThrowable; +import com.laytonsmith.core.exceptions.ConfigRuntimeException; +import com.laytonsmith.core.natives.interfaces.Mixed; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class Trades { + + public static String docs() { + return "Functions related to the management of trades and merchants. A trade is a special kind of recipe" + + " accessed through the merchant interface. A merchant is a provider of trades," + + " which may or may not be attached to a Villager or Wandering Trader."; + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class get_merchant_trades extends Recipes.recipeFunction { + + @Override + public Class[] thrown() { + return new Class[]{CREIllegalArgumentException.class, CREFormatException.class, CREBadEntityException.class}; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + CArray ret = new CArray(t); + for(MCMerchantRecipe mr : GetMerchant(args[0], t).getRecipes()) { + ret.push(trade(mr, t), t); + } + return ret; + } + + @Override + public Version since() { + return MSVersion.V3_3_3; + } + + @Override + public String getName() { + return "get_merchant_trades"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1}; + } + + @Override + public String docs() { + return "array {specifier} Returns a list of trades used by the specified merchant." + + " Specifier can be the UUID of an entity or a virtual merchant ID."; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class set_merchant_trades extends Recipes.recipeFunction { + + @Override + public Class[] thrown() { + return new Class[]{CREIllegalArgumentException.class, CREFormatException.class, CRECastException.class, + CREBadEntityException.class, CRENotFoundException.class}; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCMerchant merchant = GetMerchant(args[0], t); + CArray trades = ArgumentValidation.getArray(args[1], t); + List recipes = new ArrayList<>(); + if(trades.isAssociative()) { + throw new CRECastException("Expected non-associative array for list of trade arrays.", t); + } + for(Mixed trade : trades.asList()) { + recipes.add(trade(trade, t)); + } + merchant.setRecipes(recipes); + return CVoid.VOID; + } + + @Override + public Version since() { + return MSVersion.V3_3_3; + } + + @Override + public String getName() { + return "set_merchant_trades"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{2}; + } + + @Override + public String docs() { + return "void {specifier, array} Sets the list of trades the specified merchant can use to the provided" + + " array of TradeArrays. The specifier can be the UUID of a physical entity or the ID" + + " of a user-created virtual merchant. ----" + + " TradeArrays are similar to RecipeArray format and contain the following keys:" + + "
"
+					+ "   result: The result item array of the trade.\n"
+					+ "   ingredients: Items the player must provide. Must be 1 or 2 itemstacks.\n"
+					+ "   uses: (Optional) The number of times the recipe has been used. Defaults to 0."
+					+ " Note: this number is not kept in sync between merchants and the master list.\n"
+					+ "   maxuses: (Optional) The maximum number of times this trade can be made before it is disabled."
+					+ " Defaults to " + Integer.MAX_VALUE + ".\n"
+					+ "   hasxpreward: (Optional) Whether xp is given to the player for making this trade."
+					+ " Defaults to true."
+					+ " 
" + + " Example 1: Turns 9 stone into obsidian." + + "
"
+					+ "{\n"
+					+ "    result: {name: OBSIDIAN},\n"
+					+ "    ingredients: {{name: STONE, qty: 9}}\n"
+					+ "}"
+					+ "
" + + " Example 2: Combines a diamond and dirt to make grass, but only once." + + "
"
+					+ "{\n"
+					+ "    result: {name: 'GRASS'},\n"
+					+ "    ingredients: {{name: 'DIRT'}, {name: 'DIAMOND'}}\n"
+					+ "    maxuses: 1\n"
+					+ "}"
+					+ "
"; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class get_virtual_merchants extends Recipes.recipeFunction { + + @Override + public Class[] thrown() { + return new Class[0]; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + CArray ret = CArray.GetAssociativeArray(t); + for(Map.Entry entry : VIRTUAL_MERCHANTS.entrySet()) { + if(entry.getValue() == null) { + VIRTUAL_MERCHANTS.remove(entry.getKey()); + continue; + } + ret.set(entry.getKey(), entry.getValue().getTitle(), t); + } + return ret; + } + + @Override + public Version since() { + return MSVersion.V3_3_3; + } + + @Override + public String getName() { + return "get_virtual_merchants"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{0}; + } + + @Override + public String docs() { + return "array {} Returns an array where the keys are currently registered merchant IDs and the values are" + + " the corresponding window titles of those merchants."; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class create_virtual_merchant extends Recipes.recipeFunction { + + @Override + public Class[] thrown() { + return new Class[] {CREIllegalArgumentException.class}; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + if(VIRTUAL_MERCHANTS.containsKey(args[0].val())) { + throw new CREIllegalArgumentException("There is already a merchant with id " + args[0].val(), t); + } else { + VIRTUAL_MERCHANTS.put(args[0].val(), Static.getServer().createMerchant(args[1].val())); + return CVoid.VOID; + } + } + + @Override + public Version since() { + return MSVersion.V3_3_3; + } + + @Override + public String getName() { + return "create_virtual_merchant"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{2}; + } + + @Override + public String docs() { + return "void {ID, title} Creates a merchant that can be traded with by players but is not attached to" + + " a physical entity. The ID given should not be a UUID. The title is the text that will display" + + " at the top of the window while a player is trading with it. Created merchants will persist" + + " across recompiles, but not across server restarts. An exception will be thrown if a merchant" + + " already exists using the given ID."; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class delete_virtual_merchant extends Recipes.recipeFunction { + + @Override + public Class[] thrown() { + return new Class[0]; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + return CBoolean.get(VIRTUAL_MERCHANTS.remove(args[0].val()) != null); + } + + @Override + public Version since() { + return MSVersion.V3_3_3; + } + + @Override + public String getName() { + return "delete_virtual_merchant"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1}; + } + + @Override + public String docs() { + return "boolean {string} Deletes a virtual merchant if one by the given ID exists. Returns true if" + + " one was removed, or false if there was no match for the ID."; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class popen_trading extends Recipes.recipeFunction { + + @Override + public Class[] thrown() { + return new Class[]{CREPlayerOfflineException.class, CRELengthException.class, + CREIllegalArgumentException.class, CREBadEntityException.class, CREFormatException.class}; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCPlayer player; + boolean force = false; + if(args.length > 1) { + player = Static.GetPlayer(args[1], t); + } else { + player = Static.getPlayer(env, t); + } + if(args.length == 3) { + force = ArgumentValidation.getBooleanish(args[2], t); + } + MCMerchant merchant = GetMerchant(args[0], t); + if(!force && merchant.isTrading()) { + return CBoolean.FALSE; + } + return CBoolean.get(player.openMerchant(merchant, force) != null); + } + + @Override + public Version since() { + return MSVersion.V3_3_3; + } + + @Override + public String getName() { + return "popen_trading"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1, 2, 3}; + } + + @Override + public String docs() { + return "boolean {specifier, [player], [force]} Opens a trading interface for the current player," + + " or the one specified. Only one player can trade with a merchant at a time." + + " If the merchant is already being traded with, the function will do nothing." + + " When force is set to true, this function will first close trading with any other player." + + " Function returns true if trading was successfully opened, and false if not."; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class merchant_trader extends Recipes.recipeFunction { + + @Override + public Class[] thrown() { + return new Class[]{CREBadEntityException.class, CREFormatException.class, CREFormatException.class, + CREIllegalArgumentException.class, CRELengthException.class}; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCMerchant merchant = GetMerchant(args[0], t); + return merchant.isTrading() ? new CString(merchant.getTrader().getUniqueId().toString(), t) : CNull.NULL; + } + + @Override + public Version since() { + return MSVersion.V3_3_3; + } + + @Override + public String getName() { + return "merchant_trader"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1}; + } + + @Override + public String docs() { + return "UUID {specifier} Returns the UUID of the user trading with the merchant, or null if no one is."; + } + } + + private static final HashMap VIRTUAL_MERCHANTS = new HashMap<>(); + + /** + * Returns the merchant specified. + * @param specifier The string representing the merchant, whether entity UUID or virtual id. + * @param t + * @return abstracted merchant + */ + private static MCMerchant GetMerchant(Mixed specifier, Target t) { + MCMerchant merchant; + if(specifier.val().length() == 36 || specifier.val().length() == 32) { + try { + MCEntity entity = Static.getEntity(specifier, t); + if(!(entity instanceof MCTrader)) { + throw new CREIllegalArgumentException("The entity specified is not capable of being an merchant.", t); + } + return ((MCTrader) entity).asMerchant(); + } catch (CREFormatException iae) { + // not a UUID + } + } + merchant = VIRTUAL_MERCHANTS.get(specifier.val()); + if(merchant == null) { + throw new CREIllegalArgumentException("A merchant named \"" + specifier.val() + "\" does not exist.", t); + } + return merchant; + } + + private static MCMerchantRecipe trade(Mixed c, Target t) { + + CArray recipe = ArgumentValidation.getArray(c, t); + + MCItemStack result = ObjectGenerator.GetGenerator().item(recipe.get("result", t), t); + + MCMerchantRecipe mer = (MCMerchantRecipe) StaticLayer.GetNewRecipe(null, MCRecipeType.MERCHANT, result); + + if(recipe.containsKey("maxuses")) { + mer.setMaxUses(ArgumentValidation.getInt32(recipe.get("maxuses", t), t)); + } + if(recipe.containsKey("uses")) { + mer.setUses(ArgumentValidation.getInt32(recipe.get("uses", t), t)); + } + if(recipe.containsKey("hasxpreward")) { + mer.setHasExperienceReward(ArgumentValidation.getBoolean(recipe.get("hasxpreward", t), t)); + } + + CArray ingredients = ArgumentValidation.getArray(recipe.get("ingredients", t), t); + if(ingredients.inAssociativeMode()) { + throw new CREFormatException("Ingredients array is invalid.", t); + } + if(ingredients.size() < 1 || ingredients.size() > 2) { + throw new CRERangeException("Ingredients for merchants must contain 1 or 2 items, found " + + ingredients.size(), t); + } + List mcIngredients = new ArrayList<>(); + for(Mixed ingredient : ingredients.asList()) { + mcIngredients.add(ObjectGenerator.GetGenerator().item(ingredient, t)); + } + mer.setIngredients(mcIngredients); + + return mer; + } + + private static Mixed trade(MCMerchantRecipe r, Target t) { + if(r == null) { + return CNull.NULL; + } + CArray ret = CArray.GetAssociativeArray(t); + ret.set("result", ObjectGenerator.GetGenerator().item(r.getResult(), t), t); + CArray il = new CArray(t); + for(MCItemStack i : r.getIngredients()) { + il.push(ObjectGenerator.GetGenerator().item(i, t), t); + } + ret.set("ingredients", il, t); + ret.set("maxuses", new CInt(r.getMaxUses(), t), t); + ret.set("uses", new CInt(r.getUses(), t), t); + ret.set("hasxpreward", CBoolean.get(r.hasExperienceReward()), t); + + return ret; + } +} diff --git a/src/main/java/com/laytonsmith/core/functions/Weather.java b/src/main/java/com/laytonsmith/core/functions/Weather.java index d2f76ace67..a221e23b08 100644 --- a/src/main/java/com/laytonsmith/core/functions/Weather.java +++ b/src/main/java/com/laytonsmith/core/functions/Weather.java @@ -1,27 +1,37 @@ package com.laytonsmith.core.functions; +import com.laytonsmith.abstraction.MCBlockCommandSender; +import com.laytonsmith.abstraction.MCCommandSender; import com.laytonsmith.abstraction.MCLocation; import com.laytonsmith.abstraction.MCPlayer; import com.laytonsmith.abstraction.MCWorld; import com.laytonsmith.abstraction.StaticLayer; +import com.laytonsmith.abstraction.entities.MCCommandMinecart; import com.laytonsmith.annotations.api; -import com.laytonsmith.core.CHVersion; +import com.laytonsmith.core.ArgumentValidation; +import com.laytonsmith.core.MSVersion; import com.laytonsmith.core.ObjectGenerator; import com.laytonsmith.core.Static; import com.laytonsmith.core.constructs.CArray; import com.laytonsmith.core.constructs.CBoolean; import com.laytonsmith.core.constructs.CInt; +import com.laytonsmith.core.constructs.CString; import com.laytonsmith.core.constructs.CVoid; -import com.laytonsmith.core.constructs.Construct; import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.environments.CommandHelperEnvironment; import com.laytonsmith.core.environments.Environment; -import com.laytonsmith.core.exceptions.CancelCommandException; +import com.laytonsmith.core.exceptions.CRE.CRECastException; +import com.laytonsmith.core.exceptions.CRE.CREFormatException; +import com.laytonsmith.core.exceptions.CRE.CREInvalidWorldException; +import com.laytonsmith.core.exceptions.CRE.CRELengthException; +import com.laytonsmith.core.exceptions.CRE.CREThrowable; import com.laytonsmith.core.exceptions.ConfigRuntimeException; -import com.laytonsmith.core.functions.Exceptions.ExceptionType; +import com.laytonsmith.core.natives.interfaces.Mixed; + +import java.util.UUID; /** - * + * */ public class Weather { @@ -29,7 +39,7 @@ public static String docs() { return "Provides functions to control the weather"; } - @api(environments=CommandHelperEnvironment.class) + @api(environments = CommandHelperEnvironment.class) public static class lightning extends AbstractFunction { @Override @@ -43,52 +53,57 @@ public Integer[] numArgs() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException, ExceptionType.LengthException, ExceptionType.InvalidWorldException, ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CRECastException.class, CRELengthException.class, CREInvalidWorldException.class, + CREFormatException.class}; } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { - int x, y, z, ent; + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + int x; + int y; + int z; + UUID ent; MCWorld w = null; boolean safe = false; int safeIndex = 1; - if (args[0] instanceof CArray) { + if(args[0].isInstanceOf(CArray.TYPE)) { CArray a = (CArray) args[0]; - MCLocation l = ObjectGenerator.GetGenerator().location(a, (env.getEnv(CommandHelperEnvironment.class).GetCommandSender() instanceof MCPlayer ? env.getEnv(CommandHelperEnvironment.class).GetPlayer().getWorld() : null), t); + MCPlayer p = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); + MCLocation l = ObjectGenerator.GetGenerator().location(a, p == null ? null : p.getWorld(), t); x = (int) java.lang.Math.floor(l.getX()); y = (int) java.lang.Math.floor(l.getY()); z = (int) java.lang.Math.floor(l.getZ()); w = l.getWorld(); } else { - x = (int) java.lang.Math.floor(Static.getNumber(args[0], t)); - y = (int) java.lang.Math.floor(Static.getNumber(args[1], t)); - z = (int) java.lang.Math.floor(Static.getNumber(args[2], t)); + x = (int) java.lang.Math.floor(ArgumentValidation.getNumber(args[0], t)); + y = (int) java.lang.Math.floor(ArgumentValidation.getNumber(args[1], t)); + z = (int) java.lang.Math.floor(ArgumentValidation.getNumber(args[2], t)); safeIndex = 3; } - if (args.length >= safeIndex + 1) { - safe = Static.getBoolean(args[safeIndex]); + if(args.length >= safeIndex + 1) { + safe = ArgumentValidation.getBoolean(args[safeIndex], t); } - if (w != null) { - if (!safe) { - ent = w.strikeLightning(StaticLayer.GetLocation(w, x, y + 1, z)).getEntityId(); + if(w != null) { + if(!safe) { + ent = w.strikeLightning(StaticLayer.GetLocation(w, x, y + 1, z)).getUniqueId(); } else { - ent = w.strikeLightningEffect(StaticLayer.GetLocation(w, x, y + 1, z)).getEntityId(); + ent = w.strikeLightningEffect(StaticLayer.GetLocation(w, x, y + 1, z)).getUniqueId(); } } else { - throw new ConfigRuntimeException("World was not specified", ExceptionType.InvalidWorldException, t); + throw new CREInvalidWorldException("World was not specified", t); } - return new CInt(ent, t); + return new CString(ent.toString(), t); } @Override public String docs() { - return "int {strikeLocArray, [safe] | x, y, z, [safe]} Makes" + return "string {locationArray, [safe] | x, y, z, [safe]} Makes" + " lightning strike at the x y z coordinates specified" - + " in the array(x, y, z). Safe defaults to false, but" + + " in the array or arguments. Safe defaults to false, but" + " if true, lightning striking a player will not hurt" - + " them. Returns the entityID of the lightning bolt."; + + " them. Returns the UUID of the lightning bolt entity."; } @Override @@ -97,8 +112,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_0_1; + public MSVersion since() { + return MSVersion.V3_0_1; } @Override @@ -107,7 +122,7 @@ public Boolean runAsync() { } } - @api(environments=CommandHelperEnvironment.class) + @api(environments = CommandHelperEnvironment.class) public static class storm extends AbstractFunction { @Override @@ -121,22 +136,44 @@ public Integer[] numArgs() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { - boolean b = Static.getBoolean(args[0]); + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + boolean b = ArgumentValidation.getBoolean(args[0], t); MCWorld w = null; - if (env.getEnv(CommandHelperEnvironment.class).GetCommandSender() instanceof MCPlayer) { - w = env.getEnv(CommandHelperEnvironment.class).GetPlayer().getWorld(); + int duration = -1; + if(args.length == 2) { + if(args[1].isInstanceOf(CString.TYPE)) { + w = Static.getServer().getWorld(args[1].val()); + } else if(args[1].isInstanceOf(CInt.TYPE)) { + duration = ArgumentValidation.getInt32(args[1], t); + } else { + throw new CREFormatException("", t); + } } - if (args.length == 2) { + if(args.length == 3) { w = Static.getServer().getWorld(args[1].val()); + duration = ArgumentValidation.getInt32(args[2], t); } - if (w != null) { + if(w == null) { + MCCommandSender sender = env.getEnv(CommandHelperEnvironment.class).GetCommandSender(); + if(sender instanceof MCPlayer) { + w = ((MCPlayer) sender).getWorld(); + } else if(sender instanceof MCBlockCommandSender) { + w = ((MCBlockCommandSender) sender).getBlock().getWorld(); + } else if(sender instanceof MCCommandMinecart) { + w = ((MCCommandMinecart) sender).getWorld(); + } + } + if(w != null) { w.setStorm(b); + if(duration > 0) { + if(b) { + w.setWeatherDuration(duration); + } else { + w.setClearWeatherDuration(duration); + } + } } else { - throw new ConfigRuntimeException("World was not specified", ExceptionType.InvalidWorldException, t); - } - if (args.length == 3) { - w.setWeatherDuration(Static.getInt32(args[2], t)); + throw new CREInvalidWorldException("World was not specified", t); } return CVoid.VOID; } @@ -144,12 +181,15 @@ public Construct exec(Target t, Environment env, Construct... args) throws Cance @Override public String docs() { return "void {isStorming, [world], [int]} Creates a (rain) storm if isStorming is true, stops a storm if" - + " isStorming is false. The third argument allows setting how long this weather setting will last."; + + " isStorming is false. The second argument can be a world name or the duration in ticks of the" + + " given weather setting. The third argument allows specifying both a world and a duration." + + " The second param is required to be the world if the function is run from console."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException, ExceptionType.InvalidWorldException}; + public Class[] thrown() { + return new Class[]{CRECastException.class, CREFormatException.class, + CREInvalidWorldException.class}; } @Override @@ -158,8 +198,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_0_1; + public MSVersion since() { + return MSVersion.V3_0_1; } @Override @@ -167,13 +207,13 @@ public Boolean runAsync() { return false; } } - - @api(environments=CommandHelperEnvironment.class) + + @api(environments = CommandHelperEnvironment.class) public static class set_thunder extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException, ExceptionType.CastException}; + public Class[] thrown() { + return new Class[]{CREInvalidWorldException.class, CRECastException.class}; } @Override @@ -187,23 +227,22 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, - Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCWorld w = null; - if (args.length == 1) { - if (environment.getEnv(CommandHelperEnvironment.class).GetCommandSender() instanceof MCPlayer) { + if(args.length == 1) { + if(environment.getEnv(CommandHelperEnvironment.class).GetCommandSender() instanceof MCPlayer) { w = environment.getEnv(CommandHelperEnvironment.class).GetPlayer().getWorld(); } } else { w = Static.getServer().getWorld(args[1].val()); } - if (w != null) { - w.setThundering(Static.getBoolean(args[0])); + if(w != null) { + w.setThundering(ArgumentValidation.getBoolean(args[0], t)); } else { - throw new ConfigRuntimeException("No existing world specified!", ExceptionType.InvalidWorldException, t); + throw new CREInvalidWorldException("No existing world specified!", t); } - if (args.length == 3) { - w.setThunderDuration(Static.getInt32(args[2], t)); + if(args.length == 3) { + w.setThunderDuration(ArgumentValidation.getInt32(args[2], t)); } return CVoid.VOID; } @@ -221,22 +260,22 @@ public Integer[] numArgs() { @Override public String docs() { return "void {boolean, [world], [int]} Sets whether or not the weather can have thunder. The third argument" - + " can specify how long the thunder should last."; + + " can specify how long the thunder should last in server ticks."; } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } - + } - - @api(environments=CommandHelperEnvironment.class) + + @api(environments = CommandHelperEnvironment.class) public static class has_storm extends AbstractFunction { - + @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException}; + public Class[] thrown() { + return new Class[]{CREInvalidWorldException.class}; } @Override @@ -250,20 +289,19 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, - Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCWorld w = null; - if (args.length == 1) { + if(args.length == 1) { w = Static.getServer().getWorld(args[0].val()); } else { - if (environment.getEnv(CommandHelperEnvironment.class).GetCommandSender() instanceof MCPlayer) { + if(environment.getEnv(CommandHelperEnvironment.class).GetCommandSender() instanceof MCPlayer) { w = environment.getEnv(CommandHelperEnvironment.class).GetPlayer().getWorld(); } } - if (w != null) { + if(w != null) { return CBoolean.get(w.isStorming()); } else { - throw new ConfigRuntimeException("No existing world specified!", ExceptionType.InvalidWorldException, t); + throw new CREInvalidWorldException("No existing world specified!", t); } } @@ -283,18 +321,18 @@ public String docs() { } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } - + } - - @api(environments=CommandHelperEnvironment.class) + + @api(environments = CommandHelperEnvironment.class) public static class has_thunder extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException}; + public Class[] thrown() { + return new Class[]{CREInvalidWorldException.class}; } @Override @@ -308,20 +346,19 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, - Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCWorld w = null; - if (args.length == 1) { + if(args.length == 1) { w = Static.getServer().getWorld(args[0].val()); } else { - if (environment.getEnv(CommandHelperEnvironment.class).GetCommandSender() instanceof MCPlayer) { + if(environment.getEnv(CommandHelperEnvironment.class).GetCommandSender() instanceof MCPlayer) { w = environment.getEnv(CommandHelperEnvironment.class).GetPlayer().getWorld(); } } - if (w != null) { + if(w != null) { return CBoolean.get(w.isThundering()); } else { - throw new ConfigRuntimeException("No existing world specified!", ExceptionType.InvalidWorldException, t); + throw new CREInvalidWorldException("No existing world specified!", t); } } @@ -341,9 +378,9 @@ public String docs() { } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } - + } } diff --git a/src/main/java/com/laytonsmith/core/functions/Web.java b/src/main/java/com/laytonsmith/core/functions/Web.java index 9b09f47df6..d07fe19bc2 100644 --- a/src/main/java/com/laytonsmith/core/functions/Web.java +++ b/src/main/java/com/laytonsmith/core/functions/Web.java @@ -1,5 +1,6 @@ package com.laytonsmith.core.functions; +import com.laytonsmith.core.FileWriteMode; import com.laytonsmith.PureUtilities.Common.StackTraceUtils; import com.laytonsmith.PureUtilities.Common.StringUtils; import com.laytonsmith.PureUtilities.Version; @@ -14,9 +15,10 @@ import com.laytonsmith.annotations.api; import com.laytonsmith.annotations.core; import com.laytonsmith.annotations.noboilerplate; +import com.laytonsmith.annotations.seealso; import com.laytonsmith.core.ArgumentValidation; -import com.laytonsmith.core.CHLog; -import com.laytonsmith.core.CHVersion; +import com.laytonsmith.core.MSLog; +import com.laytonsmith.core.MSVersion; import com.laytonsmith.core.EmailProfile; import com.laytonsmith.core.LogLevel; import com.laytonsmith.core.MethodScriptFileLocations; @@ -36,14 +38,19 @@ import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.environments.Environment; import com.laytonsmith.core.environments.GlobalEnv; +import com.laytonsmith.core.environments.StaticRuntimeEnv; +import com.laytonsmith.core.exceptions.CRE.CRECastException; +import com.laytonsmith.core.exceptions.CRE.CREFormatException; +import com.laytonsmith.core.exceptions.CRE.CREIOException; +import com.laytonsmith.core.exceptions.CRE.CREPluginInternalException; +import com.laytonsmith.core.exceptions.CRE.CREThrowable; import com.laytonsmith.core.exceptions.ConfigCompileException; import com.laytonsmith.core.exceptions.ConfigRuntimeException; -import com.laytonsmith.core.exceptions.FunctionReturnException; import com.laytonsmith.core.exceptions.ProgramFlowManipulationException; -import com.laytonsmith.core.functions.Exceptions.ExceptionType; +import com.laytonsmith.core.natives.interfaces.ArrayAccess; +import com.laytonsmith.core.natives.interfaces.Mixed; import com.laytonsmith.tools.docgen.DocGenTemplates; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -61,6 +68,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; @@ -69,8 +77,10 @@ import java.util.concurrent.ThreadFactory; import java.util.logging.Level; import java.util.logging.Logger; +import javax.activation.CommandMap; import javax.activation.DataHandler; import javax.activation.DataSource; +import javax.activation.MailcapCommandMap; import javax.mail.Authenticator; import javax.mail.BodyPart; import javax.mail.Message; @@ -83,35 +93,35 @@ import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; -import org.apache.commons.codec.binary.Base64; /** * */ @core public class Web { - public static String docs(){ + + public static String docs() { return "Contains various methods to make HTTP requests."; } - private static void getCookieJar(CArray arrayJar, CookieJar cookieJar, Target t){ + private static void getCookieJar(CArray arrayJar, CookieJar cookieJar, Target t) { CArray ret = arrayJar; - for(Cookie cookie : cookieJar.getAllCookies()){ + for(Cookie cookie : cookieJar.getAllCookies()) { boolean update = false; CArray aCookie = null; - for(Construct ac : arrayJar.asList()){ - aCookie = Static.getArray(ac, t); + for(Mixed ac : arrayJar.asList()) { + aCookie = ArgumentValidation.getArray(ac, t); if(cookie.getName().equals(aCookie.get("name", t).val()) && cookie.getDomain().equals(aCookie.get("domain", t).val()) - && cookie.getPath().equals(aCookie.get("path", t).val())){ + && cookie.getPath().equals(aCookie.get("path", t).val())) { //This is just an update, not a new cookie update = true; break; } } CArray c; - if(!update){ - c = new CArray(t); + if(!update) { + c = CArray.GetAssociativeArray(t); } else { c = aCookie; } @@ -122,16 +132,16 @@ private static void getCookieJar(CArray arrayJar, CookieJar cookieJar, Target t) c.set("expiration", new CInt(cookie.getExpiration(), t), t); c.set("httpOnly", CBoolean.get(cookie.isHttpOnly()), t); c.set("secureOnly", CBoolean.get(cookie.isSecureOnly()), t); - if(!update){ - ret.push(c); + if(!update) { + ret.push(c, t); } } } - private static CookieJar getCookieJar(CArray cookieJar, Target t){ + private static CookieJar getCookieJar(CArray cookieJar, Target t) { CookieJar ret = new CookieJar(); - for(String key : cookieJar.stringKeySet()){ - CArray cookie = Static.getArray(cookieJar.get(key, t), t); + for(String key : cookieJar.stringKeySet()) { + CArray cookie = ArgumentValidation.getArray(cookieJar.get(key, t), t); String name; String value; String domain; @@ -140,23 +150,23 @@ private static CookieJar getCookieJar(CArray cookieJar, Target t){ boolean httpOnly = false; boolean secureOnly = false; if(cookie.containsKey("name") && cookie.containsKey("value") - && cookie.containsKey("domain") && cookie.containsKey("path")){ + && cookie.containsKey("domain") && cookie.containsKey("path")) { name = cookie.get("name", t).val(); value = cookie.get("value", t).val(); domain = cookie.get("domain", t).val(); path = cookie.get("path", t).val(); } else { - throw new ConfigRuntimeException("The name, value, domain, and path keys are required" - + " in all cookies.", ExceptionType.FormatException, t); + throw new CREFormatException("The name, value, domain, and path keys are required" + + " in all cookies.", t); } - if(cookie.containsKey("expiration")){ - expiration = Static.getInt(cookie.get("expiration", t), t); + if(cookie.containsKey("expiration")) { + expiration = ArgumentValidation.getInt(cookie.get("expiration", t), t); } - if(cookie.containsKey("httpOnly")){ - httpOnly = Static.getBoolean(cookie.get("httpOnly", t)); + if(cookie.containsKey("httpOnly")) { + httpOnly = ArgumentValidation.getBoolean(cookie.get("httpOnly", t), t); } - if(cookie.containsKey("secureOnly")){ - secureOnly = Static.getBoolean(cookie.get("secureOnly", t)); + if(cookie.containsKey("secureOnly")) { + secureOnly = ArgumentValidation.getBoolean(cookie.get("secureOnly", t), t); } Cookie c = new Cookie(name, value, domain, path, expiration, httpOnly, secureOnly); ret.addCookie(c); @@ -165,16 +175,16 @@ private static CookieJar getCookieJar(CArray cookieJar, Target t){ } @api + @seealso({http_clear_session_cookies.class}) public static class http_request extends AbstractFunction { + /** - * Defines the max number of HTTP threads that will run at any given time. - * Each web_request uses one thread in this pool, and so should not be used - * for long running requests, as other requests would be starved. + * Defines the max number of HTTP threads that will run at any given time. Each web_request uses one thread in + * this pool, and so should not be used for long running requests, as other requests would be starved. */ private static final int MAX_HTTP_THREADS = 3; private static int threadCount = 0; - private static final ExecutorService threadPool = Executors.newFixedThreadPool(MAX_HTTP_THREADS, new ThreadFactory() { - + private static final ExecutorService THREAD_POOL = Executors.newFixedThreadPool(MAX_HTTP_THREADS, new ThreadFactory() { @Override public Thread newThread(Runnable r) { @@ -183,17 +193,18 @@ public Thread newThread(Runnable r) { }); private static final Map DEFAULT_HEADERS = new HashMap(); - static{ + + static { DEFAULT_HEADERS.put("Accept", "text/*, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8"); - DEFAULT_HEADERS.put("Accept-Encoding", "gzip, deflate, identity"); + DEFAULT_HEADERS.put("Accept-Encoding", StringUtils.Join(WebUtility.SUPPORTED_ENCODINGS, ", ")); DEFAULT_HEADERS.put("User-Agent", "Java/" + System.getProperty("java.version") + "/" + Implementation.GetServerType().getBranding()); DEFAULT_HEADERS.put("DNT", "1"); DEFAULT_HEADERS.put("Connection", "close"); } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CREFormatException.class}; } @Override @@ -207,43 +218,52 @@ public Boolean runAsync() { } @Override - public Construct exec(final Target t, final Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(final Target t, final Environment environment, Mixed... args) throws ConfigRuntimeException { final URL url; try { url = new URL(args[0].val()); } catch (MalformedURLException ex) { - throw new Exceptions.FormatException(ex.getMessage(), t); + throw new CREFormatException(ex.getMessage(), t); } final RequestSettings settings = new RequestSettings(); final CClosure success; final CClosure error; final CArray arrayJar; - if(args[1] instanceof CClosure){ + final boolean binary; + final String textEncoding; + boolean useDefaultHeaders = true; + if(args[1].isInstanceOf(CClosure.TYPE)) { success = (CClosure) args[1]; error = null; arrayJar = null; + binary = false; + textEncoding = "UTF-8"; + Map> headers = new HashMap<>(); + for(String key : DEFAULT_HEADERS.keySet()) { + headers.put(key, Arrays.asList(DEFAULT_HEADERS.get(key))); + } + settings.setHeaders(headers); } else { - CArray csettings = Static.getArray(args[1], t); - if(csettings.containsKey("method")){ - try{ + CArray csettings = ArgumentValidation.getArray(args[1], t); + if(csettings.containsKey("method")) { + try { settings.setMethod(HTTPMethod.valueOf(csettings.get("method", t).val())); - } catch(IllegalArgumentException e){ - throw new Exceptions.FormatException(e.getMessage(), t); + } catch (IllegalArgumentException e) { + throw new CREFormatException(e.getMessage(), t); } } - boolean useDefaultHeaders = true; - if(csettings.containsKey("useDefaultHeaders")){ - useDefaultHeaders = Static.getBoolean(csettings.get("useDefaultHeaders", t)); + if(csettings.containsKey("useDefaultHeaders")) { + useDefaultHeaders = ArgumentValidation.getBoolean(csettings.get("useDefaultHeaders", t), t); } - if(csettings.containsKey("headers") && !(csettings.get("headers", t) instanceof CNull)){ - CArray headers = Static.getArray(csettings.get("headers", t), t); + if(csettings.containsKey("headers") && !(csettings.get("headers", t) instanceof CNull)) { + CArray headers = ArgumentValidation.getArray(csettings.get("headers", t), t); Map> mheaders = new HashMap>(); - for(String key : headers.stringKeySet()){ + for(String key : headers.stringKeySet()) { List h = new ArrayList(); - Construct c = headers.get(key, t); - if(c instanceof CArray){ - for(String kkey : ((CArray)c).stringKeySet()){ - h.add(((CArray)c).get(kkey, t).val()); + Mixed c = headers.get(key, t); + if(c.isInstanceOf(CArray.TYPE)) { + for(String kkey : ((CArray) c).stringKeySet()) { + h.add(((CArray) c).get(kkey, t).val()); } } else { h.add(c.val()); @@ -252,12 +272,13 @@ public Construct exec(final Target t, final Environment environment, Construct.. } settings.setHeaders(mheaders); } else { - settings.setHeaders(new HashMap>()); + settings.setHeaders(new HashMap<>()); } - if(useDefaultHeaders){ - outer: for(String key : DEFAULT_HEADERS.keySet()){ - for(String k2 : settings.getHeaders().keySet()){ - if(key.equalsIgnoreCase(k2)){ + if(useDefaultHeaders) { + outer: + for(String key : DEFAULT_HEADERS.keySet()) { + for(String k2 : settings.getHeaders().keySet()) { + if(key.equalsIgnoreCase(k2)) { //They have already included this header, so let's not touch it. continue outer; } @@ -266,16 +287,16 @@ public Construct exec(final Target t, final Environment environment, Construct.. settings.getHeaders().put(key, Arrays.asList(DEFAULT_HEADERS.get(key))); } } - if(csettings.containsKey("params") && !(csettings.get("params", t) instanceof CNull)){ - if(csettings.get("params", t) instanceof CArray){ - CArray params = Static.getArray(csettings.get("params", t), t); - Map> mparams = new HashMap>(); - for(String key : params.stringKeySet()){ - Construct c = params.get(key, t); - List l = new ArrayList(); - if(c instanceof CArray){ - for(String kkey : ((CArray)c).stringKeySet()){ - l.add(((CArray)c).get(kkey, t).val()); + if(csettings.containsKey("params") && !(csettings.get("params", t) instanceof CNull)) { + if(csettings.get("params", t).isInstanceOf(CArray.TYPE)) { + CArray params = ArgumentValidation.getArray(csettings.get("params", t), t); + Map> mparams = new HashMap<>(); + for(String key : params.stringKeySet()) { + Mixed c = params.get(key, t); + List l = new ArrayList<>(); + if(c.isInstanceOf(CArray.TYPE)) { + for(String kkey : ((CArray) c).stringKeySet()) { + l.add(((ArrayAccess) c).get(kkey, t).val()); } } else { l.add(c.val()); @@ -284,98 +305,173 @@ public Construct exec(final Target t, final Environment environment, Construct.. } settings.setComplexParameters(mparams); } else { - settings.setRawParameter(csettings.get("params", t).val()); - if(settings.getMethod() != HTTPMethod.POST){ - throw new Exceptions.FormatException("You must set the method to POST to use raw params.", t); + if(csettings.get("params", t).isInstanceOf(CByteArray.TYPE)) { + CByteArray b = (CByteArray) csettings.get("params", t); + settings.setRawParameter(b.asByteArrayCopy()); + } else { + try { + settings.setRawParameter(csettings.get("params", t).val().getBytes("UTF-8")); + } catch (UnsupportedEncodingException ex) { + throw new Error(ex); + } } } } - if(csettings.containsKey("cookiejar") && !(csettings.get("cookiejar", t) instanceof CNull)){ - arrayJar = Static.getArray(csettings.get("cookiejar", t), t); + if(csettings.containsKey("cookiejar") && !(csettings.get("cookiejar", t) instanceof CNull)) { + arrayJar = ArgumentValidation.getArray(csettings.get("cookiejar", t), t); settings.setCookieJar(getCookieJar(arrayJar, t)); } else { arrayJar = null; } - if(csettings.containsKey("followRedirects")){ - settings.setFollowRedirects(Static.getBoolean(csettings.get("followRedirects", t))); + if(csettings.containsKey("followRedirects")) { + settings.setFollowRedirects(ArgumentValidation.getBoolean(csettings.get("followRedirects", t), t)); } //Only required parameter - if(csettings.containsKey("success")){ - if(csettings.get("success", t) instanceof CClosure){ + if(csettings.containsKey("success")) { + if(csettings.get("success", t).isInstanceOf(CClosure.TYPE)) { success = (CClosure) csettings.get("success", t); } else { - throw new Exceptions.CastException("Expecting the success parameter to be a closure.", t); + throw new CRECastException("Expecting the success parameter to be a closure.", t); } } else { - throw new ConfigRuntimeException("Missing the success parameter, which is required.", ExceptionType.CastException, t); + throw new CRECastException("Missing the success parameter, which is required.", t); } - if(csettings.containsKey("error")){ - if(csettings.get("error", t) instanceof CClosure){ + if(csettings.containsKey("error")) { + if(csettings.get("error", t).isInstanceOf(CClosure.TYPE)) { error = (CClosure) csettings.get("error", t); } else { - throw new Exceptions.CastException("Expecting the error parameter to be a closure.", t); + throw new CRECastException("Expecting the error parameter to be a closure.", t); } } else { error = null; } - if(csettings.containsKey("timeout")){ - settings.setTimeout(Static.getInt32(csettings.get("timeout", t), t)); + if(csettings.containsKey("timeout")) { + settings.setTimeout(ArgumentValidation.getInt32(csettings.get("timeout", t), t)); } String username = null; String password = null; - if(csettings.containsKey("username")){ + if(csettings.containsKey("username")) { username = csettings.get("username", t).val(); } - if(csettings.containsKey("password")){ + if(csettings.containsKey("password")) { password = csettings.get("password", t).val(); } - if(csettings.containsKey("proxy")){ - CArray proxySettings = Static.getArray(csettings.get("proxy", t), t); + if(csettings.containsKey("proxy")) { + CArray proxySettings = ArgumentValidation.getArray(csettings.get("proxy", t), t); Proxy.Type type; String proxyURL; int port; - try{ + try { type = Proxy.Type.valueOf(proxySettings.get("type", t).val()); - } catch(IllegalArgumentException e){ - throw new ConfigRuntimeException(e.getMessage(), ExceptionType.FormatException, t, e); + } catch (IllegalArgumentException e) { + throw new CREFormatException(e.getMessage(), t, e); } proxyURL = proxySettings.get("url", t).val(); - port = Static.getInt32(proxySettings.get("port", t), t); + port = ArgumentValidation.getInt32(proxySettings.get("port", t), t); SocketAddress addr = new InetSocketAddress(proxyURL, port); Proxy proxy = new Proxy(type, addr); settings.setProxy(proxy); } - if(csettings.containsKey("download")){ - Construct download = csettings.get("download", t); - if(download instanceof CNull){ - settings.setDownloadTo(null); + if(csettings.containsKey("trustStore")) { + Mixed trustStore = csettings.get("trustStore", t); + if(trustStore.isInstanceOf(CBoolean.TYPE) && ArgumentValidation.getBoolean(trustStore, t) == false) { + settings.setDisableCertChecking(true); + } else if(trustStore.isInstanceOf(CArray.TYPE)) { + CArray trustStoreA = ((CArray) trustStore); + LinkedHashMap trustStoreJ = new LinkedHashMap<>((int) trustStoreA.size()); + final String noDefault = "no default"; + for(String key : trustStoreA.stringKeySet()) { + String value = trustStoreA.get(key, t).val(); + if(noDefault.equals(key) && noDefault.equals(value)) { + settings.setUseDefaultTrustStore(false); + continue; + } + trustStoreJ.put(key, value); + } + settings.setTrustStore(trustStoreJ); + } else if(trustStore instanceof CNull) { + // Do nothing, use the default settings } else { - //TODO: Remove this check and tie into the VFS once that is complete. - if(Static.InCmdLine(environment)){ - File file = new File(download.val()); - if(!file.isAbsolute()){ + throw new CRECastException("Unexpected type for value trustStore in " + getName(), t); + } + } + if(csettings.containsKey("download")) { + Mixed download = csettings.get("download", t); + if(download instanceof CNull) { + settings.setDownloadTo(null); + } else { // TODO: Remove this check and tie into the VFS once that is complete. + if(Static.InCmdLine(environment, true)) { + File file = Static.GetFileFromArgument(Construct.nval(download), environment, t, null); + if(!file.isAbsolute()) { file = new File(t.file(), file.getPath()); } settings.setDownloadTo(file); } } } + if(csettings.containsKey("downloadStrategy")) { + com.laytonsmith.core.FileWriteMode mode + = ArgumentValidation.getEnum(csettings.get("downloadStrategy", t), FileWriteMode.class, t); + com.laytonsmith.PureUtilities.Common.FileWriteMode puMode; + if(mode == com.laytonsmith.core.FileWriteMode.APPEND) { + puMode = com.laytonsmith.PureUtilities.Common.FileWriteMode.APPEND; + } else if(mode == com.laytonsmith.core.FileWriteMode.OVERWRITE) { + puMode = com.laytonsmith.PureUtilities.Common.FileWriteMode.OVERWRITE; + } else if(mode == com.laytonsmith.core.FileWriteMode.SAFE_WRITE) { + puMode = com.laytonsmith.PureUtilities.Common.FileWriteMode.SAFE_WRITE; + } else { + throw new Error("Unhandled case"); + } + settings.setDownloadStrategy(puMode); + } + if(csettings.containsKey("binary")) { + binary = ArgumentValidation.getBoolean(csettings.get("binary", t), t); + } else { + binary = false; + } + + if(csettings.containsKey("textEncoding")) { + textEncoding = csettings.get("textEncoding", t).val(); + } else { + textEncoding = "UTF-8"; + } + + if(csettings.containsKey("blocking")) { + boolean blocking = ArgumentValidation.getBoolean(csettings.get("blocking", t), t); + settings.setBlocking(blocking); + } + if(csettings.containsKey("log") && ArgumentValidation.getBoolean(csettings.get("log", t), t)) { + settings.setLogger(Logger.getLogger(Web.class.getName())); + } settings.setAuthenticationDetails(username, password); } - environment.getEnv(GlobalEnv.class).GetDaemonManager().activateThread(null); - threadPool.submit(new Runnable() { + + List st + = environment.getEnv(GlobalEnv.class).GetStackTraceManager().getCurrentStackTrace(); + environment.getEnv(StaticRuntimeEnv.class).GetDaemonManager().activateThread(null); + Runnable task = new Runnable() { @Override public void run() { - try{ + try { HTTPResponse resp = WebUtility.GetPage(url, settings); - final CArray array = new CArray(t); - array.set("body", new CString(resp.getContent(), t), t); - CArray headers = new CArray(t); - for(String key : resp.getHeaderNames()){ + final CArray array = CArray.GetAssociativeArray(t); + if(settings.getDownloadTo() == null) { + if(binary) { + array.set("data", CByteArray.wrap(resp.getContent(), t), t); + } else { + try { + array.set("body", new CString(new String(resp.getContent(), textEncoding), t), t); + } catch (UnsupportedEncodingException ex) { + throw new CREFormatException("Unsupported encoding [" + textEncoding + "]", t, ex); + } + } + } + CArray headers = CArray.GetAssociativeArray(t); + for(String key : resp.getHeaderObject().getHeaderNames()) { CArray h = new CArray(t); - for(String val : resp.getHeaders(key)){ - h.push(new CString(val, t)); + for(String val : resp.getHeaderObject().getHeaders(key)) { + h.push(new CString(val, t), t); } headers.set(key, h, t); } @@ -384,58 +480,64 @@ public void run() { array.set("responseText", resp.getResponseText()); array.set("httpVersion", resp.getHttpVersion()); array.set("error", CBoolean.get(resp.getResponseCode() >= 400 && resp.getResponseCode() < 600), t); - if(arrayJar != null){ + if(arrayJar != null) { getCookieJar(arrayJar, settings.getCookieJar(), t); } - StaticLayer.GetConvertor().runOnMainThreadLater(environment.getEnv(GlobalEnv.class).GetDaemonManager(), new Runnable() { - - @Override - public void run() { - executeFinish(success, array, t, environment); + if(settings.getBlocking()) { + executeFinish(success, array, t, environment); + } else { + StaticLayer.GetConvertor().runOnMainThreadLater( + environment.getEnv(StaticRuntimeEnv.class).GetDaemonManager(), + () -> executeFinish(success, array, t, environment)); + } + } catch (IOException e) { + final CREIOException ex = new CREIOException((e instanceof UnknownHostException ? "Unknown host: " : "") + + e.getMessage(), t); + ex.setStackTraceElements(st); + if(error != null) { + final CArray cException = ObjectGenerator.GetGenerator().exception(ex, environment, t); + if(settings.getBlocking()) { + executeFinish(error, cException, t, environment); + } else { + StaticLayer.GetConvertor().runOnMainThreadLater( + environment.getEnv(StaticRuntimeEnv.class).GetDaemonManager(), + () -> executeFinish(error, cException, t, environment)); } - }); - } catch(IOException e){ - final ConfigRuntimeException ex = new ConfigRuntimeException((e instanceof UnknownHostException?"Unknown host: ":"") - + e.getMessage(), ExceptionType.IOException, t); - if(error != null){ - StaticLayer.GetConvertor().runOnMainThreadLater(environment.getEnv(GlobalEnv.class).GetDaemonManager(), new Runnable() { - - @Override - public void run() { - executeFinish(error, ObjectGenerator.GetGenerator().exception(ex, t), t, environment); - } - }); } else { ConfigRuntimeException.HandleUncaughtException(ex, environment); } - } catch(Exception e){ + } catch (Exception e) { e.printStackTrace(); } finally { - environment.getEnv(GlobalEnv.class).GetDaemonManager().deactivateThread(null); + environment.getEnv(StaticRuntimeEnv.class).GetDaemonManager().deactivateThread(null); } } - }); + }; + if(settings.getBlocking()) { + task.run(); + } else { + THREAD_POOL.submit(task); + } return CVoid.VOID; } - private void executeFinish(CClosure closure, Construct arg, Target t, Environment environment){ - try{ - closure.execute(new Construct[]{arg}); - } catch(FunctionReturnException e){ + private void executeFinish(CClosure closure, Mixed arg, Target t, Environment environment) { + try { + Mixed ret = closure.executeCallable(new Mixed[]{arg}); //Just ignore this if it's returning void. Otherwise, warn. //TODO: Eventually, this should be taggable as a compile error - if(!(e.getReturn() instanceof CVoid)){ - CHLog.GetLogger().Log(CHLog.Tags.RUNTIME, LogLevel.WARNING, "Returning a value from the closure. The value is" + if(!(ret instanceof CVoid)) { + MSLog.GetLogger().Log(MSLog.Tags.RUNTIME, LogLevel.WARNING, "Returning a value from the closure. The value is" + " being ignored.", t); } - } catch(ProgramFlowManipulationException e){ + } catch (ProgramFlowManipulationException e) { //This is an error - CHLog.GetLogger().Log(CHLog.Tags.RUNTIME, LogLevel.WARNING, "Only return may be used inside the closure.", t); - } catch(ConfigRuntimeException e){ + MSLog.GetLogger().Log(MSLog.Tags.RUNTIME, LogLevel.WARNING, "Only return may be used inside the closure.", t); + } catch (ConfigRuntimeException e) { ConfigRuntimeException.HandleUncaughtException(e, environment); - } catch(Throwable e){ + } catch (Throwable e) { //Other throwables we just need to report - CHLog.GetLogger().Log(CHLog.Tags.RUNTIME, LogLevel.ERROR, "An unexpected exception has occurred. No extra" + MSLog.GetLogger().Log(MSLog.Tags.RUNTIME, LogLevel.ERROR, "An unexpected exception has occurred. No extra" + " information is available, but please report this error:\n" + StackTraceUtils.GetStacktrace(e), t); } } @@ -457,7 +559,7 @@ public String docs() { @Override public String generate(String... args) { - if("HTTPMethod".equals(args[0])){ + if("HTTPMethod".equals(args[0])) { return StringUtils.Join(HTTPMethod.values(), ", "); } else { return ""; @@ -465,45 +567,52 @@ public String generate(String... args) { } }); templates.put("CODE", DocGenTemplates.CODE); - return super.getBundledDocs(templates); + templates.put("DEFAULT_HEADERS", (e) -> DEFAULT_HEADERS.toString()); + try { + return super.getBundledDocs(templates); + } catch (DocGenTemplates.Generator.GenerateException ex) { + Logger.getLogger(Web.class.getName()).log(Level.SEVERE, null, ex); + // just return the unformatted docs, which are more useful than nothing. + return super.getBundledDocs(); + } } @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Getting headers from a website", "http_request('http://www.google.com', array(\n" - + "\tsuccess: closure(@response,\n" - + "\t\tmsg(@response['headers']['Server'][0])\n" - + "\t)\n" - + "));\n", "gws"), + + "\tsuccess: closure(@response,\n" + + "\t\tmsg(@response['headers']['Server'][0]);\n" + + "\t)\n" + + "));\n", "gws"), new ExampleScript("Using a cookie jar", "@cookiejar = array()\n" - + "http_request('http://www.google.com', array(\n" - + "\tcookiejar: @cookiejar, success: closure(@resp,\n" - + "\t\tmsg(@cookiejar)\n" - + "\t)\n" - + "));\n", ""), + + "http_request('http://www.google.com', array(\n" + + "\tcookiejar: @cookiejar, success: closure(@resp) {\n" + + "\t\tmsg(@cookiejar);\n" + + "\t}\n" + + "));\n", ""), new ExampleScript("Sending some json to the server", - "http_request('http://example.com', array(\n" - + "\tmethod: 'POST',\n" - + "\theaders: array(\n" - + "\t\t// The content type isn't set automatically if we send a string via params,\n" - + "\t\t// so we have to set this manually to application/json here, since we're sending" - + "\t\t// json data. Other data types may have different MIME types." - + "\t\t'Content-Type': 'application/json'\n" - + "\t)," - + "\tparams: json_encode(array(\n" - + "\t\t'arg1': 'value',\n" - + "\t\t'arg2': 'value',\n" - + "\t)),\n" - + "\tsuccess: closure(@response){\n" - + "\t\t// Handle the server's response\n" - + "\t}}" - + "));", "") + "http_request('http://example.com', array(\n" + + "\tmethod: 'POST',\n" + + "\theaders: array(\n" + + "\t\t// The content type isn't set automatically if we send a string via params,\n" + + "\t\t// so we have to set this manually to application/json here, since we're sending\n" + + "\t\t// json data. Other data types may have different MIME types.\n" + + "\t\t'Content-Type': 'application/json'\n" + + "\t)," + + "\tparams: json_encode(array(\n" + + "\t\t'arg1': 'value',\n" + + "\t\t'arg2': 'value',\n" + + "\t)),\n" + + "\tsuccess: closure(@response){\n" + + "\t\t// Handle the server's response\n" + + "\t}" + + "));", "") }; } @@ -513,8 +622,8 @@ public ExampleScript[] examples() throws ConfigCompileException { public static class http_clear_session_cookies extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException}; + public Class[] thrown() { + return new Class[]{CRECastException.class}; } @Override @@ -528,8 +637,8 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - CArray array = Static.getArray(args[0], t); + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + CArray array = ArgumentValidation.getArray(args[0], t); CookieJar jar = getCookieJar(array, t); jar.clearSessionCookies(); return CVoid.VOID; @@ -553,7 +662,7 @@ public String docs() { @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } } @@ -562,8 +671,8 @@ public Version since() { public static class url_encode extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{}; } @Override @@ -577,7 +686,7 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { try { return new CString(URLEncoder.encode(args[0].val(), "UTF-8"), t); } catch (UnsupportedEncodingException ex) { @@ -603,7 +712,7 @@ public String docs() { @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } @Override @@ -619,8 +728,8 @@ public ExampleScript[] examples() throws ConfigCompileException { public static class url_decode extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{}; } @Override @@ -634,7 +743,7 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { try { return new CString(URLDecoder.decode(args[0].val(), "UTF-8"), t); } catch (UnsupportedEncodingException ex) { @@ -659,7 +768,7 @@ public String docs() { @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } @Override @@ -673,11 +782,12 @@ public ExampleScript[] examples() throws ConfigCompileException { @api @noboilerplate + @seealso({com.laytonsmith.tools.docgen.templates.Profiles.class}) public static class email extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.FormatException, ExceptionType.PluginInternalException, ExceptionType.IOException}; + public Class[] thrown() { + return new Class[]{CREFormatException.class, CREPluginInternalException.class, CREIOException.class}; } @Override @@ -691,10 +801,10 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { // Argument processing CArray options; - if(args.length == 1){ + if(args.length == 1) { options = ArgumentValidation.getArray(args[0], t); } else { // Load the profile for transport data, if specified. @@ -702,46 +812,45 @@ public Construct exec(Target t, Environment environment, Construct... args) thro options = CArray.GetAssociativeArray(t); Profiles.Profile p; try { - p = environment.getEnv(GlobalEnv.class).getProfiles().getProfileById(profileName); - } catch(Profiles.InvalidProfileException ex){ - throw new ConfigRuntimeException(ex.getMessage(), ExceptionType.FormatException, t, ex); + p = environment.getEnv(StaticRuntimeEnv.class).getProfiles().getProfileById(profileName); + } catch (Profiles.InvalidProfileException ex) { + throw new CREFormatException(ex.getMessage(), t, ex); } - if(!(p instanceof EmailProfile)){ - throw new ConfigRuntimeException("Profile type is expected to be \"email\", but \"" + p.getType() + "\" was found.", ExceptionType.CastException, t); + if(!(p instanceof EmailProfile)) { + throw new CRECastException("Profile type is expected to be \"email\", but \"" + p.getType() + "\" was found.", t); } - Map data = ((EmailProfile)p).getMap(); - for(String key : data.keySet()){ + Map data = ((EmailProfile) p).getMap(); + for(String key : data.keySet()) { options.set(key, Construct.GetConstruct(data.get(key)), t); } // Override any transport data that was also specified in the options, as // well as adding the email settings here too. CArray options2 = ArgumentValidation.getArray(args[1], t); - for(String key : options2.stringKeySet()){ + for(String key : options2.stringKeySet()) { options.set(key, options2.get(key, t), t); } } - // Transport options String host = ArgumentValidation.getItemFromArray(options, "host", t, new CString("localhost", t)).val(); final String mailUser = ArgumentValidation.getItemFromArray(options, "user", t, new CString("", t)).val(); final String mailPassword = ArgumentValidation.getItemFromArray(options, "password", t, new CString("", t)).val(); int mailPort = ArgumentValidation.getInt32(ArgumentValidation.getItemFromArray(options, "port", t, new CInt(587, t)), t); - boolean useSSL = ArgumentValidation.getBoolean(ArgumentValidation.getItemFromArray(options, "use_ssl", t, CBoolean.FALSE), t); - boolean useStartTLS = ArgumentValidation.getBoolean(ArgumentValidation.getItemFromArray(options, "use_start_tls", t, CBoolean.FALSE), t); + boolean useSSL = ArgumentValidation.getBooleanObject(ArgumentValidation.getItemFromArray(options, "use_ssl", t, CBoolean.FALSE), t); + boolean useStartTLS = ArgumentValidation.getBooleanObject(ArgumentValidation.getItemFromArray(options, "use_start_tls", t, CBoolean.FALSE), t); int timeout = ArgumentValidation.getInt32(ArgumentValidation.getItemFromArray(options, "timeout", t, new CInt(10000, t)), t); //Standard email options String from = ArgumentValidation.getItemFromArray(options, "from", t, null).val(); String subject = ArgumentValidation.getItemFromArray(options, "subject", t, new CString("", t)).val(); String body = ArgumentValidation.getItemFromArray(options, "body", t, new CString("", t)).val(); - Construct cto = ArgumentValidation.getItemFromArray(options, "to", t, null); + Mixed cto = ArgumentValidation.getItemFromArray(options, "to", t, null); CArray to; - if(cto instanceof CString){ + if(cto.isInstanceOf(CString.TYPE)) { to = new CArray(t); - to.push(cto); + to.push(cto, t); } else { - to = (CArray)cto; + to = (CArray) cto; } CArray attachments = ArgumentValidation.getArray(ArgumentValidation.getItemFromArray(options, "attachments", t, new CArray(t)), t); @@ -751,19 +860,18 @@ public Construct exec(Target t, Environment environment, Construct... args) thro properties.setProperty("mail.smtp.port", Integer.toString(mailPort)); properties.setProperty("mail.smtp.starttls.enable", Boolean.toString(useStartTLS)); properties.setProperty("mail.smtp.ssl.enable", Boolean.toString(useSSL)); - if(timeout > 0){ + if(timeout > 0) { properties.setProperty("mail.smtp.connectiontimeout", Integer.toString(timeout)); properties.setProperty("mail.smtp.timeout", Integer.toString(timeout)); } - properties.setProperty("mail.debug", Boolean.toString(Prefs.DebugMode())); - if(!"".equals(mailUser)){ + if(!"".equals(mailUser)) { properties.setProperty("mail.smtp.user", mailUser); properties.setProperty("mail.smtp.auth", "true"); } - if(!"".equals(mailPassword)){ + if(!"".equals(mailPassword)) { properties.setProperty("mail.smtp.password", mailPassword); properties.setProperty("mail.smtp.auth", "true"); } @@ -777,27 +885,27 @@ protected PasswordAuthentication getPasswordAuthentication() { }); - try{ + try { MimeMessage message = new MimeMessage(session); message.setFrom(new InternetAddress(from)); message.setSubject(subject); - if(!"".equals(body)){ - CArray bodyAttachment = new CArray(t); + if(!"".equals(body)) { + CArray bodyAttachment = CArray.GetAssociativeArray(t); bodyAttachment.set("type", "text/plain"); bodyAttachment.set("content", body); - attachments.push(bodyAttachment, 0); + attachments.push(bodyAttachment, 0, t); } - for(Construct c : to.asList()){ + for(Mixed c : to.asList()) { Message.RecipientType type = Message.RecipientType.TO; String address; - if(c instanceof CArray){ - CArray ca = (CArray)c; + if(c.isInstanceOf(CArray.TYPE)) { + CArray ca = (CArray) c; String stype = ArgumentValidation.getItemFromArray(ca, "type", t, new CString("TO", t)).val(); - switch(stype){ + switch(stype) { case "TO": type = Message.RecipientType.TO; break; @@ -808,7 +916,7 @@ protected PasswordAuthentication getPasswordAuthentication() { type = Message.RecipientType.BCC; break; default: - throw new ConfigRuntimeException("Recipient type must be one of either: TO, CC, or BCC, but \"" + stype + "\" was found.", ExceptionType.FormatException, t); + throw new CREFormatException("Recipient type must be one of either: TO, CC, or BCC, but \"" + stype + "\" was found.", t); } address = ArgumentValidation.getItemFromArray(ca, "address", t, null).val(); } else { @@ -817,26 +925,26 @@ protected PasswordAuthentication getPasswordAuthentication() { message.addRecipient(type, new InternetAddress(address)); } - if(attachments.size() == 1){ + if(attachments.size() == 1) { CArray pattachment = ArgumentValidation.getArray(attachments.get(0, t), t); String type = ArgumentValidation.getItemFromArray(pattachment, "type", t, null).val(); String fileName = ArgumentValidation.getItemFromArray(pattachment, "filename", t, new CString("", t)).val().trim(); String description = ArgumentValidation.getItemFromArray(pattachment, "description", t, new CString("", t)).val().trim(); String disposition = ArgumentValidation.getItemFromArray(pattachment, "disposition", t, new CString("", t)).val().trim(); - Construct content = ArgumentValidation.getItemFromArray(pattachment, "content", t, null); - if(!"".equals(fileName)){ + Mixed content = ArgumentValidation.getItemFromArray(pattachment, "content", t, null); + if(!"".equals(fileName)) { message.setFileName(fileName); } - if(!"".equals(description)){ + if(!"".equals(description)) { message.setDescription(description); } - if(!"".equals(disposition)){ + if(!"".equals(disposition)) { message.setDisposition(disposition); } message.setContent(getContent(content, t), type); } else { Multipart mp = new MimeMultipart("alternative"); - for(Construct attachment : attachments.asList()){ + for(Mixed attachment : attachments.asList()) { CArray pattachment = ArgumentValidation.getArray(attachment, t); final String type = ArgumentValidation.getItemFromArray(pattachment, "type", t, null).val(); final String fileName = ArgumentValidation.getItemFromArray(pattachment, "filename", t, new CString("", t)).val().trim(); @@ -844,14 +952,14 @@ protected PasswordAuthentication getPasswordAuthentication() { String disposition = ArgumentValidation.getItemFromArray(pattachment, "disposition", t, new CString("", t)).val().trim(); final Object content = getContent(ArgumentValidation.getItemFromArray(pattachment, "content", t, null), t); BodyPart bp = new MimeBodyPart(); - if(!"".equals(fileName)){ + if(!"".equals(fileName)) { bp.setFileName(fileName); bp.setHeader("Content-ID", "<" + fileName + ">"); } - if(!"".equals(description)){ + if(!"".equals(description)) { bp.setDescription(description); } - if(!"".equals(disposition)){ + if(!"".equals(disposition)) { bp.setDisposition(disposition); } @@ -859,10 +967,10 @@ protected PasswordAuthentication getPasswordAuthentication() { @Override public InputStream getInputStream() throws IOException { - if(content instanceof String){ - return new ByteArrayInputStream(((String)content).getBytes("UTF-8")); + if(content instanceof String) { + return new ByteArrayInputStream(((String) content).getBytes("UTF-8")); } else { - return new ByteArrayInputStream((byte[])content); + return new ByteArrayInputStream((byte[]) content); } } @@ -878,7 +986,7 @@ public String getContentType() { @Override public String getName() { - if("".equals(fileName)){ + if("".equals(fileName)) { return "Untitled"; } else { return fileName; @@ -891,38 +999,46 @@ public String getName() { message.setContent(mp); } - Transport tr = session.getTransport(useSSL?"smtps":"smtp"); + Transport tr = session.getTransport(useSSL ? "smtps" : "smtp"); try { tr.connect(host, mailPort, mailUser, mailPassword); message.saveChanges(); + MailcapCommandMap mc = (MailcapCommandMap) CommandMap.getDefaultCommandMap(); +// mc.addMailcap("text/html;; x-java-content-handler=com.sun.mail.handlers.text_html"); +// mc.addMailcap("text/xml;; x-java-content-handler=com.sun.mail.handlers.text_xml"); +// mc.addMailcap("text/plain;; x-java-content-handler=com.sun.mail.handlers.text_plain"); + mc.addMailcap("multipart/*;; x-java-content-handler=com.sun.mail.handlers.multipart_mixed"); + mc.addMailcap("message/rfc822;; x-java-content-handler=com.sun.mail.handlers.message_rfc822"); + CommandMap.setDefaultCommandMap(mc); tr.sendMessage(message, message.getAllRecipients()); } finally { tr.close(); } - } catch(MessagingException ex){ - if(ex.getCause() instanceof SocketTimeoutException){ - throw new ConfigRuntimeException(ex.getCause().getMessage(), ExceptionType.IOException, t, ex); + } catch (MessagingException ex) { + if(ex.getCause() instanceof SocketTimeoutException) { + throw new CREIOException(ex.getCause().getMessage(), t, ex); } - throw new ConfigRuntimeException(ex.getMessage(), ExceptionType.PluginInternalException, t, ex); + throw new CREPluginInternalException(ex.getMessage(), t, ex); } return CVoid.VOID; } /** * Parses the content from the construct. + * * @param c * @param t * @return */ - private Object getContent(Construct c, Target t){ - if(c instanceof CString){ + private Object getContent(Mixed c, Target t) { + if(c.isInstanceOf(CString.TYPE)) { return c.val(); - } else if(c instanceof CByteArray){ - CByteArray cb = (CByteArray)c; + } else if(c.isInstanceOf(CByteArray.TYPE)) { + CByteArray cb = (CByteArray) c; return cb.asByteArrayCopy(); } else { - throw new ConfigRuntimeException("Only strings and byte_arrays may be added as attachments' content.", ExceptionType.FormatException, t); + throw new CREFormatException("Only strings and byte_arrays may be added as attachments' content.", t); } } @@ -943,98 +1059,100 @@ public String docs() { @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } @Override public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("Sending a plain text email using default transport and email settings", "email(array(\n" - + "\tfrom: 'from@example.com',\n" - + "\tto: 'to@example.com',\n" - + "\tbody: 'Email body',\n" - + "));", ""), + + "\tfrom: 'from@example.com',\n" + + "\tto: 'to@example.com',\n" + + "\tbody: 'Email body',\n" + + "));", ""), new ExampleScript("Sending a plain text email using gmail", "email(array(\n" - + "\thost: 'smtp.gmail.com',\n" - + "\tport: 465,\n" - + "\tuse_start_tls: true,\n" - + "\tuse_ssl: true,\n" - + "\tuser: 'username@gmail.com',\n" - + "\tpassword: 'myPassword',\n" - + "\n" - + "\tfrom: 'from@gmail.com',\n" - + "\tto: 'to@example.com',\n" - + "\tsubject: 'Subject',\n" - + "\tbody: 'Body'\n" - + "));", ""), + + "\thost: 'smtp.gmail.com',\n" + + "\tport: 465,\n" + + "\tuse_start_tls: true,\n" + + "\tuse_ssl: true,\n" + + "\tuser: 'username@gmail.com',\n" + + "\tpassword: 'myPassword',\n" + + "\n" + + "\tfrom: 'from@gmail.com',\n" + + "\tto: 'to@example.com',\n" + + "\tsubject: 'Subject',\n" + + "\tbody: 'Body'\n" + + "));", ""), new ExampleScript("Sending a plain text email settings saved in " + MethodScriptFileLocations.getDefault().getProfilesFile().getName() - + " as type \"email\" and id \"myID\". This is most useful for keeping credentials out of code directly.", - "email('myID', array(\n" - + "\tfrom: 'from@gmail.com',\n" - + "\tto: 'to@example.com',\n" - + "\tsubject: 'Subject',\n" - + "\tbody: 'Body'\n" - + "));", ""), + + " as type \"email\" and id \"myID\". This is most useful for keeping credentials out of code directly.", + "email('myID', array(\n" + + "\tfrom: 'from@gmail.com',\n" + + "\tto: 'to@example.com',\n" + + "\tsubject: 'Subject',\n" + + "\tbody: 'Body'\n" + + "));", ""), new ExampleScript("Sending a html email, with text fallback", "email(array(\n" - + "\tfrom: 'from@example.com',\n" - + "\tto: 'to@example.com',\n" - + "\tsubject: 'Test Email',\n" - + "\tbody: 'This is the plain text body, which would show up in some email clients, such a mobile devices',\n" - + "\tattachments: array(\n" - + "\t\tarray(\n" - + "\t\t\ttype: 'text/html'," - + "\t\t\tcontent: '

This is the html body, which would show up in most email clients. The plain text body will not show if this does.

'\n" - + "\t\t)\n" - + "\t)\n" - + "));", ""), + + "\tfrom: 'from@example.com',\n" + + "\tto: 'to@example.com',\n" + + "\tsubject: 'Test Email',\n" + + "\tbody: 'This is the plain text body, which would show up in some email clients, such a mobile devices',\n" + + "\tattachments: array(\n" + + "\t\tarray(\n" + + "\t\t\ttype: 'text/html',\n" + + "\t\t\tcontent: '

This is the html body, which would show up in most email clients. The plain text body will not show if this does.

'\n" + + "\t\t)\n" + + "\t)\n" + + "));", ""), new ExampleScript("Sending an email, with multiple recipients", "email(array(\n" - + "\tfrom: 'from@example.com',\n" - + "\tto: array('to@example.com', array(type: 'BCC', address: 'bcc@example.com')),\n" - + "\tbody: 'Two recipients'\n" - + "));", ""), + + "\tfrom: 'from@example.com',\n" + + "\tto: array(\n" + + "\t\t'to@example.com',\n" + + "\t\tarray(type: 'BCC', address: 'bcc@example.com')\n" + + "\t),\n" + + "\tbody: 'Two recipients'\n" + + "));", ""), new ExampleScript("Sending an email, with a text attachment", "email(array(\n" - + "\tfrom: 'from@example.com',\n" - + "\tto: 'to@example.com',\n" - + "\tattachments: array(\n" - + "\t\tarray(\n" - + "\t\t\ttype: 'text/plain',\n" - + "\t\t\tfilename: 'test.txt',\n" - + "\t\t\tcontent: read('test.txt'), // This text may come from anywhere, not just the file system\n" - + "\t\t\tdisposition: 'attachment', // This is what tells the email client to make it downloadable\n" - + "\t\t\tdescription: 'A description of the file, which may or may not be shown by the email client'\n" - + "\t\t)\n" - + "\t)" - + "));", ""), + + "\tfrom: 'from@example.com',\n" + + "\tto: 'to@example.com',\n" + + "\tattachments: array(\n" + + "\t\tarray(\n" + + "\t\t\ttype: 'text/plain',\n" + + "\t\t\tfilename: 'test.txt',\n" + + "\t\t\tcontent: read('test.txt'), // This text may come from anywhere, not just the file system\n" + + "\t\t\tdisposition: 'attachment', // This is what tells the email client to make it downloadable\n" + + "\t\t\tdescription: 'A description of the file, which may or may not be shown by the email client'\n" + + "\t\t)\n" + + "\t)\n" + + "));", ""), new ExampleScript("Sending an email, with a binary attachment", "email(array(\n" - + "\tfrom: 'from@example.com',\n" - + "\tto: 'to@example.com',\n" - + "\tattachments: array(\n" - + "\t\tarray(\n" - + "\t\t\ttype: 'application/pdf', // This will vary depending on the file type, and cannot be automatically determined\n" - + "\t\t\tfilename: 'test.pdf',\n" - + "\t\t\tcontent: read_binary('test.pdf'),\n" - + "\t\t\tdisposition: 'attachment', // This is what tells the email client to make it downloadable\n" - + "\t\t)\n" - + "\t)" - + "));", ""), + + "\tfrom: 'from@example.com',\n" + + "\tto: 'to@example.com',\n" + + "\tattachments: array(\n" + + "\t\tarray(\n" + + "\t\t\ttype: 'application/pdf', // This will vary depending on the file type, and cannot be automatically determined\n" + + "\t\t\tfilename: 'test.pdf',\n" + + "\t\t\tcontent: read_binary('test.pdf'),\n" + + "\t\t\tdisposition: 'attachment', // This is what tells the email client to make it downloadable\n" + + "\t\t)\n" + + "\t)" + + "));", ""), new ExampleScript("Sending an html email with an inline image", "email(array(\n" - + "\tfrom: 'from@example.com',\n" - + "\tto: 'to@example.com',\n" - + "\tsubject: 'Test Email',\n" - + "\tbody: 'This is the plain text body, which would show up in some email clients, such a mobile devices',\n" - + "\tattachments: array(\n" - + "\t\tarray(\n" - + "\t\t\ttype: 'text/html'," - + "\t\t\tcontent: '

This is an inline image:

'\n" - + "\t\t), array(\n" - + "\t\t\ttype: 'image/png',\n" - + "\t\t\tfilename: 'image.png', // This needs to be unique across all attachments, and is referenced by \"cid:image.png\" in the html\n" - + "\t\t\tcontent: read_binary('image.png'),\n" - + "\t\t\tdisposition: 'inline', // Technically we could leave this off, because it defaults to inline\n" - + "\t\t\tdescription: 'An image',\n" - + "\t)\n" - + "));", ""), - }; + + "\tfrom: 'from@example.com',\n" + + "\tto: 'to@example.com',\n" + + "\tsubject: 'Test Email',\n" + + "\tbody: 'This is the plain text body, which would show up in some email clients, such a mobile devices',\n" + + "\tattachments: array(\n" + + "\t\tarray(\n" + + "\t\t\ttype: 'text/html',\n" + + "\t\t\tcontent: '

This is an inline image:

'\n" + + "\t\t), array(\n" + + "\t\t\ttype: 'image/png',\n" + + "\t\t\tfilename: 'image.png', // This needs to be unique across all attachments, and is referenced by \"cid:image.png\" in the html\n" + + "\t\t\tcontent: read_binary('image.png'),\n" + + "\t\t\tdisposition: 'inline', // Technically we could leave this off, because it defaults to inline\n" + + "\t\t\tdescription: 'An image',\n" + + "\t)\n" + + ")));", "")}; } } diff --git a/src/main/java/com/laytonsmith/core/functions/World.java b/src/main/java/com/laytonsmith/core/functions/World.java index 4d8f86c46f..a91c9db9d5 100644 --- a/src/main/java/com/laytonsmith/core/functions/World.java +++ b/src/main/java/com/laytonsmith/core/functions/World.java @@ -1,43 +1,71 @@ package com.laytonsmith.core.functions; import com.laytonsmith.PureUtilities.Common.StringUtils; +import com.laytonsmith.PureUtilities.Vector3D; import com.laytonsmith.PureUtilities.Version; import com.laytonsmith.abstraction.MCChunk; import com.laytonsmith.abstraction.MCCommandSender; -import com.laytonsmith.abstraction.MCItemStack; import com.laytonsmith.abstraction.MCLocation; import com.laytonsmith.abstraction.MCPlayer; import com.laytonsmith.abstraction.MCWorld; +import com.laytonsmith.abstraction.MCWorldBorder; import com.laytonsmith.abstraction.MCWorldCreator; import com.laytonsmith.abstraction.StaticLayer; -import com.laytonsmith.abstraction.Velocity; -import com.laytonsmith.abstraction.blocks.MCFallingBlock; +import com.laytonsmith.abstraction.blocks.MCBlockFace; +import com.laytonsmith.abstraction.blocks.MCMaterial; +import com.laytonsmith.abstraction.entities.MCFallingBlock; import com.laytonsmith.abstraction.enums.MCDifficulty; import com.laytonsmith.abstraction.enums.MCGameRule; +import com.laytonsmith.abstraction.enums.MCVersion; import com.laytonsmith.abstraction.enums.MCWorldEnvironment; import com.laytonsmith.abstraction.enums.MCWorldType; import com.laytonsmith.annotations.api; -import com.laytonsmith.core.CHVersion; +import com.laytonsmith.annotations.hide; +import com.laytonsmith.core.ArgumentValidation; +import com.laytonsmith.core.MSLog; +import com.laytonsmith.core.MSLog.Tags; +import com.laytonsmith.core.MSVersion; import com.laytonsmith.core.ObjectGenerator; +import com.laytonsmith.core.Optimizable; +import com.laytonsmith.core.ParseTree; import com.laytonsmith.core.Static; +import com.laytonsmith.core.compiler.CompilerEnvironment; +import com.laytonsmith.core.compiler.CompilerWarning; +import com.laytonsmith.core.compiler.FileOptions; +import com.laytonsmith.core.compiler.signature.FunctionSignatures; +import com.laytonsmith.core.compiler.signature.SignatureBuilder; import com.laytonsmith.core.constructs.CArray; import com.laytonsmith.core.constructs.CBoolean; import com.laytonsmith.core.constructs.CDouble; import com.laytonsmith.core.constructs.CInt; import com.laytonsmith.core.constructs.CNull; +import com.laytonsmith.core.constructs.CNumber; import com.laytonsmith.core.constructs.CString; import com.laytonsmith.core.constructs.CVoid; -import com.laytonsmith.core.constructs.Construct; import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.environments.CommandHelperEnvironment; import com.laytonsmith.core.environments.Environment; +import com.laytonsmith.core.exceptions.CRE.CRECastException; +import com.laytonsmith.core.exceptions.CRE.CREFormatException; +import com.laytonsmith.core.exceptions.CRE.CREIllegalArgumentException; +import com.laytonsmith.core.exceptions.CRE.CREInsufficientArgumentsException; +import com.laytonsmith.core.exceptions.CRE.CREInvalidWorldException; +import com.laytonsmith.core.exceptions.CRE.CRENotFoundException; +import com.laytonsmith.core.exceptions.CRE.CRERangeException; +import com.laytonsmith.core.exceptions.CRE.CREThrowable; import com.laytonsmith.core.exceptions.CancelCommandException; +import com.laytonsmith.core.exceptions.ConfigCompileException; import com.laytonsmith.core.exceptions.ConfigRuntimeException; -import com.laytonsmith.core.functions.Exceptions.ExceptionType; +import com.laytonsmith.core.natives.interfaces.Booleanish; +import com.laytonsmith.core.natives.interfaces.Mixed; + import java.io.IOException; +import java.util.EnumSet; import java.util.Enumeration; +import java.util.List; import java.util.Properties; import java.util.Random; +import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import java.util.logging.Level; @@ -45,16 +73,13 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -/** - * - */ public class World { public static String docs() { return "Provides functions for manipulating a world"; } - @api(environments=CommandHelperEnvironment.class) + @api(environments = CommandHelperEnvironment.class) public static class get_spawn extends AbstractFunction { @Override @@ -69,12 +94,13 @@ public Integer[] numArgs() { @Override public String docs() { - return "array {[world]} Returns a location array for the specified world, or the current player's world, if not specified."; + return "array {[world]} Returns a location array for the specified world, or the current player's world" + + " if one is not specified."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException, ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CREInvalidWorldException.class, CREFormatException.class}; } @Override @@ -83,8 +109,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_0; + public MSVersion since() { + return MSVersion.V3_3_0; } @Override @@ -93,31 +119,30 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - String world; - if (args.length == 1) { - world = args[0].val(); + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCWorld w; + if(args.length == 1) { + w = Static.getServer().getWorld(args[0].val()); } else { - if(environment.getEnv(CommandHelperEnvironment.class).GetPlayer() == null){ - throw new ConfigRuntimeException("A world must be specified in a context with no player.", ExceptionType.InvalidWorldException, t); + MCPlayer p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); + if(p == null) { + throw new CREInvalidWorldException("A world must be specified in a context with no player.", t); } - world = environment.getEnv(CommandHelperEnvironment.class).GetPlayer().getWorld().getName(); + w = p.getWorld(); } - MCWorld w = Static.getServer().getWorld(world); - if (w == null) { - throw new ConfigRuntimeException("The specified world \"" + world + "\" is not a valid world.", ExceptionType.InvalidWorldException, t); + if(w == null) { + throw new CREInvalidWorldException("The specified world \"" + args[0].val() + "\" is not a valid world.", t); } - return ObjectGenerator.GetGenerator().location(w.getSpawnLocation(), false); + return ObjectGenerator.GetGenerator().location(w.getSpawnLocation()); } } - @api(environments=CommandHelperEnvironment.class) + @api(environments = CommandHelperEnvironment.class) public static class set_spawn extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException, - ExceptionType.CastException, ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CREInvalidWorldException.class, CRECastException.class, CREFormatException.class}; } @Override @@ -131,29 +156,42 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - MCWorld w = (environment.getEnv(CommandHelperEnvironment.class).GetPlayer() != null ? environment.getEnv(CommandHelperEnvironment.class).GetPlayer().getWorld() : null); + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCWorld w = null; + if(args.length == 1) { + MCPlayer p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); + if(p != null) { + w = p.getWorld(); + } + MCLocation l = ObjectGenerator.GetGenerator().location(args[0], w, t); + l.getWorld().setSpawnLocation(l); + return CVoid.VOID; + } else if(args.length == 2) { + w = Static.getServer().getWorld(args[0].val()); + MCLocation l = ObjectGenerator.GetGenerator().location(args[1], w, t); + w.setSpawnLocation(l); + return CVoid.VOID; + } + int x = 0; int y = 0; int z = 0; - if (args.length == 1) { - MCLocation l = ObjectGenerator.GetGenerator().location(args[0], w, t); - w = l.getWorld(); - x = l.getBlockX(); - y = l.getBlockY(); - z = l.getBlockZ(); - } else if (args.length == 3) { - x = Static.getInt32(args[0], t); - y = Static.getInt32(args[1], t); - z = Static.getInt32(args[2], t); - } else if (args.length == 4) { + if(args.length == 3) { + MCPlayer p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); + if(p != null) { + w = p.getWorld(); + } + x = ArgumentValidation.getInt32(args[0], t); + y = ArgumentValidation.getInt32(args[1], t); + z = ArgumentValidation.getInt32(args[2], t); + } else if(args.length == 4) { w = Static.getServer().getWorld(args[0].val()); - x = Static.getInt32(args[1], t); - y = Static.getInt32(args[2], t); - z = Static.getInt32(args[3], t); + x = ArgumentValidation.getInt32(args[1], t); + y = ArgumentValidation.getInt32(args[2], t); + z = ArgumentValidation.getInt32(args[3], t); } - if (w == null) { - throw new ConfigRuntimeException("Invalid world given.", ExceptionType.InvalidWorldException, t); + if(w == null) { + throw new CREInvalidWorldException("Invalid world given.", t); } w.setSpawnLocation(x, y, z); return CVoid.VOID; @@ -166,23 +204,23 @@ public String getName() { @Override public Integer[] numArgs() { - return new Integer[]{1, 3, 4}; + return new Integer[]{1, 2, 3, 4}; } @Override public String docs() { - return "void {locationArray | [world], x, y, z} Sets the spawn of the world. Note that in some cases, a plugin" - + " may set the spawn differently, and this method will do nothing. In that case, you should use" - + " the plugin's commands to set the spawn."; + return "void {[world], locationArray | [world], x, y, z} Sets the spawn of the world." + + " Note that in some cases a plugin may override the spawn."; } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } } - @api(environments=CommandHelperEnvironment.class) + @api(environments = CommandHelperEnvironment.class) + @hide("Deprecated.") public static class refresh_chunk extends AbstractFunction { @Override @@ -197,13 +235,13 @@ public Integer[] numArgs() { @Override public String docs() { - return "void {[world], x, z | [world], locationArray} Resends the chunk data to all clients, using the specified world, or the current" - + " players world if not provided."; + return "void {[world], x, z | [world], locationArray} This is not guaranteed to work reliably! Resends the" + + " chunk data to all clients, using the specified world or current player's world."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException, ExceptionType.FormatException, ExceptionType.InvalidWorldException}; + public Class[] thrown() { + return new Class[]{CRECastException.class, CREFormatException.class, CREInvalidWorldException.class}; } @Override @@ -212,8 +250,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_0; + public MSVersion since() { + return MSVersion.V3_3_0; } @Override @@ -222,203 +260,216 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCPlayer m = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); MCWorld world; int x; int z; - if (args.length == 1) { + if(args.length == 1) { //Location array provided MCLocation l = ObjectGenerator.GetGenerator().location(args[0], m != null ? m.getWorld() : null, t); world = l.getWorld(); x = l.getBlockX(); z = l.getBlockZ(); - } else if (args.length == 2) { + } else if(args.length == 2) { //Either location array and world provided, or x and z. Test for array at pos 2 - if (args[1] instanceof CArray) { + if(args[1].isInstanceOf(CArray.TYPE)) { world = Static.getServer().getWorld(args[0].val()); MCLocation l = ObjectGenerator.GetGenerator().location(args[1], null, t); x = l.getBlockX(); z = l.getBlockZ(); } else { - if (m == null) { - throw new ConfigRuntimeException("No world specified", ExceptionType.InvalidWorldException, t); + if(m == null) { + throw new CREInvalidWorldException("No world specified", t); } world = m.getWorld(); - x = Static.getInt32(args[0], t); - z = Static.getInt32(args[1], t); + x = ArgumentValidation.getInt32(args[0], t); + z = ArgumentValidation.getInt32(args[1], t); } } else { //world, x and z provided world = Static.getServer().getWorld(args[0].val()); - x = Static.getInt32(args[1], t); - z = Static.getInt32(args[2], t); + if(world == null) { + throw new CREInvalidWorldException("World " + args[0].val() + " does not exist.", t); + } + x = ArgumentValidation.getInt32(args[1], t); + z = ArgumentValidation.getInt32(args[2], t); } world.refreshChunk(x, z); return CVoid.VOID; } } - @api + @api(environments = {CommandHelperEnvironment.class}) public static class load_chunk extends AbstractFunction { - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException, ExceptionType.FormatException, ExceptionType.InvalidWorldException}; - } - - @Override - public boolean isRestricted() { - return true; - } - - @Override - public Boolean runAsync() { - return false; - } - - @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - MCPlayer m = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); - MCWorld world; - int x; - int z; - if (args.length == 1) { - //Location array provided - MCLocation l = ObjectGenerator.GetGenerator().location(args[0], m != null ? m.getWorld() : null, t); - world = l.getWorld(); - x = l.getBlockX(); - z = l.getBlockZ(); - } else if (args.length == 2) { - //Either location array and world provided, or x and z. Test for array at pos 2 - if (args[1] instanceof CArray) { - world = Static.getServer().getWorld(args[0].val()); - MCLocation l = ObjectGenerator.GetGenerator().location(args[1], null, t); - x = l.getBlockX(); - z = l.getBlockZ(); - } else { - if (m == null) { - throw new ConfigRuntimeException("No world specified", ExceptionType.InvalidWorldException, t); - } - world = m.getWorld(); - x = Static.getInt32(args[0], t); - z = Static.getInt32(args[1], t); - } - } else { - //world, x and z provided - world = Static.getServer().getWorld(args[0].val()); - x = Static.getInt32(args[1], t); - z = Static.getInt32(args[2], t); - } - world.loadChunk(x, z); - return CVoid.VOID; - } - - @Override - public String getName() { - return "load_chunk"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{1, 2, 3}; - } - - @Override - public String docs() { - return "void {[world], x, z | [world], locationArray} Loads the chunk, using the specified world, or the current" - + " players world if not provided."; - } - - @Override - public Version since() { - return CHVersion.V3_3_1; - } - } - - @api(environments=CommandHelperEnvironment.class) - public static class unload_chunk extends AbstractFunction { - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException, ExceptionType.FormatException, ExceptionType.InvalidWorldException}; - } - - @Override - public boolean isRestricted() { - return true; - } - - @Override - public Boolean runAsync() { - return false; - } - - @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - MCPlayer m = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); - MCWorld world; - int x; - int z; - if (args.length == 1) { - //Location array provided - MCLocation l = ObjectGenerator.GetGenerator().location(args[0], m != null ? m.getWorld() : null, t); - world = l.getWorld(); - x = l.getBlockX(); - z = l.getBlockZ(); - } else if (args.length == 2) { - //Either location array and world provided, or x and z. Test for array at pos 2 - if (args[1] instanceof CArray) { - world = Static.getServer().getWorld(args[0].val()); - MCLocation l = ObjectGenerator.GetGenerator().location(args[1], null, t); - x = l.getBlockX(); - z = l.getBlockZ(); - } else { - if (m == null) { - throw new ConfigRuntimeException("No world specified", ExceptionType.InvalidWorldException, t); - } - world = m.getWorld(); - x = Static.getInt32(args[0], t); - z = Static.getInt32(args[1], t); - } - } else { - //world, x and z provided - world = Static.getServer().getWorld(args[0].val()); - x = Static.getInt32(args[1], t); - z = Static.getInt32(args[2], t); - } - world.unloadChunk(x, z); - return CVoid.VOID; - } - - @Override - public String getName() { - return "unload_chunk"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{1, 2, 3}; - } - - @Override - public String docs() { - return "void {[world], x, z | [world], locationArray} Unloads the chunk, using the specified world, or the current" - + " players world if not provided."; - } - - @Override - public Version since() { - return CHVersion.V3_3_1; - } - } - - @api + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class, CREFormatException.class, CREInvalidWorldException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCPlayer m = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); + MCWorld world; + int x; + int z; + if(args.length == 1) { + //Location array provided + MCLocation l = ObjectGenerator.GetGenerator().location(args[0], m != null ? m.getWorld() : null, t); + world = l.getWorld(); + x = l.getBlockX(); + z = l.getBlockZ(); + } else if(args.length == 2) { + //Either location array and world provided, or x and z. Test for array at pos 2 + if(args[1].isInstanceOf(CArray.TYPE)) { + world = Static.getServer().getWorld(args[0].val()); + if(world == null) { + throw new CREInvalidWorldException("The given world (" + args[0].val() + ") does not exist.", t); + } + MCLocation l = ObjectGenerator.GetGenerator().location(args[1], null, t); + x = l.getBlockX(); + z = l.getBlockZ(); + } else { + if(m == null) { + throw new CREInvalidWorldException("No world specified", t); + } + world = m.getWorld(); + x = ArgumentValidation.getInt32(args[0], t); + z = ArgumentValidation.getInt32(args[1], t); + } + } else { + //world, x and z provided + world = Static.getServer().getWorld(args[0].val()); + if(world == null) { + throw new CREInvalidWorldException("The given world (" + args[0].val() + ") does not exist.", t); + } + x = ArgumentValidation.getInt32(args[1], t); + z = ArgumentValidation.getInt32(args[2], t); + } + world.loadChunk(x, z); + return CVoid.VOID; + } + + @Override + public String getName() { + return "load_chunk"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1, 2, 3}; + } + + @Override + public String docs() { + return "void {[world], x, z | [world], locationArray} Loads a chunk for a world using the" + + " x and z coordinates. The current player's world is used if one isn't provided."; + } + + @Override + public Version since() { + return MSVersion.V3_3_1; + } + } + + @api(environments = CommandHelperEnvironment.class) + public static class unload_chunk extends AbstractFunction { + + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class, CREFormatException.class, CREInvalidWorldException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCPlayer m = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); + MCWorld world; + int x; + int z; + if(args.length == 1) { + //Location array provided + MCLocation l = ObjectGenerator.GetGenerator().location(args[0], m != null ? m.getWorld() : null, t); + world = l.getWorld(); + x = l.getBlockX(); + z = l.getBlockZ(); + } else if(args.length == 2) { + //Either location array and world provided, or x and z. Test for array at pos 2 + if(args[1].isInstanceOf(CArray.TYPE)) { + world = Static.getServer().getWorld(args[0].val()); + MCLocation l = ObjectGenerator.GetGenerator().location(args[1], null, t); + x = l.getBlockX(); + z = l.getBlockZ(); + } else { + if(m == null) { + throw new CREInvalidWorldException("No world specified", t); + } + world = m.getWorld(); + x = ArgumentValidation.getInt32(args[0], t); + z = ArgumentValidation.getInt32(args[1], t); + } + } else { + //world, x and z provided + world = Static.getServer().getWorld(args[0].val()); + x = ArgumentValidation.getInt32(args[1], t); + z = ArgumentValidation.getInt32(args[2], t); + } + if(world == null) { // Happens when m is a fake console or null command sender. + throw new CREInvalidWorldException("No world specified", t); + } + world.unloadChunk(x, z); + return CVoid.VOID; + } + + @Override + public String getName() { + return "unload_chunk"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1, 2, 3}; + } + + @Override + public String docs() { + return "void {[world], x, z | [world], locationArray} Unloads a chunk for a world using the" + + " x and z coordinates. The current player's world is used if one is not provided."; + } + + @Override + public Version since() { + return MSVersion.V3_3_1; + } + } + + @api(environments = {CommandHelperEnvironment.class}) public static class get_loaded_chunks extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException, ExceptionType.FormatException, ExceptionType.InvalidWorldException}; + public Class[] thrown() { + return new Class[]{CRECastException.class, CREFormatException.class, + CREInvalidWorldException.class, CRENotFoundException.class}; } @Override @@ -432,29 +483,35 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCPlayer m = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); - MCWorld world; - if (args.length == 1) { - // World Provided - world = Static.getServer().getWorld(args[0].val()); - } else { - if (m == null) { - throw new ConfigRuntimeException("No world specified", ExceptionType.InvalidWorldException, t); - } - world = m.getWorld(); - } - MCChunk[] chunks = world.getLoadedChunks(); + MCWorld world; + if(args.length == 1) { + // World Provided + world = Static.getServer().getWorld(args[0].val()); + } else { + if(m == null) { + throw new CREInvalidWorldException("No world specified", t); + } + world = m.getWorld(); + } + if(world == null) { // Happens when m is a fake console or null command sender. + throw new CREInvalidWorldException("No world specified", t); + } + MCChunk[] chunks = world.getLoadedChunks(); + if(chunks == null) { // Happens when m is a fake player. + throw new CRENotFoundException( + "Could not find the chunk objects of the world (are you running in cmdline mode?)", t); + } CArray ret = new CArray(t); - for (int i = 0; i < chunks.length; i++) { - CArray chunk = new CArray(t); - chunk.set("x", new CInt(chunks[i].getX(), t), t); - chunk.set("z", new CInt(chunks[i].getZ(), t), t); - chunk.set("world", chunks[i].getWorld().getName(), t); - ret.set(i, chunk, t); + for(MCChunk c : chunks) { + CArray chunk = CArray.GetAssociativeArray(t); + chunk.set("x", new CInt(c.getX(), t), t); + chunk.set("z", new CInt(c.getZ(), t), t); + chunk.set("world", c.getWorld().getName(), t); + ret.push(chunk, t); } - - return ret; + return ret; } @Override @@ -464,23 +521,163 @@ public String getName() { @Override public Integer[] numArgs() { - return new Integer[] {0, 1}; + return new Integer[]{0, 1}; + } + + @Override + public String docs() { + return "array {[world]} Gets an array of all currently loaded chunks for a world." + + " The current player's world is used if one is not provided." + + " The chunk objects are associative arrays with the keys: x, z, and world."; + } + + @Override + public Version since() { + return MSVersion.V3_3_1; + } + + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class set_chunk_force_loaded extends AbstractFunction { + + @Override + public String getName() { + return "set_chunk_force_loaded"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{2, 3, 4}; + } + + @Override + public String docs() { + return "void {[world], x, z, forced | locationArray, forced} Sets a chunk to be persistently loaded."; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCPlayer m = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); + MCWorld world; + int x; + int z; + boolean forced; + if(args.length == 2) { + MCLocation l = ObjectGenerator.GetGenerator().location(args[0], m != null ? m.getWorld() : null, t); + world = l.getWorld(); + x = l.getBlockX(); + z = l.getBlockZ(); + forced = ArgumentValidation.getBooleanObject(args[1], t); + } else if(args.length == 3) { + if(m == null) { + throw new CREInvalidWorldException("No world specified", t); + } + world = m.getWorld(); + x = ArgumentValidation.getInt32(args[0], t); + z = ArgumentValidation.getInt32(args[1], t); + forced = ArgumentValidation.getBooleanObject(args[2], t); + } else { + world = Static.getServer().getWorld(args[0].val()); + if(world == null) { + throw new CREInvalidWorldException("The given world (" + args[0].val() + ") does not exist.", t); + } + x = ArgumentValidation.getInt32(args[1], t); + z = ArgumentValidation.getInt32(args[2], t); + forced = ArgumentValidation.getBooleanObject(args[3], t); + } + world.setChunkForceLoaded(x, z, forced); + return CVoid.VOID; + } + + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class, CREFormatException.class, CREInvalidWorldException.class}; + } + + @Override + public Version since() { + return MSVersion.V3_3_5; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class get_force_loaded_chunks extends AbstractFunction { + + @Override + public String getName() { + return "get_force_loaded_chunks"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{0, 1}; } @Override public String docs() { - return "array {[world]} Gets all currently loaded chunks, in the specified world, or the current" - + " players world if not provided."; + return "array {[world]} Gets an array of all chunk coordinates that are persistently loaded."; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCWorld world; + if(args.length == 1) { + world = Static.getServer().getWorld(args[0].val()); + } else { + MCPlayer m = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); + if(m == null) { + throw new CREInvalidWorldException("No world specified", t); + } + world = m.getWorld(); + } + if(world == null) { + throw new CREInvalidWorldException("World does not exist", t); + } + MCChunk[] chunks = world.getForceLoadedChunks(); + CArray ret = new CArray(t); + for(MCChunk c : chunks) { + CArray chunk = CArray.GetAssociativeArray(t); + chunk.set("x", new CInt(c.getX(), t), t); + chunk.set("z", new CInt(c.getZ(), t), t); + chunk.set("world", c.getWorld().getName(), t); + ret.push(chunk, t); + } + return ret; + } + + @Override + public Class[] thrown() { + return new Class[]{CREInvalidWorldException.class}; } @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_5; + } + + @Override + public boolean isRestricted() { + return false; } + @Override + public Boolean runAsync() { + return false; + } } - @api(environments=CommandHelperEnvironment.class) + @api(environments = CommandHelperEnvironment.class) public static class regen_chunk extends AbstractFunction { @Override @@ -495,13 +692,15 @@ public Integer[] numArgs() { @Override public String docs() { - return "void {x, z, [world]| locationArray, [world]} Regenerate the chunk, using the specified world, or the current" - + " players world if not provided. Beware that this is destructive! Any data in this chunk will be lost!"; + return "boolean {x, z, [world]| locationArray, [world]} Regenerate the chunk for a world." + + " The current player's world is used if one is not provided. Beware that this is destructive!" + + " Any data in this chunk will be lost! Returns true if the operation was successful." + + " This function is deprecated. Results will vary per platform and may not work at all."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException, ExceptionType.FormatException, ExceptionType.InvalidWorldException}; + public Class[] thrown() { + return new Class[]{CRECastException.class, CREFormatException.class, CREInvalidWorldException.class}; } @Override @@ -510,8 +709,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } @Override @@ -520,48 +719,58 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCPlayer m = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); MCWorld world; int x; int z; - if (args.length == 1) { + if(args.length == 1) { //Location array provided MCLocation l = ObjectGenerator.GetGenerator().location(args[0], m != null ? m.getWorld() : null, t); world = l.getWorld(); - x = l.getChunk().getX(); - z = l.getChunk().getZ(); - } else if (args.length == 2) { + x = l.getBlockX() >> 4; + z = l.getBlockZ() >> 4; + } else if(args.length == 2) { //Either location array and world provided, or x and z. Test for array at pos 1 - if (args[0] instanceof CArray) { + if(args[0].isInstanceOf(CArray.TYPE)) { world = Static.getServer().getWorld(args[1].val()); + if(world == null) { + throw new CREInvalidWorldException("World " + args[1].val() + " does not exist.", t); + } MCLocation l = ObjectGenerator.GetGenerator().location(args[0], null, t); - x = l.getChunk().getX(); - z = l.getChunk().getZ(); + x = l.getBlockX() >> 4; + z = l.getBlockZ() >> 4; } else { - if (m == null) { - throw new ConfigRuntimeException("No world specified", ExceptionType.InvalidWorldException, t); + if(m == null) { + throw new CREInvalidWorldException("No world specified", t); } world = m.getWorld(); - x = Static.getInt32(args[0], t); - z = Static.getInt32(args[1], t); + x = ArgumentValidation.getInt32(args[0], t); + z = ArgumentValidation.getInt32(args[1], t); } } else { //world, x and z provided - x = Static.getInt32(args[0], t); - z = Static.getInt32(args[1], t); + x = ArgumentValidation.getInt32(args[0], t); + z = ArgumentValidation.getInt32(args[1], t); world = Static.getServer().getWorld(args[2].val()); + if(world == null) { + throw new CREInvalidWorldException("World " + args[2].val() + " does not exist.", t); + } } - return CBoolean.get(world.regenerateChunk(x, z)); + try { + return CBoolean.get(world.regenerateChunk(x, z)); + } catch (UnsupportedOperationException ex) { + return CBoolean.FALSE; + } } } - @api(environments=CommandHelperEnvironment.class) + @api(environments = CommandHelperEnvironment.class) public static class is_slime_chunk extends AbstractFunction { @Override @@ -576,13 +785,13 @@ public Integer[] numArgs() { @Override public String docs() { - return "boolean {x, z, [world]| locationArray, [world]} Returns if the chunk is a slime spawning chunk, using the specified world, or the current" - + " players world if not provided."; + return "boolean {x, z, [world]| locationArray, [world]} Returns if the chunk is a slime spawning chunk." + + " The current player's world is used if one is not provided."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException, ExceptionType.FormatException, ExceptionType.InvalidWorldException}; + public Class[] thrown() { + return new Class[]{CRECastException.class, CREFormatException.class, CREInvalidWorldException.class}; } @Override @@ -591,8 +800,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } @Override @@ -603,64 +812,65 @@ public Boolean runAsync() { Random rnd = new Random(); @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCPlayer m = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); MCWorld world; int x; int z; - long seed; - if (args.length == 1) { + if(args.length == 1) { //Location array provided MCLocation l = ObjectGenerator.GetGenerator().location(args[0], m != null ? m.getWorld() : null, t); world = l.getWorld(); - seed = world.getSeed(); - x = l.getChunk().getX(); - z = l.getChunk().getZ(); - } else if (args.length == 2) { + x = l.getBlockX() >> 4; + z = l.getBlockZ() >> 4; + } else if(args.length == 2) { //Either location array and world provided, or x and z. Test for array at pos 1 - if (args[0] instanceof CArray) { + if(args[0].isInstanceOf(CArray.TYPE)) { world = Static.getServer().getWorld(args[1].val()); - seed = world.getSeed(); + if(world == null) { + throw new CREInvalidWorldException("The given world (" + args[1].val() + ") does not exist.", t); + } MCLocation l = ObjectGenerator.GetGenerator().location(args[0], null, t); - x = l.getChunk().getX(); - z = l.getChunk().getZ(); + x = l.getBlockX() >> 4; + z = l.getBlockZ() >> 4; } else { - if (m == null) { - throw new ConfigRuntimeException("No world specified", ExceptionType.InvalidWorldException, t); + if(m == null) { + throw new CREInvalidWorldException("No world specified", t); } world = m.getWorld(); - seed = world.getSeed(); - x = Static.getInt32(args[0], t); - z = Static.getInt32(args[1], t); + x = ArgumentValidation.getInt32(args[0], t); + z = ArgumentValidation.getInt32(args[1], t); } } else { //world, x and z provided - x = Static.getInt32(args[0], t); - z = Static.getInt32(args[1], t); + x = ArgumentValidation.getInt32(args[0], t); + z = ArgumentValidation.getInt32(args[1], t); world = Static.getServer().getWorld(args[2].val()); - seed = world.getSeed(); - } - rnd.setSeed(seed - + x * x * 0x4c1906 - + x * 0x5ac0db - + z * z * 0x4307a7L - + z * 0x5f24f - ^ 0x3ad8025f); + if(world == null) { + throw new CREInvalidWorldException("The given world (" + args[2].val() + ") does not exist.", t); + } + } + rnd.setSeed(world.getSeed() + + x * x * 0x4c1906 + + x * 0x5ac0db + + z * z * 0x4307a7L + + z * 0x5f24f + ^ 0x3ad8025f); return CBoolean.get(rnd.nextInt(10) == 0); } } - private static final SortedMap TimeLookup = new TreeMap(); + private static final SortedMap TIME_LOOKUP = new TreeMap<>(); static { - synchronized (World.class) { + synchronized(World.class) { Properties p = new Properties(); try { p.load(Minecraft.class.getResourceAsStream("/time_names.txt")); Enumeration e = p.propertyNames(); - while (e.hasMoreElements()) { + while(e.hasMoreElements()) { String name = e.nextElement().toString(); - TimeLookup.put(name, new CString(p.getProperty(name).toString(), Target.UNKNOWN)); + TIME_LOOKUP.put(name, new CString(p.getProperty(name), Target.UNKNOWN)); } } catch (IOException ex) { Logger.getLogger(World.class.getName()).log(Level.SEVERE, null, ex); @@ -668,7 +878,7 @@ public Construct exec(Target t, Environment environment, Construct... args) thro } } - @api(environments=CommandHelperEnvironment.class) + @api(environments = CommandHelperEnvironment.class) public static class set_world_time extends AbstractFunction { @Override @@ -684,13 +894,13 @@ public Integer[] numArgs() { @Override public String docs() { StringBuilder doc = new StringBuilder(); - synchronized (World.class) { + synchronized(World.class) { doc.append("void {[world], time} Sets the time of a given world. Should be a number from 0 to" - + " 24000, if not, it is modulo scaled. ---- Alternatively, common time notation (9:30pm, 4:00 am)" - + " is acceptable, and convenient english mappings also exist:"); + + " 24000, if not, it is modulo scaled. ---- Alternatively, common time notation" + + " (9:30pm, 4:00 am) is acceptable, and convenient english mappings also exist:"); doc.append("
    "); - for (String key : TimeLookup.keySet()) { - doc.append("
  • ").append(key).append(" = ").append(TimeLookup.get(key)).append("
  • "); + for(String key : TIME_LOOKUP.keySet()) { + doc.append("
  • ").append(key).append(" = ").append(TIME_LOOKUP.get(key)).append("
  • "); } doc.append("
"); } @@ -698,8 +908,8 @@ public String docs() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException, ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CREInvalidWorldException.class, CREFormatException.class}; } @Override @@ -708,8 +918,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_0; + public MSVersion since() { + return MSVersion.V3_3_0; } @Override @@ -718,43 +928,42 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCWorld w = null; - if (environment.getEnv(CommandHelperEnvironment.class).GetPlayer() != null) { + if(environment.getEnv(CommandHelperEnvironment.class).GetPlayer() != null) { w = environment.getEnv(CommandHelperEnvironment.class).GetPlayer().getWorld(); } - if (args.length == 2) { + if(args.length == 2) { w = Static.getServer().getWorld(args[0].val()); } - if (w == null) { - throw new ConfigRuntimeException("No world specified", ExceptionType.InvalidWorldException, t); + if(w == null) { + throw new CREInvalidWorldException("No world specified", t); } - long time = 0; String stime = (args.length == 1 ? args[0] : args[1]).val().toLowerCase(); - if (TimeLookup.containsKey(stime.replaceAll("[^a-z]", ""))) { - stime = TimeLookup.get(stime.replaceAll("[^a-z]", "")).val(); + if(TIME_LOOKUP.containsKey(stime.replaceAll("[^a-z]", ""))) { + stime = TIME_LOOKUP.get(stime.replaceAll("[^a-z]", "")).val(); } - if (stime.matches("^([\\d]+)[:.]([\\d]+)[ ]*?(?:([pa])\\.*m\\.*){0,1}$")) { + if(stime.matches("^([\\d]+)[:.]([\\d]+)[ ]*?(?:([pa])\\.*m\\.*){0,1}$")) { Pattern p = Pattern.compile("^([\\d]+)[:.]([\\d]+)[ ]*?(?:([pa])\\.*m\\.*){0,1}$"); Matcher m = p.matcher(stime); m.find(); int hour = Integer.parseInt(m.group(1)); int minute = Integer.parseInt(m.group(2)); String offset = "a"; - if (m.group(3) != null) { + if(m.group(3) != null) { offset = m.group(3); } - if (offset.equals("p")) { + if(offset.equals("p")) { hour += 12; } - if (hour == 24) { + if(hour == 24) { hour = 0; } - if (hour > 24) { - throw new ConfigRuntimeException("Invalid time provided", ExceptionType.FormatException, t); + if(hour > 24) { + throw new CREFormatException("Invalid time provided", t); } - if (minute > 59) { - throw new ConfigRuntimeException("Invalid time provided", ExceptionType.FormatException, t); + if(minute > 59) { + throw new CREFormatException("Invalid time provided", t); } hour -= 6; hour = hour % 24; @@ -763,17 +972,15 @@ public Construct exec(Target t, Environment environment, Construct... args) thro stime = Long.toString(ttime); } try { - Long.valueOf(stime); + w.setTime(Long.parseLong(stime)); } catch (NumberFormatException e) { - throw new ConfigRuntimeException("Invalid time provided", ExceptionType.FormatException, t); + throw new CREFormatException("Invalid time provided", t); } - time = Long.parseLong(stime); - w.setTime(time); return CVoid.VOID; } } - @api(environments=CommandHelperEnvironment.class) + @api(environments = CommandHelperEnvironment.class) public static class get_world_time extends AbstractFunction { @Override @@ -788,13 +995,12 @@ public Integer[] numArgs() { @Override public String docs() { - return "int {[world]} Returns the time of the specified world, as an integer from" - + " 0 to 24000-1"; + return "int {[world]} Returns the time of the specified world, as an integer from 0 to 24000-1"; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException}; + public Class[] thrown() { + return new Class[]{CREInvalidWorldException.class}; } @Override @@ -803,8 +1009,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_0; + public MSVersion since() { + return MSVersion.V3_3_0; } @Override @@ -813,27 +1019,42 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCWorld w = null; - if (environment.getEnv(CommandHelperEnvironment.class).GetPlayer() != null) { + if(environment.getEnv(CommandHelperEnvironment.class).GetPlayer() != null) { w = environment.getEnv(CommandHelperEnvironment.class).GetPlayer().getWorld(); } - if (args.length == 1) { + if(args.length == 1) { w = Static.getServer().getWorld(args[0].val()); } - if (w == null) { - throw new ConfigRuntimeException("No world specified", ExceptionType.InvalidWorldException, t); + if(w == null) { + throw new CREInvalidWorldException("No world specified", t); } return new CInt(w.getTime(), t); } } - @api(environments={CommandHelperEnvironment.class}) - public static class create_world extends AbstractFunction{ + @api(environments = CommandHelperEnvironment.class) + public static class set_world_day extends AbstractFunction { + + @Override + public String getName() { + return "set_world_day"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1, 2}; + } + + @Override + public String docs() { + return "void {[world], day} Set the current day number of a given world"; + } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.FormatException, ExceptionType.CastException}; + public Class[] thrown() { + return new Class[]{CREInvalidWorldException.class}; } @Override @@ -842,36 +1063,137 @@ public boolean isRestricted() { } @Override - public Boolean runAsync() { - return false; + public MSVersion since() { + return MSVersion.V3_3_4; } @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCWorld w = null; + if(environment.getEnv(CommandHelperEnvironment.class).GetPlayer() != null) { + w = environment.getEnv(CommandHelperEnvironment.class).GetPlayer().getWorld(); + } + if(args.length == 2) { + w = Static.getServer().getWorld(args[0].val()); + } + if(w == null) { + throw new CREInvalidWorldException("No world specified", t); + } + + int day = ArgumentValidation.getInt32((args.length == 1 ? args[0] : args[1]), t); + if(day < 0) { + throw new CRERangeException("Day cannot be negative.", t); + } + + w.setFullTime((day * 24000) + w.getTime()); + return CVoid.VOID; + } + } + + @api(environments = CommandHelperEnvironment.class) + public static class get_world_day extends AbstractFunction { + + @Override + public String getName() { + return "get_world_day"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{0, 1}; + } + + @Override + public String docs() { + return "int {[world]} Returns the current day number of the specified world"; + } + + @Override + public Class[] thrown() { + return new Class[]{CREInvalidWorldException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public MSVersion since() { + return MSVersion.V3_3_4; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCWorld w = null; + if(environment.getEnv(CommandHelperEnvironment.class).GetPlayer() != null) { + w = environment.getEnv(CommandHelperEnvironment.class).GetPlayer().getWorld(); + } + if(args.length == 1) { + w = Static.getServer().getWorld(args[0].val()); + } + if(w == null) { + throw new CREInvalidWorldException("No world specified", t); + } + return new CInt((long) java.lang.Math.floor(w.getFullTime() / 24000), t); + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class create_world extends AbstractFunction { + + @Override + public Class[] thrown() { + return new Class[]{CREFormatException.class, CRECastException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { MCWorldCreator creator = StaticLayer.GetConvertor().getWorldCreator(args[0].val()); - if (args.length >= 3) { + if(args.length >= 3) { MCWorldType type; try { type = MCWorldType.valueOf(args[1].val().toUpperCase()); } catch (IllegalArgumentException e) { - throw new ConfigRuntimeException(args[1].val() + " is not a valid world type. Must be one of: " + StringUtils.Join(MCWorldType.values(), ", "), ExceptionType.FormatException, t); + throw new CREFormatException(args[1].val() + " is not a valid world type.", t); } MCWorldEnvironment environment; try { environment = MCWorldEnvironment.valueOf(args[2].val().toUpperCase()); } catch (IllegalArgumentException e) { - throw new ConfigRuntimeException(args[2].val() + " is not a valid environment type. Must be one of: " + StringUtils.Join(MCWorldEnvironment.values(), ", "), ExceptionType.FormatException, t); + throw new CREFormatException(args[2].val() + " is not a valid environment type. Must be one of: " + + StringUtils.Join(MCWorldEnvironment.values(), ", "), t); } creator.type(type).environment(environment); } - if ((args.length >= 4) && !(args[3] instanceof CNull)) { - if (args[3] instanceof CInt) { - creator.seed(Static.getInt(args[3], t)); + if((args.length >= 4) && !(args[3] instanceof CNull)) { + if(args[3].isInstanceOf(CInt.TYPE)) { + creator.seed(ArgumentValidation.getInt(args[3], t)); } else { creator.seed(args[3].val().hashCode()); } } - if (args.length == 5) { + if(args.length == 5) { creator.generator(args[4].val()); } creator.createWorld(); @@ -890,25 +1212,28 @@ public Integer[] numArgs() { @Override public String docs() { - return "void {name, [type, environment, [seed, [generator]]]} Creates a new world with the specified options." - + " If the world already exists, it will be loaded from disk, and the last 3 arguments may be" - + " ignored. name is the name of the world, type is one of " - + StringUtils.Join(MCWorldType.values(), ", ") + " and environment is one of " - + StringUtils.Join(MCWorldEnvironment.values(), ", ") + ". The seed can be an integer, a string (will be the hashcode), or null (will be random int)." - + " Generator is the name of a world generator loaded on the server."; + return "void {name, type, environment, [seed, [generator]]} Creates or loads a world." + + " The 'name' is the same as the directory name for the world." + + " If a world directory by that name already exists, it will be loaded." + + " The 'type' is one of " + StringUtils.Join(MCWorldType.values(), ", ") + "." + + " However, if level.dat exists in the world directory, the type from that will be used instead." + + " The 'environment' is one of " + StringUtils.Join(MCWorldEnvironment.values(), ", ") + "." + + " This affects which regions directory is used in the world directory." + + " The 'seed' can be an integer, a string (will be the hashcode), or null (will be random int)." + + " The 'generator' is the name of a custom world generator loaded on the server."; } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } } - @api(environments={CommandHelperEnvironment.class}) - public static class get_worlds extends AbstractFunction{ + @api(environments = {CommandHelperEnvironment.class}) + public static class get_worlds extends AbstractFunction { @Override - public ExceptionType[] thrown() { + public Class[] thrown() { return null; } @@ -923,10 +1248,10 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { CArray worlds = new CArray(t); - for(MCWorld w : Static.getServer().getWorlds()){ - worlds.push(new CString(w.getName(), t)); + for(MCWorld w : Static.getServer().getWorlds()) { + worlds.push(new CString(w.getName(), t), t); } return worlds; } @@ -947,12 +1272,12 @@ public String docs() { } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } } - @api(environments={CommandHelperEnvironment.class}) + @api(environments = {CommandHelperEnvironment.class}) public static class get_chunk_loc extends AbstractFunction { @Override @@ -966,53 +1291,56 @@ public Integer[] numArgs() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws CancelCommandException, ConfigRuntimeException { MCCommandSender cs = env.getEnv(CommandHelperEnvironment.class).GetCommandSender(); MCPlayer p = null; MCWorld w = null; MCLocation l = null; - if (cs instanceof MCPlayer) { - p = (MCPlayer)cs; + if(cs instanceof MCPlayer) { + p = (MCPlayer) cs; Static.AssertPlayerNonNull(p, t); l = p.getLocation(); + if(l == null) { + throw new CRENotFoundException( + "Could not find the location of the given player (are you running in cmdline mode?)", t); + } w = l.getWorld(); } - if (args.length == 1) { - if (args[0] instanceof CArray) { + if(args.length == 1) { + if(args[0].isInstanceOf(CArray.TYPE)) { l = ObjectGenerator.GetGenerator().location(args[0], w, t); } else { - throw new ConfigRuntimeException("expecting argument 1 of get_chunk_loc to be a location array" - , ExceptionType.FormatException, t); + throw new CREFormatException("Expecting argument 1 of get_chunk_loc to be a location array", t); } } else { - if (p == null) { - throw new ConfigRuntimeException("expecting a player context for get_chunk_loc when used without arguments" - , ExceptionType.InsufficientArgumentsException, t); + if(p == null) { + throw new CREInsufficientArgumentsException( + "Expecting a player context for get_chunk_loc when used without arguments", t); } } - CArray chunk = new CArray(t, - new CInt(l.getChunk().getX(), t), - new CInt(l.getChunk().getZ(), t), - new CString(l.getChunk().getWorld().getName(), t)); - chunk.set("x", new CInt(l.getChunk().getX(), t), t); - chunk.set("z", new CInt(l.getChunk().getZ(), t), t); - chunk.set("world", l.getChunk().getWorld().getName(), t); + CArray chunk = CArray.GetAssociativeArray(t); + chunk.set(0, new CInt(l.getBlockX() >> 4, t), t); + chunk.set(1, new CInt(l.getBlockZ() >> 4, t), t); + chunk.set(2, new CString(l.getWorld().getName(), t), t); + chunk.set("x", new CInt(l.getBlockX() >> 4, t), t); + chunk.set("z", new CInt(l.getBlockZ() >> 4, t), t); + chunk.set("world", l.getWorld().getName(), t); return chunk; } @Override public String docs() { - return "array {[location array]} Returns an array of x, z, world " - + "coords of the chunk of either the location specified or the location of " - + "the player running the command."; + return "array {[locationArray]} Returns an array of (x, z, world) coordinates of the chunk of either the" + + " location specified or the location of the player running the command."; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.FormatException, ExceptionType.InsufficientArgumentsException}; + public Class[] thrown() { + return new Class[]{CREFormatException.class, CREInsufficientArgumentsException.class, + CRENotFoundException.class}; } @Override @@ -1021,8 +1349,8 @@ public boolean isRestricted() { } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } @Override @@ -1031,12 +1359,12 @@ public Boolean runAsync() { } } - @api(environments={CommandHelperEnvironment.class}) - public static class spawn_falling_block extends AbstractFunction{ + @api(environments = {CommandHelperEnvironment.class}) + public static class spawn_falling_block extends AbstractFunction implements Optimizable { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CREFormatException.class}; } @Override @@ -1050,38 +1378,25 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { MCPlayer p = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); - MCLocation loc = ObjectGenerator.GetGenerator().location(args[0], p != null ? p.getWorld() : null , t); - MCItemStack item = Static.ParseItemNotation(this.getName(), args[1].val(), 1, t); - - CArray vect = null; + MCLocation loc = ObjectGenerator.GetGenerator().location(args[0], p != null ? p.getWorld() : null, t); - if (args.length == 3) { - if (args[2] instanceof CArray) { - vect = (CArray)args[2]; - - if (vect.size() < 3) { - throw new ConfigRuntimeException("Argument 3 of spawn_falling_block must have 3 items", ExceptionType.FormatException, t); - } - } else { - throw new ConfigRuntimeException("Expected array for argument 3 of spawn_falling_block", ExceptionType.FormatException, t); - } + MCMaterial mat = StaticLayer.GetMaterial(args[1].val()); + if(mat == null) { + mat = Static.ParseItemNotation(getName(), args[1].val(), 1, t).getType(); } + if(!mat.isBlock()) { + throw new CREIllegalArgumentException("The value \"" + args[1].val() + + "\" is not a valid block material.", t); + } + MCFallingBlock block = loc.getWorld().spawnFallingBlock(loc, mat.createBlockData()); - MCFallingBlock block = loc.getWorld().spawnFallingBlock(loc, item.getType().getType(), (byte)item.getData().getData()); - - if (args.length == 3 && vect != null) { - double x = Double.valueOf(vect.get(0, t).val()); - double y = Double.valueOf(vect.get(1, t).val()); - double z = Double.valueOf(vect.get(2, t).val()); - - Velocity v = new Velocity(x, y, z); - - block.setVelocity(v); + if(args.length == 3) { + block.setVelocity(ObjectGenerator.GetGenerator().vector(args[2], t)); } - return new CInt(block.getEntityId(), t); + return new CString(block.getUniqueId().toString(), t); } @Override @@ -1096,24 +1411,41 @@ public Integer[] numArgs() { @Override public String docs() { - return "integer {location array, id[:type], [vector array ie. array(x, y, z)]} Spawns a" - + " falling block of the specified id and type at the specified location, applying" - + " vector array as a velocity if given. Values for the vector array are doubles, and 1.0" - + " seems to imply about 3 times walking speed. Gravity applies for y."; + return "string {locationArray, blockName, [vectorArray]} Spawns a falling block entity of the specified" + + " block type at the specified location, applying the vector array as a velocity if given." + + " Values for the vector array are doubles, and 1.0 seems to imply about 3 times walking speed." + + " Gravity applies for y."; } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; + } + + @Override + public ParseTree optimizeDynamic(Target t, Environment env, + Set> envs, + List children, FileOptions fileOptions) + throws ConfigCompileException, ConfigRuntimeException { + if(children.size() > 1 && children.get(1).getData().val().contains(":")) { + env.getEnv(CompilerEnvironment.class).addCompilerWarning(fileOptions, + new CompilerWarning("The 1:1 format is deprecated in " + getName(), t, null)); + } + return null; + } + + @Override + public Set optimizationOptions() { + return EnumSet.of(Optimizable.OptimizationOption.OPTIMIZE_DYNAMIC); } } - @api + @api(environments = {CommandHelperEnvironment.class}) public static class world_info extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException}; + public Class[] thrown() { + return new Class[]{CREInvalidWorldException.class}; } @Override @@ -1127,19 +1459,21 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, - Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCWorld w = Static.getServer().getWorld(args[0].val()); - if (w == null) { - throw new ConfigRuntimeException("Unknown world: " + args[0], - ExceptionType.InvalidWorldException, t); + if(w == null) { + throw new CREInvalidWorldException("Unknown world: " + args[0], t); } - CArray ret = new CArray(t); + CArray ret = CArray.GetAssociativeArray(t); ret.set("name", new CString(w.getName(), t), t); + ret.set("uuid", new CString(w.getUniqueID().toString(), t), t); ret.set("seed", new CInt(w.getSeed(), t), t); ret.set("environment", new CString(w.getEnvironment().name(), t), t); ret.set("generator", new CString(w.getGenerator(), t), t); ret.set("worldtype", new CString(w.getWorldType().name(), t), t); + ret.set("sealevel", new CInt(w.getSeaLevel(), t), t); + ret.set("minheight", new CInt(w.getMinHeight(), t), t); + ret.set("maxheight", new CInt(w.getMaxHeight(), t), t); return ret; } @@ -1155,22 +1489,23 @@ public Integer[] numArgs() { @Override public String docs() { - return "array {world} Returns an associative array of all the info needed to duplicate the world." - + " The keys are name, seed, environment, generator, and worldtype."; + return "array {world} Returns an associative array of immutable info about the world." + + " The keys are name, uuid, seed, environment, generator, sealevel, minheight and maxheight." + + " The worldtype key is deprecated as of 1.16."; } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } } - @api + @api(environments = {CommandHelperEnvironment.class}) public static class unload_world extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException}; + public Class[] thrown() { + return new Class[]{CREInvalidWorldException.class}; } @Override @@ -1184,14 +1519,14 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { boolean save = true; - if (args.length == 2) { - save = Static.getBoolean(args[1]); + if(args.length == 2) { + save = ArgumentValidation.getBoolean(args[1], t); } MCWorld world = Static.getServer().getWorld(args[0].val()); - if (world == null) { - throw new ConfigRuntimeException("Unknown world: " + args[0].val(), ExceptionType.InvalidWorldException, t); + if(world == null) { + throw new CREInvalidWorldException("Unknown world: " + args[0].val(), t); } return CBoolean.get(Static.getServer().unloadWorld(world, save)); } @@ -1214,11 +1549,11 @@ public String docs() { @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } } - @api + @api(environments = {CommandHelperEnvironment.class}) public static class get_difficulty extends AbstractFunction { @Override @@ -1232,8 +1567,8 @@ public Integer[] numArgs() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException}; + public Class[] thrown() { + return new Class[]{CREInvalidWorldException.class}; } @Override @@ -1248,25 +1583,26 @@ public Boolean runAsync() { @Override public String docs() { - return "string {world} Returns the difficulty of the world, It will be one of " + StringUtils.Join(MCDifficulty.values(), ", ", ", or ", " or ") + "."; + return "string {world} Returns the difficulty of the world, It will be one of " + + StringUtils.Join(MCDifficulty.values(), ", ", ", or ", " or ") + "."; } @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCWorld world = Static.getServer().getWorld(args[0].val()); - if (world == null) { - throw new ConfigRuntimeException("Unknown world: " + args[0].val(), ExceptionType.InvalidWorldException, t); + if(world == null) { + throw new CREInvalidWorldException("Unknown world: " + args[0].val(), t); } return new CString(world.getDifficulty().toString(), t); } } - @api + @api(environments = {CommandHelperEnvironment.class}) public static class set_difficulty extends AbstractFunction { @Override @@ -1280,8 +1616,8 @@ public Integer[] numArgs() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException, ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CREInvalidWorldException.class, CREFormatException.class}; } @Override @@ -1296,36 +1632,37 @@ public Boolean runAsync() { @Override public String docs() { - return "void {[world], difficulty} Sets the difficulty of the world with the given name, or all worlds if the name is not given." - + " difficulty can be " + StringUtils.Join(MCDifficulty.values(), ", ", ", or ", " or ") + "."; + return "void {[world], difficulty} Sets the difficulty of the world with the given name, or all worlds" + + " if the name is not given. The difficulty can be " + + StringUtils.Join(MCDifficulty.values(), ", ", ", or ", " or ") + "."; } @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCDifficulty difficulty; - if (args.length == 1) { + if(args.length == 1) { try { difficulty = MCDifficulty.valueOf(args[0].val().toUpperCase()); } catch (IllegalArgumentException exception) { - throw new ConfigRuntimeException("The difficulty \"" + args[0].val() + "\" does not exist.", ExceptionType.FormatException, t); + throw new CREFormatException("The difficulty \"" + args[0].val() + "\" does not exist.", t); } - for (MCWorld world : Static.getServer().getWorlds()) { + for(MCWorld world : Static.getServer().getWorlds()) { world.setDifficulty(difficulty); } } else { MCWorld world = Static.getServer().getWorld(args[0].val()); - if (world == null) { - throw new ConfigRuntimeException("Unknown world: " + args[0].val(), ExceptionType.InvalidWorldException, t); + if(world == null) { + throw new CREInvalidWorldException("Unknown world: " + args[0].val(), t); } try { difficulty = MCDifficulty.valueOf(args[1].val().toUpperCase()); } catch (IllegalArgumentException exception) { - throw new ConfigRuntimeException("The difficulty \"" + args[1].val() + "\" does not exist.", ExceptionType.FormatException, t); + throw new CREFormatException("The difficulty \"" + args[1].val() + "\" does not exist.", t); } world.setDifficulty(difficulty); } @@ -1333,7 +1670,7 @@ public Construct exec(Target t, Environment environment, Construct... args) thro } } - @api + @api(environments = {CommandHelperEnvironment.class}) public static class get_pvp extends AbstractFunction { @Override @@ -1347,8 +1684,8 @@ public Integer[] numArgs() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException}; + public Class[] thrown() { + return new Class[]{CREInvalidWorldException.class}; } @Override @@ -1368,20 +1705,20 @@ public String docs() { @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCWorld world = Static.getServer().getWorld(args[0].val()); - if (world == null) { - throw new ConfigRuntimeException("Unknown world: " + args[0].val(), ExceptionType.InvalidWorldException, t); + if(world == null) { + throw new CREInvalidWorldException("Unknown world: " + args[0].val(), t); } return CBoolean.get(world.getPVP()); } } - @api + @api(environments = {CommandHelperEnvironment.class}) public static class set_pvp extends AbstractFunction { @Override @@ -1395,8 +1732,8 @@ public Integer[] numArgs() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException}; + public Class[] thrown() { + return new Class[]{CREInvalidWorldException.class}; } @Override @@ -1411,33 +1748,34 @@ public Boolean runAsync() { @Override public String docs() { - return "void {[world], boolean} Sets if PVP is allowed in the world with the given name, or all worlds if the name is not given."; + return "void {[world], boolean} Sets if PVP is allowed in the world with the given name," + + " or all worlds if the name is not given. (uses gamerule in 1.21.9+)"; } @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - if (args.length == 1) { - boolean pvp = Static.getBoolean(args[0]); - for (MCWorld world : Static.getServer().getWorlds()) { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + if(args.length == 1) { + boolean pvp = ArgumentValidation.getBoolean(args[0], t); + for(MCWorld world : Static.getServer().getWorlds()) { world.setPVP(pvp); } } else { MCWorld world = Static.getServer().getWorld(args[0].val()); - if (world == null) { - throw new ConfigRuntimeException("Unknown world: " + args[0].val(), ExceptionType.InvalidWorldException, t); + if(world == null) { + throw new CREInvalidWorldException("Unknown world: " + args[0].val(), t); } - world.setPVP(Static.getBoolean(args[1])); + world.setPVP(ArgumentValidation.getBoolean(args[1], t)); } return CVoid.VOID; } } - @api + @api(environments = {CommandHelperEnvironment.class}) public static class get_gamerule extends AbstractFunction { @Override @@ -1451,8 +1789,8 @@ public Integer[] numArgs() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException, ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CREInvalidWorldException.class, CREFormatException.class}; } @Override @@ -1467,41 +1805,69 @@ public Boolean runAsync() { @Override public String docs() { - return "mixed {world, [gameRule]} Returns an associative array containing the values of all existing gamerules for the given world." - + " If gameRule is set, the function only returns the value of the specified gamerule, a boolean." - + "gameRule can be " + StringUtils.Join(MCGameRule.values(), ", ", ", or ", " or ") + "."; + return "mixed {world, [gameRule]} Returns an associative array containing the values of all existing" + + " gamerules for the given world. If the gameRule parameter is specified, the function only" + + " returns that one value instead of an array. ---- " + + " The gamerules for MC 1.21.11+ are: " + StringUtils.Join(MCGameRule.getGameRules(), ", ", ", or ", " or ") + "."; } @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCWorld world = Static.getServer().getWorld(args[0].val()); - if (world == null) { - throw new ConfigRuntimeException("Unknown world: " + args[0].val(), ExceptionType.InvalidWorldException, t); + if(world == null) { + throw new CREInvalidWorldException("Unknown world: " + args[0].val(), t); } - if (args.length == 1) { - CArray gameRules = new CArray(t); - for (MCGameRule gameRule : MCGameRule.values()) { - gameRules.set(new CString(gameRule.getGameRule(), t), CBoolean.get(world.getGameRuleValue(gameRule)), t); + if(args.length == 1) { + CArray gameRules = CArray.GetAssociativeArray(t); + for(String ruleName : world.getGameRules()) { + ruleName = ruleName.replace("minecraft:", ""); // Spigot adds this, but not Paper + Object value = world.getGameRuleValue(ruleName); + if(value instanceof Boolean bool) { + gameRules.set(ruleName, CBoolean.get(bool), t); + } else { + gameRules.set(ruleName, new CInt((int) value, t), t); + } } return gameRules; - } else { - MCGameRule gameRule; - try { - gameRule = MCGameRule.valueOf(args[1].val().toUpperCase()); - } catch (IllegalArgumentException exception) { - throw new ConfigRuntimeException("The gamerule \"" + args[1].val() + "\" does not exist.", ExceptionType.FormatException, t); + } + + String ruleName = args[1].val(); + boolean inverted = false; + if(!world.isGameRule(ruleName)) { + String convertedName = MCGameRule.getByLegacyName(ruleName); + if(convertedName == null) { + throw new CREFormatException("The gamerule \"" + ruleName + "\" does not exist.", t); } - return CBoolean.get(world.getGameRuleValue(gameRule)); + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_21_11)) { + MSLog.GetLogger().w(Tags.DEPRECATION, "The Gamerule \"" + ruleName + "\" was changed to \"" + + convertedName + "\".", t); + inverted = MCGameRule.isBoolInvertedFromLegacy(convertedName); + } + ruleName = convertedName; + } + Object value; + try { + value = world.getGameRuleValue(ruleName); + } catch(IllegalArgumentException ex) { + throw new CREFormatException("The gamerule \"" + ruleName + "\" is not available on this server.", t); + } + if(value == null) { + throw new CREFormatException("The gamerule \"" + ruleName + "\" does not exist on this version.", t); + } + if(value instanceof Boolean bool) { + return CBoolean.get(inverted != bool); + } else { + return new CInt((int) value, t); } } } - @api + @api(environments = {CommandHelperEnvironment.class}) public static class set_gamerule extends AbstractFunction { @Override @@ -1515,8 +1881,8 @@ public Integer[] numArgs() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException, ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CREInvalidWorldException.class, CREFormatException.class, CRECastException.class}; } @Override @@ -1531,61 +1897,98 @@ public Boolean runAsync() { @Override public String docs() { - return "void {[world], gameRule, value} Sets the value of the gamerule for the specified world, value is a boolean. If world is not given the value is set for all worlds." - + " gameRule can be " + StringUtils.Join(MCGameRule.values(), ", ", ", or ", " or ") + "."; + return "boolean {[world], gameRule, value} Sets the value of the gamerule for the specified world." + + " If world is not given the value is set for all worlds. Returns true if successful. ---- " + + " The gamerules for MC 1.21.11+ are: " + StringUtils.Join(MCGameRule.getGameRules(), ", ", ", or ", " or ") + "."; } @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - MCGameRule gameRule; - if (args.length == 2) { - try { - gameRule = MCGameRule.valueOf(args[0].val().toUpperCase()); - } catch (IllegalArgumentException exception) { - throw new ConfigRuntimeException("The gamerule \"" + args[0].val() + "\" does not exist.", ExceptionType.FormatException, t); + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + int offset = args.length - 2; + List worlds = Static.getServer().getWorlds(); + MCWorld world = worlds.get(0); + String ruleName = args[offset].val(); + boolean inverted = false; + if(!world.isGameRule(ruleName)) { + String convertedName = MCGameRule.getByLegacyName(ruleName); + if(convertedName == null) { + throw new CREFormatException("The gamerule \"" + ruleName + "\" does not exist.", t); } - boolean value = Static.getBoolean(args[1]); - for (MCWorld world : Static.getServer().getWorlds()) { - world.setGameRuleValue(gameRule, value); + if(Static.getServer().getMinecraftVersion().gte(MCVersion.MC1_21_11)) { + MSLog.GetLogger().w(Tags.DEPRECATION, "The Gamerule \"" + ruleName + "\" was changed to \"" + + convertedName + "\".", t); + inverted = MCGameRule.isBoolInvertedFromLegacy(convertedName); } - } else { - try { - gameRule = MCGameRule.valueOf(args[1].val().toUpperCase()); - } catch (IllegalArgumentException exception) { - throw new ConfigRuntimeException("The gamerule \"" + args[1].val() + "\" does not exist.", ExceptionType.FormatException, t); + ruleName = convertedName; + } + try { + boolean success = false; + Object value = world.getGameRuleValue(ruleName); + if(value instanceof Boolean) { + value = inverted != ArgumentValidation.getBooleanish(args[offset + 1], t); + } else { + value = ArgumentValidation.getInt32(args[offset + 1], t); } - MCWorld world = Static.getServer().getWorld(args[0].val()); - if (world == null) { - throw new ConfigRuntimeException("Unknown world: " + args[0].val(), ExceptionType.InvalidWorldException, t); + if(args.length == 2) { + for(MCWorld w : Static.getServer().getWorlds()) { + success = w.setGameRuleValue(ruleName, value); + } + } else { + world = Static.getServer().getWorld(args[0].val()); + if(world == null) { + throw new CREInvalidWorldException("Unknown world: " + args[0].val(), t); + } + success = world.setGameRuleValue(ruleName, value); } - world.setGameRuleValue(gameRule, Static.getBoolean(args[2])); + return CBoolean.get(success); + } catch(IllegalArgumentException ex) { + throw new CREFormatException(ex.getMessage(), t); } - return CVoid.VOID; } } - @api - public static class location_shift extends AbstractFunction { + @api(environments = {CommandHelperEnvironment.class}) + public static class set_keep_spawn_loaded extends AbstractFunction { @Override public String getName() { - return "location_shift"; + return "set_keep_spawn_loaded"; } @Override public Integer[] numArgs() { - return new Integer[]{3}; + return new Integer[]{2}; } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException, ExceptionType.FormatException, - ExceptionType.RangeException}; + public String docs() { + return "void {world, boolean} Sets whether or not the spawn chunks in the given world should stay loaded." + + " Spawn chunks are never kept loaded starting from MC 1.21.9."; + } + + @Override + public Version since() { + return MSVersion.V3_3_4; + } + + @Override + public Class[] thrown() { + return new Class[]{CREInvalidWorldException.class}; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCWorld world = Static.getServer().getWorld(args[0].val()); + if(world == null) { + throw new CREInvalidWorldException("Unknown world: " + args[0].val(), t); + } + world.setKeepSpawnInMemory(ArgumentValidation.getBooleanObject(args[1], t)); + return CVoid.VOID; } @Override @@ -1597,43 +2000,119 @@ public boolean isRestricted() { public Boolean runAsync() { return false; } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class location_shift extends AbstractFunction { @Override - public String docs() { - return "void {location_from, location_to, distance} Shift from one location to another, by defined distance."; + public String getName() { + return "location_shift"; } @Override - public Version since() { - return CHVersion.V3_3_1; + public Integer[] numArgs() { + return new Integer[]{2, 3, 4}; } @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { + public Class[] thrown() { + return new Class[]{CREInvalidWorldException.class, CREFormatException.class}; + } - MCPlayer p = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); + @Override + public boolean isRestricted() { + return true; + } - MCLocation from = ObjectGenerator.GetGenerator().location(args[0], p != null ? p.getWorld() : null, t); - MCLocation to = ObjectGenerator.GetGenerator().location(args[1], p != null ? p.getWorld() : null, t); + @Override + public Boolean runAsync() { + return false; + } + + @Override + public String docs() { + return "array {origin, target, [distance], [clamp] | origin, direction, [distance]} Returns a location array that" + + " is the specified distance from the origin location along a vector. ---- If a target location is" + + " specified, the vector is obtained from that. The target's world is ignored. If a direction is" + + " specified, that vector is used instead. Distance defaults to 1.0. Direction can be one of " + + StringUtils.Join(MCBlockFace.values(), ", ", ", or ", " or ") + ". If the distance is greater" + + " than the target (when using the first mode), the entity will move past the target if the" + + " distance is greater than the actual distance to the point, unless clamp is set to true."; + } - int distance = Static.getInt32(args[2], t); + @Override + public FunctionSignatures getSignatures() { + return new SignatureBuilder(CArray.TYPE) + .param(CArray.TYPE, "origin", "The original location.") + .param(CArray.TYPE, "target", "The final target location.") + .param(CNumber.TYPE, "distance", "Defaults to 1.0. The distance to move. If clamp is true," + + " the maximum distance to move.", true) + .param(Booleanish.TYPE, "clamp", "Defaults to false. If true, and the target location is closer" + + " than the distance provided, the final destination is returned, instead of a location" + + " beyond the target.", true) - if (distance <= 0) { - throw new ConfigRuntimeException("Distance must be greater than 0.", ExceptionType.RangeException, t); - } + .newSignature(CArray.TYPE) + .param(CArray.TYPE, "origin", "The original location.") + .param(CString.TYPE, "direction", "The direction to move, a BlockFace value.") + .param(CNumber.TYPE, "distance", "Defaults to 1.0. The distance to move.", true) + .build(); - MCLocation shifted_from = from; + } - Velocity velocity = to.toVector().subtract(from.toVector()).normalize(); + @Override + public Version since() { + return MSVersion.V3_3_1; + } - for (int i = 0; i < distance; i++) { - shifted_from = shifted_from.add(velocity); + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCPlayer p = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); + MCLocation loc = ObjectGenerator.GetGenerator().location(args[0], p != null ? p.getWorld() : null, t); + double distance = 1; + boolean clamp = false; + if(args.length >= 3) { + distance = ArgumentValidation.getNumber(args[2], t); + } + if(args.length >= 4) { + clamp = ArgumentValidation.getBooleanish(args[3], t); + } + Vector3D vector; + if(args[1].isInstanceOf(CArray.TYPE)) { + MCLocation to = ObjectGenerator.GetGenerator().location(args[1], loc.getWorld(), t); + if(clamp) { + // Need to check if the shift would go beyond the target, if so, just return the final + // destination. + if(loc.distance(to) < distance) { + return ObjectGenerator.GetGenerator().location(to); + } + } + vector = to.toVector().subtract(loc.toVector()).normalize(); + } else { + try { + MCBlockFace facing = MCBlockFace.valueOf(args[1].val().toUpperCase()); + vector = new Vector3D(facing.getModX(), facing.getModY(), facing.getModZ()).normalize(); + } catch (IllegalArgumentException iae) { + throw new CREFormatException("Expected a location array or direction.", t); + } } - return ObjectGenerator.GetGenerator().location(shifted_from); + loc.add(vector.multiply(distance)); + return ObjectGenerator.GetGenerator().location(loc); + } + + @Override + public ExampleScript[] examples() throws ConfigCompileException { + return new ExampleScript[]{ + new ExampleScript("Using a target location to teleport the player towards it.", + "set_ploc(location_shift(ploc(), pcursor()));", + "{0: 0.0, 1: 64.0, 2: 1.0, 3: world, 4: 0.0, 5: 90.0, x: 0.0, y: 64.0, z: 1.0, world: world, yaw: 0.0, pitch: 90.0}"), + new ExampleScript("Using a direction to get the block 2 meters above the player's targeted block.", + "location_shift(pcursor(), 'UP', 2)", + "{0: 0.0, 1: 66.0, 2: 0.0, 3: world, 4: 0.0, 5: 0.0, x: 0.0, y: 66.0, z: 0.0, world: world, yaw: 0.0, pitch: 0.0}")}; } } - @api + @api(environments = {CommandHelperEnvironment.class}) public static class get_yaw extends AbstractFunction { @Override @@ -1647,8 +2126,8 @@ public Integer[] numArgs() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException, ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CREInvalidWorldException.class, CREFormatException.class}; } @Override @@ -1663,39 +2142,46 @@ public Boolean runAsync() { @Override public String docs() { - return "void {location_from, location_to} Calculate yaw from one location to another."; + return "double {location_from, location_to} Calculate yaw from one location to another on the X-Z plane." + + " The rotation is measured in degrees (0-359.99...) relative to the (x=0,z=1) vector, which" + + " points south. Throws a FormatException if locations have differing worlds." + + " Can return 'NaN' if the x and z coordinates are identical."; } @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { MCPlayer p = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); MCLocation from = ObjectGenerator.GetGenerator().location(args[0], p != null ? p.getWorld() : null, t); MCLocation to = ObjectGenerator.GetGenerator().location(args[1], p != null ? p.getWorld() : null, t); - MCLocation subtract = to.subtract(from); + MCLocation subtract; + try { + subtract = to.subtract(from); + } catch (IllegalArgumentException ex) { + throw new CREFormatException("Locations are in differing worlds.", t); + } double dX = subtract.getX(); - double dY = subtract.getY(); double dZ = subtract.getZ(); - double distanceXZ = java.lang.Math.sqrt(dX * dX + dZ * dZ); - - double yaw = java.lang.Math.toDegrees(java.lang.Math.acos(dX / distanceXZ)); - if (dZ < 0.0) { - yaw += java.lang.Math.abs(180 - yaw) * 2; + double yaw = java.lang.Math.atan(dX / dZ) * 180 / java.lang.Math.PI; // In degrees [-90:90]. + if(dZ < 0) { // Bottom circle quadrant. + yaw += 180; + } else if(dX < 0) { // Top left half quadrant. + yaw += 360; } - return new CDouble(yaw - 90, t); + return new CDouble(360 - yaw, t); } } - @api + @api(environments = {CommandHelperEnvironment.class}) public static class get_pitch extends AbstractFunction { @Override @@ -1709,8 +2195,8 @@ public Integer[] numArgs() { } @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException, ExceptionType.FormatException}; + public Class[] thrown() { + return new Class[]{CREInvalidWorldException.class, CREFormatException.class}; } @Override @@ -1725,16 +2211,17 @@ public Boolean runAsync() { @Override public String docs() { - return "void {location_from, location_to} Calculate pitch from one location to another."; + return "double {location_from, location_to} Calculate pitch from one location to another." + + " This will be from -90.0 to 90.0, which is up and down respectively."; } @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; } @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { MCPlayer p = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); @@ -1747,20 +2234,136 @@ public Construct exec(Target t, Environment env, Construct... args) throws Confi double dZ = subtract.getZ(); double distanceXZ = java.lang.Math.sqrt(dX * dX + dZ * dZ); - double distanceY = java.lang.Math.sqrt(distanceXZ * distanceXZ + dY * dY); - double pitch = java.lang.Math.toDegrees(java.lang.Math.acos(dY / distanceY)) - 90; + double pitch = java.lang.Math.atan(dY / distanceXZ) * -180 / java.lang.Math.PI; return new CDouble(pitch, t); } } - @api + @api(environments = {CommandHelperEnvironment.class}) + public static class get_vector extends AbstractFunction { + + @Override + public String getName() { + return "get_vector"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1, 2}; + } + + @Override + public Class[] thrown() { + return new Class[]{CRERangeException.class, CREFormatException.class}; + } + + @Override + public boolean isRestricted() { + return false; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public String docs() { + return "array {locationArray, [magnitude]} Returns a vector from the yaw and pitch in a location array." + + " Only the yaw and pitch keys are required in the array." + + " The second parameter, that defines the magnitude of the vector, defaults to 1.0."; + } + + @Override + public Version since() { + return MSVersion.V3_3_2; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + CArray loc = ArgumentValidation.getArray(args[0], t); + double yaw; + double pitch; + if(loc.isAssociative()) { + yaw = ArgumentValidation.getDouble(loc.get("yaw", t), t); + pitch = ArgumentValidation.getDouble(loc.get("pitch", t), t); + } else { + yaw = ArgumentValidation.getDouble(loc.get(4, t), t); + pitch = ArgumentValidation.getDouble(loc.get(5, t), t); + } + yaw = java.lang.Math.toRadians(yaw); + pitch = java.lang.Math.toRadians(pitch); + double cosPitch = java.lang.Math.cos(pitch); + double x = java.lang.Math.sin(yaw) * -cosPitch; + double y = -java.lang.Math.sin(pitch); + double z = java.lang.Math.cos(yaw) * cosPitch; + Vector3D v = new Vector3D(x, y, z); + if(args.length == 2) { + v = v.multiply(ArgumentValidation.getDouble(args[1], t)); + } + return ObjectGenerator.GetGenerator().vector(v); + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class distance extends AbstractFunction { + + @Override + public String getName() { + return "distance"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{2}; + } + + @Override + public Class[] thrown() { + return new Class[]{CRERangeException.class, CREFormatException.class, CREInvalidWorldException.class, + CREIllegalArgumentException.class}; + } + + @Override + public boolean isRestricted() { + return false; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public String docs() { + return "double {locationA, locationB} Returns the distance between two locations."; + } + + @Override + public Version since() { + return MSVersion.V3_3_2; + } + + @Override + public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException { + MCLocation loc1 = ObjectGenerator.GetGenerator().location(args[0], null, t); + MCLocation loc2 = ObjectGenerator.GetGenerator().location(args[1], null, t); + try { + return new CDouble(loc1.distance(loc2), t); + } catch (IllegalArgumentException iae) { + throw new CREIllegalArgumentException("Cannot measure distance between two different worlds.", t); + } + } + } + + @api(environments = {CommandHelperEnvironment.class}) public static class save_world extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException}; + public Class[] thrown() { + return new Class[]{CREInvalidWorldException.class}; } @Override @@ -1774,7 +2377,7 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { MCWorld world = Static.getWorld(args[0], t); world.save(); return CVoid.VOID; @@ -1797,8 +2400,300 @@ public String docs() { @Override public Version since() { - return CHVersion.V3_3_1; + return MSVersion.V3_3_1; + } + + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class get_temperature extends AbstractFunction { + + @Override + public Class[] thrown() { + return new Class[]{CREFormatException.class}; + } + + @Override + public boolean isRestricted() { + return false; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target target, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCPlayer player = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); + MCWorld world = null; + if(player != null) { + world = player.getWorld(); + } + MCLocation loc = ObjectGenerator.GetGenerator().location(args[0], world, target); + return new CDouble(loc.getBlock().getTemperature(), target); + } + + @Override + public String getName() { + return "get_temperature"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1}; + } + + @Override + public String docs() { + return "double {locationArray} Returns the current temperature at the location given."; + } + + @Override + public Version since() { + return MSVersion.V3_3_1; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class get_world_border extends AbstractFunction { + + @Override + public Class[] thrown() { + return new Class[]{CREInvalidWorldException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCWorld w = Static.getServer().getWorld(args[0].val()); + if(w == null) { + throw new CREInvalidWorldException("Unknown world: " + args[0], t); + } + MCWorldBorder wb = w.getWorldBorder(); + CArray ret = CArray.GetAssociativeArray(t); + ret.set("width", new CDouble(wb.getSize(), t), t); + ret.set("center", ObjectGenerator.GetGenerator().location(wb.getCenter(), false), t); + ret.set("damagebuffer", new CDouble(wb.getDamageBuffer(), t), t); + ret.set("damageamount", new CDouble(wb.getDamageAmount(), t), t); + ret.set("warningtime", new CInt(wb.getWarningTime(), t), t); + ret.set("warningdistance", new CInt(wb.getWarningDistance(), t), t); + return ret; + } + + @Override + public String getName() { + return "get_world_border"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1}; + } + + @Override + public String docs() { + return "array {world_name} Returns an associative array of all information about the world's border." + + " The keys are width, center, damagebuffer, damageamount, warningtime, warningdistance."; + } + + @Override + public Version since() { + return MSVersion.V3_3_2; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class set_world_border extends AbstractFunction { + + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class, CREFormatException.class, CREInvalidWorldException.class, + CRERangeException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCWorld w = Static.getServer().getWorld(args[0].val()); + if(w == null) { + throw new CREInvalidWorldException("Unknown world: " + args[0], t); + } + MCWorldBorder wb = w.getWorldBorder(); + Mixed c = args[1]; + if(!(c.isInstanceOf(CArray.TYPE))) { + throw new CREFormatException("Expected array but given \"" + args[1].val() + "\"", t); + } + CArray params = (CArray) c; + if(params.containsKey("width")) { + if(params.containsKey("seconds")) { + wb.setSize(ArgumentValidation.getDouble(params.get("width", t), t), + ArgumentValidation.getInt32(params.get("seconds", t), t)); + } else { + wb.setSize(ArgumentValidation.getDouble(params.get("width", t), t)); + } + } + if(params.containsKey("center")) { + wb.setCenter(ObjectGenerator.GetGenerator().location(params.get("center", t), w, t)); + } + if(params.containsKey("damagebuffer")) { + wb.setDamageBuffer(ArgumentValidation.getDouble(params.get("damagebuffer", t), t)); + } + if(params.containsKey("damageamount")) { + wb.setDamageAmount(ArgumentValidation.getDouble(params.get("damageamount", t), t)); + } + if(params.containsKey("warningtime")) { + wb.setWarningTime(ArgumentValidation.getInt32(params.get("warningtime", t), t)); + } + if(params.containsKey("warningdistance")) { + wb.setWarningDistance(ArgumentValidation.getInt32(params.get("warningdistance", t), t)); + } + return CVoid.VOID; + } + + @Override + public String getName() { + return "set_world_border"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{2}; + } + + @Override + public String docs() { + return "void {world_name, paramArray} Updates the world's border with the given values. In addition to the" + + " keys returned by get_world_border(), you can also specify the \"seconds\". This is time in" + + " which the border will move from the previous width to the new \"width\"."; + } + + @Override + public Version since() { + return MSVersion.V3_3_2; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class get_world_autosave extends AbstractFunction { + + @Override + public Class[] thrown() { + return new Class[]{CREInvalidWorldException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + MCWorld w = Static.getServer().getWorld(args[0].val()); + if(w == null) { + throw new CREInvalidWorldException("Unknown world: " + args[0].val(), t); + } + return CBoolean.get(w.isAutoSave()); + } + + @Override + public String getName() { + return "get_world_autosave"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1}; + } + + @Override + public String docs() { + return "boolean {world_name} Fetch the world's current auto-save state."; + } + + @Override + public Version since() { + return MSVersion.V3_3_5; + } + } + + @api(environments = {CommandHelperEnvironment.class}) + public static class set_world_autosave extends AbstractFunction { + + @Override + public Class[] thrown() { + return new Class[]{}; } + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return false; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + if(args.length == 1) { + boolean autosave = ArgumentValidation.getBooleanish(args[0], t); + for(MCWorld world : Static.getServer().getWorlds()) { + world.setAutoSave(autosave); + } + } else { + MCWorld world = Static.getServer().getWorld(args[0].val()); + if(world == null) { + throw new CREInvalidWorldException("Unknown world: " + args[0].val(), t); + } + world.setAutoSave(ArgumentValidation.getBooleanish(args[1], t)); + } + return CVoid.VOID; + } + + @Override + public String getName() { + return "set_world_autosave"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1, 2}; + } + + @Override + public String docs() { + return "void {[world_name], newState} Updates the world's auto-save state. If no world is specified," + + " set the option for all worlds."; + } + + @Override + public Version since() { + return MSVersion.V3_3_5; + } } } diff --git a/src/main/java/com/laytonsmith/core/functions/WorldEdit.java b/src/main/java/com/laytonsmith/core/functions/WorldEdit.java deleted file mode 100644 index 6aaa331b49..0000000000 --- a/src/main/java/com/laytonsmith/core/functions/WorldEdit.java +++ /dev/null @@ -1,2753 +0,0 @@ -package com.laytonsmith.core.functions; - -import com.laytonsmith.PureUtilities.Version; -import com.laytonsmith.abstraction.MCCommandSender; -import com.laytonsmith.abstraction.MCLocation; -import com.laytonsmith.abstraction.MCPlayer; -import com.laytonsmith.abstraction.MCWorld; -import com.laytonsmith.abstraction.bukkit.BukkitMCCommandSender; -import com.laytonsmith.abstraction.bukkit.BukkitMCLocation; -import com.laytonsmith.abstraction.bukkit.BukkitMCPlayer; -import com.laytonsmith.abstraction.bukkit.BukkitMCWorld; -import com.laytonsmith.annotations.api; -import com.laytonsmith.core.CHVersion; -import com.laytonsmith.core.ObjectGenerator; -import com.laytonsmith.core.Static; -import com.laytonsmith.core.constructs.CArray; -import com.laytonsmith.core.constructs.CBoolean; -import com.laytonsmith.core.constructs.CDouble; -import com.laytonsmith.core.constructs.CInt; -import com.laytonsmith.core.constructs.CNull; -import com.laytonsmith.core.constructs.CString; -import com.laytonsmith.core.constructs.CVoid; -import com.laytonsmith.core.constructs.Construct; -import com.laytonsmith.core.constructs.Target; -import com.laytonsmith.core.environments.CommandHelperEnvironment; -import com.laytonsmith.core.environments.Environment; -import com.laytonsmith.core.exceptions.CancelCommandException; -import com.laytonsmith.core.exceptions.ConfigRuntimeException; -import com.laytonsmith.core.functions.Exceptions.ExceptionType; -import com.sk89q.worldedit.BlockVector; -import com.sk89q.worldedit.BlockVector2D; -import com.sk89q.worldedit.EditSession; -import com.sk89q.worldedit.EmptyClipboardException; -import com.sk89q.worldedit.LocalSession; -import com.sk89q.worldedit.LocalWorld; -import com.sk89q.worldedit.MaxChangedBlocksException; -import com.sk89q.worldedit.Vector; -import com.sk89q.worldedit.WorldEditException; -import com.sk89q.worldedit.bukkit.BukkitUtil; -import com.sk89q.worldedit.data.DataException; -import com.sk89q.worldedit.regions.CuboidRegion; -import com.sk89q.worldedit.regions.CuboidRegionSelector; -import com.sk89q.worldedit.regions.RegionSelector; -import com.sk89q.worldedit.schematic.SchematicFormat; -import com.sk89q.worldguard.protection.ApplicableRegionSet; -import com.sk89q.worldguard.protection.GlobalRegionManager; -import com.sk89q.worldguard.protection.databases.ProtectionDatabaseException; -import com.sk89q.worldguard.protection.databases.RegionDBUtil; -import com.sk89q.worldguard.protection.flags.BooleanFlag; -import com.sk89q.worldguard.protection.flags.DefaultFlag; -import com.sk89q.worldguard.protection.flags.DoubleFlag; -import com.sk89q.worldguard.protection.flags.EnumFlag; -import com.sk89q.worldguard.protection.flags.Flag; -import com.sk89q.worldguard.protection.flags.IntegerFlag; -import com.sk89q.worldguard.protection.flags.InvalidFlagFormat; -import com.sk89q.worldguard.protection.flags.LocationFlag; -import com.sk89q.worldguard.protection.flags.RegionGroup; -import com.sk89q.worldguard.protection.flags.RegionGroupFlag; -import com.sk89q.worldguard.protection.flags.SetFlag; -import com.sk89q.worldguard.protection.flags.StateFlag; -import com.sk89q.worldguard.protection.flags.StringFlag; -import com.sk89q.worldguard.protection.managers.RegionManager; -import com.sk89q.worldguard.protection.regions.GlobalProtectedRegion; -import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; -import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion; -import com.sk89q.worldguard.protection.regions.ProtectedRegion; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.EntityType; - -/** - * - */ -public class WorldEdit { - - public static String docs() { - return "Provides various methods for programmatically hooking into WorldEdit and WorldGuard"; - } - - @api(environments=CommandHelperEnvironment.class) - public static class sk_pos1 extends SKFunction { - - @Override - public String getName() { - return "sk_pos1"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{0, 1, 2}; - } - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException, ExceptionType.CastException}; - } - - @Override - public String docs() { - return "mixed {[player], locationArray | [player]} Sets the player's point 1, or returns it if the array to set isn't specified. If" - + " the location is returned, it is returned as a 4 index array:(x, y, z, world)"; - } - - @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { - MCPlayer m = null; - MCLocation l = null; - boolean setter = false; - Static.checkPlugin("WorldEdit", t); - - if (env.getEnv(CommandHelperEnvironment.class).GetCommandSender() instanceof MCPlayer) { - m = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); - } - if (args.length == 2) { - m = Static.GetPlayer(args[0].val(), t); - l = ObjectGenerator.GetGenerator().location(args[1], m.getWorld(), t); - setter = true; - } else if (args.length == 1) { - if (args[0] instanceof CArray) { - l = ObjectGenerator.GetGenerator().location(args[0], ( m == null ? null : m.getWorld() ), t); - setter = true; - } else { - m = Static.GetPlayer(args[0].val(), t); - } - } - - if (m == null) { - throw new ConfigRuntimeException(this.getName() + " needs a player", ExceptionType.PlayerOfflineException, t); - } - - RegionSelector sel = Static.getWorldEditPlugin(t).getSession(( (BukkitMCPlayer) m )._Player()).getRegionSelector(BukkitUtil.getLocalWorld(( (BukkitMCWorld) m.getWorld() ).__World())); - if (!( sel instanceof CuboidRegionSelector )) { - throw new ConfigRuntimeException("Only cuboid regions are supported with " + this.getName(), ExceptionType.PluginInternalException, t); - } - if (setter) { - sel.selectPrimary(BukkitUtil.toVector(( (BukkitMCLocation) l )._Location())); - return CVoid.VOID; - } else { - Vector pt = ( (CuboidRegion) sel.getIncompleteRegion() ).getPos1(); - if (pt == null) { - throw new ConfigRuntimeException("Point in " + this.getName() + "undefined", ExceptionType.PluginInternalException, t); - } - return new CArray(t, - new CInt(pt.getBlockX(), t), - new CInt(pt.getBlockY(), t), - new CInt(pt.getBlockZ(), t), - new CString(m.getWorld().getName(), t)); - } - } - } - - @api(environments=CommandHelperEnvironment.class) - public static class sk_pos2 extends SKFunction { - - @Override - public String getName() { - return "sk_pos2"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{0, 1, 2}; - } - - @Override - public String docs() { - return "mixed {[player], array | [player]} Sets the player's point 2, or returns it if the array to set isn't specified"; - } - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException, ExceptionType.CastException}; - } - - @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { - MCPlayer m = null; - MCLocation l = null; - boolean setter = false; - Static.checkPlugin("WorldEdit", t); - - if (env.getEnv(CommandHelperEnvironment.class).GetCommandSender() instanceof MCPlayer) { - m = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); - } - if (args.length == 2) { - m = Static.GetPlayer(args[0].val(), t); - l = ObjectGenerator.GetGenerator().location(args[1], m.getWorld(), t); - setter = true; - } else if (args.length == 1) { - if (args[0] instanceof CArray) { - l = ObjectGenerator.GetGenerator().location(args[0], ( m == null ? null : m.getWorld() ), t); - setter = true; - } else { - m = Static.GetPlayer(args[0].val(), t); - } - } - - if (m == null) { - throw new ConfigRuntimeException(this.getName() + " needs a player", ExceptionType.PlayerOfflineException, t); - } - - RegionSelector sel = Static.getWorldEditPlugin(t).getSession(( (BukkitMCPlayer) m )._Player()).getRegionSelector(BukkitUtil.getLocalWorld(( (BukkitMCWorld) m.getWorld() ).__World())); - if (!( sel instanceof CuboidRegionSelector )) { - throw new ConfigRuntimeException("Only cuboid regions are supported with " + this.getName(), ExceptionType.PluginInternalException, t); - } - - if (setter) { - sel.selectSecondary(BukkitUtil.toVector(( (BukkitMCLocation) l )._Location())); - return CVoid.VOID; - } else { - Vector pt = ( (CuboidRegion) sel.getIncompleteRegion() ).getPos2(); - if (pt == null) { - throw new ConfigRuntimeException("Point in " + this.getName() + "undefined", ExceptionType.PluginInternalException, t); - } - return new CArray(t, - new CInt(pt.getBlockX(), t), - new CInt(pt.getBlockY(), t), - new CInt(pt.getBlockZ(), t), - new CString(m.getWorld().getName(), t)); - } - } - } - -// public static class sk_points extends SKFunction { -// -// public String getName() { -// return "sk_points"; -// } -// -// public Integer[] numArgs() { -// return new Integer[]{0, 1, 2}; -// } -// -// public String docs() { -// return "mixed {[player], arrayOfArrays | [player]} Sets a series of points, or returns the poly selection for this player, if one is specified." -// + " The array should be an array of arrays, and the arrays should be array(x, y, z)"; -// } -// -// public ExceptionType[] thrown() { -// return new ExceptionType[]{ExceptionType.PlayerOfflineException, ExceptionType.CastException}; -// } -// -// public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { -// Static.checkPlugin("WorldEdit", t); -// return CVoid.VOID; -// } -// } - @api - public static class sk_region_info extends SKFunction { - - @Override - public String getName() { - return "sk_region_info"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{2, 3}; - } - - @Override - public String docs() { - return "array {region, world, [value]} Given a region name, returns an array of information about that region." - + " ---- If value is set, it should be an integer of one of the following indexes, and only that information for that index" - + " will be returned. Otherwise if value is not specified (or is -1), it returns an array of" - + " information with the following pieces of information in the specified index:
    " - + "
  • 0 - An array of points that define this region
  • " - + "
  • 1 - An array of owners of this region
  • " - + "
  • 2 - An array of members of this region
  • " - + "
  • 3 - An array of arrays of this region's flags, where each array is: array(flag_name, value)
  • " - + "
  • 4 - This region's priority
  • " - + "
  • 5 - The volume of this region (in meters cubed)
  • " - + "
" - + "If the region cannot be found, a PluginInternalException is thrown."; - } - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PluginInternalException, - ExceptionType.CastException, ExceptionType.RangeException}; - } - - @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { - try { - String regionName = args[0].val(); - String worldName = args[1].val(); - int index = -1; - - if (args.length == 3) { - index = Static.getInt32(args[2], t); - } - - int maxIndex = 5; - if (index < -1 || index > maxIndex) { - throw new ConfigRuntimeException(this.getName() + " expects the index to be between -1 and " + maxIndex, - ExceptionType.RangeException, t); - } - - World world = Bukkit.getServer().getWorld(worldName); - if (world == null) { - throw new ConfigRuntimeException("Unknown world specified", ExceptionType.PluginInternalException, t); - } - RegionManager mgr = Static.getWorldGuardPlugin(t).getGlobalRegionManager().get(world); - ProtectedRegion region = mgr.getRegion(regionName); - if (region == null) { - throw new ConfigRuntimeException("Region could not be found!", ExceptionType.PluginInternalException, t); - } - - CArray ret = new CArray(t); - - //Fill these data structures in with the information we need - if (index == 0 || index == -1) { - List points = new ArrayList(); - - boolean first = true; - if (region instanceof ProtectedPolygonalRegion) { - for (BlockVector2D pt : ((ProtectedPolygonalRegion) region).getPoints()) { - points.add(new Location(world, pt.getX(), first ? region.getMaximumPoint().getY() - : region.getMinimumPoint().getY(), pt.getZ())); - first = false; - } - } else { - points.add(com.sk89q.worldguard.bukkit.BukkitUtil.toLocation(world, region.getMaximumPoint())); - points.add(com.sk89q.worldguard.bukkit.BukkitUtil.toLocation(world, region.getMinimumPoint())); - } - - CArray pointSet = new CArray(t); - for (Location l : points) { - CArray point = new CArray(t); - point.push(new CInt(l.getBlockX(), t)); - point.push(new CInt(l.getBlockY(), t)); - point.push(new CInt(l.getBlockZ(), t)); - point.push(new CString(l.getWorld().getName(), t)); - pointSet.push(point); - } - - ret.push(pointSet); - } - - if (index == 1 || index == -1) { - List ownersPlayers = new ArrayList(); - List ownersGroups = new ArrayList(); - ownersPlayers.addAll(region.getOwners().getPlayers()); - ownersGroups.addAll(region.getOwners().getGroups()); - - CArray ownerSet = new CArray(t); - for (String owner : ownersPlayers) { - ownerSet.push(new CString(owner, t)); - } - for (String owner : ownersGroups) { - ownerSet.push(new CString("*" + owner, t)); - } - - ret.push(ownerSet); - } - - if (index == 2 || index == -1) { - List membersPlayers = new ArrayList(); - List membersGroups = new ArrayList(); - membersPlayers.addAll(region.getMembers().getPlayers()); - membersGroups.addAll(region.getMembers().getGroups()); - - CArray memberSet = new CArray(t); - for (String member : membersPlayers) { - memberSet.push(new CString(member, t)); - } - for (String member : membersGroups) { - memberSet.push(new CString("*" + member, t)); - } - - ret.push(memberSet); - } - - if (index == 3 || index == -1) { - Map flags = new HashMap(); - for (Map.Entry, Object> ent : region.getFlags().entrySet()) { - flags.put(ent.getKey().getName(), String.valueOf(ent.getValue())); - } - - CArray flagSet = new CArray(t); - for (Map.Entry flag : flags.entrySet()) { - CArray fl = new CArray(t, - new CString(flag.getKey(), t), - new CString(flag.getValue(), t)); - flagSet.push(fl); - } - - ret.push(flagSet); - } - - if (index == 4 || index == -1) { - int priority; - priority = region.getPriority(); - ret.push(new CInt(priority, t)); - } - - if (index == 5 || index == -1) { - float volume; - volume = region.volume(); - ret.push(new CDouble(volume, t)); - } - - if (ret.size() == 1) { - return ret.get(0, t); - } - return ret; - - } catch (NoClassDefFoundError e) { - throw new ConfigRuntimeException("It does not appear as though the WorldEdit or WorldGuard plugin is loaded properly. Execution of " + this.getName() + " cannot continue.", ExceptionType.InvalidPluginException, t, e); - } - } - } - - @api - public static class sk_region_overlaps extends SKFunction { - - @Override - public String getName() { - return "sk_region_overlaps"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{3}; - } - - @Override - public String docs() { - return "boolean {world, region1, array(region2, [regionN...])} Returns true or false whether or not the specified regions overlap."; - } - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PluginInternalException}; - } - - @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { - String region1 = args[1].val(); - List checkRegions = new ArrayList(); - Static.checkPlugin("WorldGuard", t); - World world = Bukkit.getServer().getWorld(args[0].val()); - if (world == null) { - throw new ConfigRuntimeException("Unknown world specified", ExceptionType.PluginInternalException, t); - } - RegionManager mgr = Static.getWorldGuardPlugin(t).getGlobalRegionManager().get(world); - if (args[2] instanceof CArray) { - CArray arg = (CArray) args[2]; - for (int i = 0; i < arg.size(); i++) { - ProtectedRegion region = mgr.getRegion(arg.get(i, t).val()); - if (region == null) { - throw new ConfigRuntimeException("Region " + arg.get(i, t).val() + " could not be found!", ExceptionType.PluginInternalException, t); - } - checkRegions.add(region); - } - } else { - ProtectedRegion region = mgr.getRegion(args[2].val()); - if (region == null) { - throw new ConfigRuntimeException("Region " + args[2] + " could not be found!", ExceptionType.PluginInternalException, t); - } - checkRegions.add(region); - } - - ProtectedRegion region = mgr.getRegion(region1); - if (region == null) { - throw new ConfigRuntimeException("Region could not be found!", ExceptionType.PluginInternalException, t); - } - - try { - if (!region.getIntersectingRegions(checkRegions).isEmpty()) { - return CBoolean.TRUE; - } - } catch (Exception e) { - } - return CBoolean.FALSE; - } - } - - @api - public static class sk_region_intersect extends SKFunction { - - @Override - public String getName() { - return "sk_region_intersect"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{2, 3}; - } - - @Override - public String docs() { - return "array {world, region1, [array(region2, [regionN...])]} Returns an array of regions names which intersect with defined region." - + " You can pass an array of regions to verify or omit this parameter and all regions in selected world will be checked."; - } - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PluginInternalException}; - } - - @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { - String region1 = args[1].val(); - List checkRegions = new ArrayList(); - List getRegions; - CArray listRegions = new CArray(t); - - Static.checkPlugin("WorldGuard", t); - World world = Bukkit.getServer().getWorld(args[0].val()); - if (world == null) { - throw new ConfigRuntimeException("Unknown world specified", ExceptionType.PluginInternalException, t); - } - - RegionManager mgr = Static.getWorldGuardPlugin(t).getGlobalRegionManager().get(world); - - if (args.length == 2) { - checkRegions.addAll(mgr.getRegions().values()); - } else { - if (args[2] instanceof CArray) { - CArray arg = (CArray) args[2]; - for (int i = 0; i < arg.size(); i++) { - ProtectedRegion region = mgr.getRegion(arg.get(i, t).val()); - if (region == null) { - throw new ConfigRuntimeException(String.format("Region %s could not be found!", arg.get(i, t).val()), ExceptionType.PluginInternalException, t); - } - checkRegions.add(region); - } - } else { - ProtectedRegion region = mgr.getRegion(args[2].val()); - if (region == null) { - throw new ConfigRuntimeException(String.format("Region %s could not be found!", args[2]), ExceptionType.PluginInternalException, t); - } - checkRegions.add(region); - } - } - - ProtectedRegion region = mgr.getRegion(region1); - if (region == null) { - throw new ConfigRuntimeException(String.format("Region %s could not be found!", region1), ExceptionType.PluginInternalException, t); - } - - try { - getRegions = region.getIntersectingRegions(checkRegions); - - if (!getRegions.isEmpty()) { - for (ProtectedRegion r : getRegions) { - if (args.length != 2 || !r.getId().equals(region.getId())) { - listRegions.push(new CString(r.getId(), t)); - } - } - - if (listRegions.isEmpty()) { - return new CArray(t); - } - - return listRegions; - } - } catch (Exception e) { - } - return new CArray(t); - } - } - - @api - public static class sk_all_regions extends SKFunction { - - @Override - public String getName() { - return "sk_all_regions"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{0, 1}; - } - - @Override - public String docs() { - return "array {[world]} Returns all the regions in all worlds, or just the one world, if specified."; - } - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; - } - - @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { - Static.checkPlugin("WorldGuard", t); - List checkWorlds = null; - CArray arr = new CArray(t); - if (args.length == 1) { - World world = Bukkit.getServer().getWorld(args[0].val()); - if (world != null) { - checkWorlds = Arrays.asList(world); - } - } - if (checkWorlds == null) { - checkWorlds = Bukkit.getServer().getWorlds(); - } - GlobalRegionManager mgr = Static.getWorldGuardPlugin(t).getGlobalRegionManager(); - for (World world : checkWorlds) { - for (String region : mgr.get(world).getRegions().keySet()) { - arr.push(new CString(region, t)); - } - } - return arr; - - } - } - - @api(environments=CommandHelperEnvironment.class) - public static class sk_current_regions extends SKFunction { - - @Override - public String getName() { - return "sk_current_regions"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{0, 1}; - } - - @Override - public String docs() { - return "mixed {[player]} Returns the list regions that player is in. If no player specified, then the current player is used." - + " If region is found, an array of region names are returned, else an empty array is returned"; - } - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PlayerOfflineException, ExceptionType.PluginInternalException}; - } - - @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { - Static.checkPlugin("WorldGuard", t); - World world; - - MCPlayer m = null; - - if (env.getEnv(CommandHelperEnvironment.class).GetCommandSender() instanceof MCPlayer) { - m = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); - } - if (args.length == 1) { - m = Static.GetPlayer(args[0].val(), t); - } - - if (m == null) { - throw new ConfigRuntimeException(this.getName() + " needs a player", ExceptionType.PlayerOfflineException, t); - } - - world = Bukkit.getServer().getWorld(m.getWorld().getName()); - - RegionManager mgr = Static.getWorldGuardPlugin(t).getGlobalRegionManager().get(world); - Vector pt = new Vector(m.getLocation().getBlockX(), m.getLocation().getBlockY(), m.getLocation().getBlockZ()); - ApplicableRegionSet set = mgr.getApplicableRegions(pt); - - CArray regions = new CArray(t); - - List sortedRegions = new ArrayList(); - - for (ProtectedRegion r : set) { - boolean placed = false; - for (int i = 0; i < sortedRegions.size(); i++) { - if (sortedRegions.get(i).volume() < r.volume()) { - sortedRegions.add(i, r); - placed = true; - break; - } - } - if (!placed) { - sortedRegions.add(r); - } - } - - for (ProtectedRegion region : sortedRegions) { - regions.push(new CString(region.getId(), t)); - } - - if (regions.size() > 0) { - return regions; - } - - return new CArray(t); - } - } - - @api(environments=CommandHelperEnvironment.class) - public static class sk_regions_at extends SKFunction { - - @Override - public String getName() { - return "sk_regions_at"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{1}; - } - - @Override - public String docs() { - return "mixed {Locationarray} Returns a list of regions at the specified location. " - + "If regions are found, an array of region names are returned, otherwise, an empty array is returned."; - } - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.CastException, ExceptionType.PluginInternalException, ExceptionType.InsufficientArgumentsException}; - } - - @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { - Static.checkPlugin("WorldGuard", t); - World world; - - if (!( args[0] instanceof CArray )) { - throw new ConfigRuntimeException(this.getName() + " needs a locationarray", ExceptionType.CastException, t); - } - - MCWorld w = null; - MCCommandSender c = env.getEnv(CommandHelperEnvironment.class).GetCommandSender(); - if (c instanceof MCPlayer) { - w = ((MCPlayer)c).getWorld(); - } - - MCLocation loc = ObjectGenerator.GetGenerator().location(args[0], w, t); - - if (loc.getWorld() == null) { - throw new ConfigRuntimeException(this.getName() + " needs a world", ExceptionType.InsufficientArgumentsException, t); - } - - world = Bukkit.getServer().getWorld(loc.getWorld().getName()); - - RegionManager mgr = Static.getWorldGuardPlugin(t).getGlobalRegionManager().get(world); - Vector pt = new Vector(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()); - ApplicableRegionSet set = mgr.getApplicableRegions(pt); - - CArray regions = new CArray(t); - - List sortedRegions = new ArrayList(); - - for (ProtectedRegion r : set) { - boolean placed = false; - for (int i = 0; i < sortedRegions.size(); i++) { - if (sortedRegions.get(i).volume() < r.volume()) { - sortedRegions.add(i, r); - placed = true; - break; - } - } - if (!placed) { - sortedRegions.add(r); - } - } - - for (ProtectedRegion region : sortedRegions) { - regions.push(new CString(region.getId(), t)); - } - - if (regions.size() > 0) { - return regions; - } - - return new CArray(t); - } - } - - @api - public static class sk_region_volume extends SKFunction { - - @Override - public String getName() { - return "sk_region_volume"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{2}; - } - - @Override - public String docs() { - return "int {region, world} Returns the volume of the given region in the given world."; - } - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PluginInternalException}; - } - - @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { - Static.checkPlugin("WorldGuard", t); - World world; - - world = Bukkit.getServer().getWorld(args[1].val()); - - RegionManager mgr = Static.getWorldGuardPlugin(t).getGlobalRegionManager().get(world); - - ProtectedRegion region = mgr.getRegion(args[0].val()); - - if (region == null) { - throw new ConfigRuntimeException(String.format("The region (%s) does not exist in world (%s).", args[0].val(), args[1].val()), ExceptionType.PluginInternalException, t); - } - - return new CInt(region.volume(), t); - } - } - - @api - public static class sk_region_create extends SKFunction { - - @Override - public String getName() { - return "sk_region_create"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{1, 2, 3}; - } - - @Override - public String docs() { - return "void {[world], name, array(locationArrayPos1, locationArrayPos2, [[locationArrayPosN]...])|[world], '__global__'}" - + " Create region of the given name in the given world. You can omit list of points if you want to create a __global__ region."; - } - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException, ExceptionType.PluginInternalException}; - } - - @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { - Static.checkPlugin("WorldGuard", t); - - MCWorld w = null; - String region; - CArray points; - List verticies = new ArrayList<>(); - - if ((args.length == 3) || (args.length == 2 && "__global__".equalsIgnoreCase(args[1].val()))) { - - w = Static.getServer().getWorld(args[0].val()); - region = args[1].val(); - - if (w == null) { - throw new ConfigRuntimeException("The specified world \"" + args[0].val() + "\" is not a valid world.", ExceptionType.InvalidWorldException, t); - } - } else { - region = args[0].val(); - } - - if (!"__global__".equalsIgnoreCase(region)) { - - if ((args.length == 1) || !(args[args.length - 1] instanceof CArray)) { - throw new ConfigRuntimeException("Pass an array of LocationArrays for a new region.", ExceptionType.PluginInternalException, t); - } - - points = (CArray) args[args.length - 1]; - - if (points.isEmpty()) { - throw new ConfigRuntimeException("Expecting an array of LocationArrays but found none.", ExceptionType.PluginInternalException, t); - } - - for (int i = 0; i < points.size(); i++) { - - if (!(points.get(i, t) instanceof CArray)) { - throw new ConfigRuntimeException("LocationArrays must be arrays.", ExceptionType.PluginInternalException, t); - } - - CArray point = (CArray) points.get(i, t); - - if (point.size() >= 3) { - - double x = 0; - double y = 0; - double z = 0; - - if (!point.inAssociativeMode()) { - x = Static.getNumber(point.get(0, t), t); - y = Static.getNumber(point.get(1, t), t); - z = Static.getNumber(point.get(2, t), t); - } - - if (point.containsKey("x")) { - x = Static.getNumber(point.get("x", t), t); - } - if (point.containsKey("y")) { - y = Static.getNumber(point.get("y", t), t); - } - if (point.containsKey("z")) { - z = Static.getNumber(point.get("z", t), t); - } - - verticies.add(new BlockVector(x, y, z)); - } - } - - if (verticies.isEmpty()) { - throw new ConfigRuntimeException("Expecting an array of LocationArrays but found no valid Location arrays.", ExceptionType.PluginInternalException, t); - } - - if (w == null) { - for (int i = 0; i < points.size(); i++) { - - CArray point = (CArray) points.get(i, t); - - if (point.size() >= 4) { - - MCWorld world = null; - - if (!point.inAssociativeMode()) { - world = Static.getServer().getWorld(point.get(3, t).val()); - } - - if (point.containsKey("world")) { - world = Static.getServer().getWorld(point.get("world", t).val()); - } - - if (world != null) { - if (w == null) { - w = world; - } else { - if (!w.equals(world)) { - throw new ConfigRuntimeException(String.format("Conflicting worlds in LocationArrays." - + " (%s) & (%s)",w.getName(),world.getName()), ExceptionType.InvalidWorldException, t); - } - } - } - } - } - } - } - - if (w == null){ - if (env.getEnv(CommandHelperEnvironment.class).GetCommandSender() instanceof MCPlayer) { - w = env.getEnv(CommandHelperEnvironment.class).GetPlayer().getWorld(); - } - } - - if (w == null) { - throw new ConfigRuntimeException("No world specified.", ExceptionType.InvalidWorldException, t); - } - - RegionManager mgr = Static.getWorldGuardPlugin(t).getGlobalRegionManager().get(((BukkitMCWorld) w).__World()); - - ProtectedRegion regionExists = mgr.getRegion(region); - - if (regionExists != null) { - throw new ConfigRuntimeException(String.format("The region (%s) already exists in world (%s)," - + " and cannot be created again.", region, w.getName()), ExceptionType.PluginInternalException, t); - } - - ProtectedRegion newRegion; - - if ("__global__".equalsIgnoreCase(region)) { - newRegion = new GlobalProtectedRegion(region); - } else { - if (verticies.size() == 2) { - newRegion = new ProtectedCuboidRegion(region, verticies.get(0), verticies.get(1)); - } else { - - List pointsPoly = new ArrayList<>(); - int minY = 0; - int maxY = 0; - - for (int i = 0; i < verticies.size(); i++) { - - BlockVector vector = verticies.get(i); - - int x = vector.getBlockX(); - int y = vector.getBlockY(); - int z = vector.getBlockZ(); - - pointsPoly.add(new BlockVector2D(x, z)); - - if (i == 0) { - minY = maxY = y; - } else { - if (y < minY) { - minY = y; - } else if (y > maxY) { - maxY = y; - } - } - } - newRegion = new ProtectedPolygonalRegion(region, pointsPoly, minY, maxY); - } - - if (newRegion == null) { - throw new ConfigRuntimeException("Error while creating protected region", ExceptionType.PluginInternalException, t); - } - } - - mgr.addRegion(newRegion); - - try { - mgr.save(); - } catch (ProtectionDatabaseException e) { - throw new ConfigRuntimeException("Error while creating protected region", ExceptionType.PluginInternalException, t, e); - } - - return CVoid.VOID; - } - - @Override - public CHVersion since() { - return CHVersion.V3_3_1; - } - } - - @api - public static class sk_region_update extends SKFunction { - - @Override - public String getName() { - return "sk_region_update"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{2, 3}; - } - - @Override - public String docs() { - return "void {[world], name, array(locationArrayPos1, locationArrayPos2, [[locationArrayPosN]...])} Updates the location of a given region to the new location. Other properties of the region, like owners, members, priority, etc are unaffected."; - } - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException, ExceptionType.PluginInternalException}; - } - - @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { - Static.checkPlugin("WorldGuard", t); - - World world = null; - String region; - - if (args.length == 2) { - - region = args[0].val(); - - MCPlayer m = null; - - if (env.getEnv(CommandHelperEnvironment.class).GetCommandSender() instanceof MCPlayer) { - m = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); - } - - if (m != null) { - world = Bukkit.getServer().getWorld(m.getWorld().getName()); - } - } else { - region = args[1].val(); - world = Bukkit.getServer().getWorld(args[0].val()); - } - - if (world == null) { - throw new ConfigRuntimeException("Unknown world specified", ExceptionType.InvalidWorldException, t); - } - - if ("__global__".equalsIgnoreCase(region)) { - throw new ConfigRuntimeException("You cannot change position of __global__ region.", ExceptionType.PluginInternalException, t); - } - - RegionManager mgr = Static.getWorldGuardPlugin(t).getGlobalRegionManager().get(world); - - ProtectedRegion oldRegion = mgr.getRegion(region); - - if (oldRegion == null) { - throw new ConfigRuntimeException(String.format("The region (%s) does not exist in world (%s).", region, world.getName()), ExceptionType.PluginInternalException, t); - } - - if (!(args[args.length - 1] instanceof CArray)) { - throw new ConfigRuntimeException("Pass an array of points to define a new region", ExceptionType.PluginInternalException, t); - } - - List points = new ArrayList(); - List points2D = new ArrayList(); - - int minY = 0; - int maxY = 0; - - ProtectedRegion newRegion = null; - - CArray arg = (CArray) args[args.length - 1]; - - for (int i = 0; i < arg.size(); i++) { - MCLocation point = ObjectGenerator.GetGenerator().location(arg.get(i, t), null, t); - - int x = point.getBlockX(); - int y = point.getBlockY(); - int z = point.getBlockZ(); - - if (arg.size() == 2) { - points.add(new BlockVector(x, y, z)); - } else { - points2D.add(new BlockVector2D(x, z)); - - if (i == 0) { - minY = maxY = y; - } else { - if (y < minY) { - minY = y; - } else if (y > maxY) { - maxY = y; - } - } - } - } - - if (arg.size() == 2) { - newRegion = new ProtectedCuboidRegion(region, points.get(0), points.get(1)); - } else { - newRegion = new ProtectedPolygonalRegion(region, points2D, minY, maxY); - } - - if (newRegion == null) { - throw new ConfigRuntimeException("Error while redefining protected region", ExceptionType.PluginInternalException, t); - } - - mgr.addRegion(newRegion); - - newRegion.setMembers(oldRegion.getMembers()); - newRegion.setOwners(oldRegion.getOwners()); - newRegion.setFlags(oldRegion.getFlags()); - newRegion.setPriority(oldRegion.getPriority()); - try { - newRegion.setParent(oldRegion.getParent()); - } catch (Exception ignore) { - } - - try { - mgr.save(); - } catch (Exception e) { - throw new ConfigRuntimeException("Error while redefining protected region", ExceptionType.PluginInternalException, t, e); - } - - return CVoid.VOID; - } - - @Override - public CHVersion since() { - return CHVersion.V3_3_1; - } - } - - @api - public static class sk_region_rename extends SKFunction { - - @Override - public String getName() { - return "sk_region_rename"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{2, 3}; - } - - @Override - public String docs() { - return "void {[world], oldName, newName} Rename the existing region. Other properties of the region, like owners, members, priority, etc are unaffected."; - } - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException, ExceptionType.PluginInternalException}; - } - - @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { - Static.checkPlugin("WorldGuard", t); - - World world = null; - String oldRegionName; - String newRegionName; - - if (args.length == 2) { - - oldRegionName = args[0].val(); - newRegionName = args[1].val(); - - MCPlayer m = null; - - if (env.getEnv(CommandHelperEnvironment.class).GetCommandSender() instanceof MCPlayer) { - m = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); - } - - if (m != null) { - world = Bukkit.getServer().getWorld(m.getWorld().getName()); - } - } else { - oldRegionName = args[1].val(); - newRegionName = args[2].val(); - world = Bukkit.getServer().getWorld(args[0].val()); - } - - if (world == null) { - throw new ConfigRuntimeException("Unknown world specified", ExceptionType.InvalidWorldException, t); - } - - if ("__global__".equalsIgnoreCase(oldRegionName)) { - throw new ConfigRuntimeException("You cannot change name of __global__ region.", ExceptionType.PluginInternalException, t); - } - - if ("__global__".equalsIgnoreCase(newRegionName)) { - throw new ConfigRuntimeException("You cannot change name of any region to __global__.", ExceptionType.PluginInternalException, t); - } - - RegionManager mgr = Static.getWorldGuardPlugin(t).getGlobalRegionManager().get(world); - - ProtectedRegion oldRegion = mgr.getRegion(oldRegionName); - - if (oldRegion == null) { - throw new ConfigRuntimeException(String.format("The region (%s) does not exist in world (%s).", oldRegionName, world.getName()), ExceptionType.PluginInternalException, t); - } - - ProtectedRegion newRegion = null; - - if (oldRegion instanceof ProtectedCuboidRegion) { - newRegion = new ProtectedCuboidRegion(newRegionName, oldRegion.getMinimumPoint(), oldRegion.getMaximumPoint()); - } else { - newRegion = new ProtectedPolygonalRegion(newRegionName, oldRegion.getPoints(), oldRegion.getMinimumPoint().getBlockY(), oldRegion.getMaximumPoint().getBlockY()); - } - - if (newRegion == null) { - throw new ConfigRuntimeException("Error while redefining protected region", ExceptionType.PluginInternalException, t); - } - - mgr.addRegion(newRegion); - - newRegion.setMembers(oldRegion.getMembers()); - newRegion.setOwners(oldRegion.getOwners()); - newRegion.setFlags(oldRegion.getFlags()); - newRegion.setPriority(oldRegion.getPriority()); - try { - newRegion.setParent(oldRegion.getParent()); - } catch (Exception ignore) { - } - - mgr.removeRegion(oldRegionName); - - try { - mgr.save(); - } catch (Exception e) { - throw new ConfigRuntimeException("Error while renaming protected region", ExceptionType.PluginInternalException, t, e); - } - - return CVoid.VOID; - } - - @Override - public CHVersion since() { - return CHVersion.V3_3_1; - } - } - - @api - public static class sk_region_remove extends SKFunction { - - @Override - public String getName() { - return "sk_region_remove"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{1, 2}; - } - - @Override - public String docs() { - return "void {[world], name} Remove existed region."; - } - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException, ExceptionType.PluginInternalException}; - } - - @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { - Static.checkPlugin("WorldGuard", t); - - World world = null; - String region; - - if (args.length == 1) { - - region = args[0].val(); - - MCPlayer m = null; - - if (env.getEnv(CommandHelperEnvironment.class).GetCommandSender() instanceof MCPlayer) { - m = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); - } - - if (m != null) { - world = Bukkit.getServer().getWorld(m.getWorld().getName()); - } - } else { - region = args[1].val(); - world = Bukkit.getServer().getWorld(args[0].val()); - } - - if (world == null) { - throw new ConfigRuntimeException("Unknown world specified", ExceptionType.InvalidWorldException, t); - } - - RegionManager mgr = Static.getWorldGuardPlugin(t).getGlobalRegionManager().get(world); - - ProtectedRegion regionExists = mgr.getRegion(region); - - if (regionExists == null) { - throw new ConfigRuntimeException(String.format("The region (%s) does not exist in world (%s).", region, world.getName()), ExceptionType.PluginInternalException, t); - } - - mgr.removeRegion(region); - - try { - mgr.save(); - } catch (Exception e) { - throw new ConfigRuntimeException("Error while removing protected region", ExceptionType.PluginInternalException, t, e); - } - - return CVoid.VOID; - } - - @Override - public CHVersion since() { - return CHVersion.V3_3_1; - } - } - - @api - public static class sk_region_exists extends SKFunction { - - @Override - public String getName() { - return "sk_region_exists"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{1, 2}; - } - - @Override - public String docs() { - return "void {[world], name} Check if a given region exists."; - } - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException}; - } - - @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { - Static.checkPlugin("WorldGuard", t); - - World world = null; - String region; - - if (args.length == 1) { - - region = args[0].val(); - - MCPlayer m = null; - - if (env.getEnv(CommandHelperEnvironment.class).GetCommandSender() instanceof MCPlayer) { - m = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); - } - - if (m != null) { - world = Bukkit.getServer().getWorld(m.getWorld().getName()); - } - } else { - region = args[1].val(); - world = Bukkit.getServer().getWorld(args[0].val()); - } - - if (world == null) { - throw new ConfigRuntimeException("Unknown world specified", ExceptionType.InvalidWorldException, t); - } - - RegionManager mgr = Static.getWorldGuardPlugin(t).getGlobalRegionManager().get(world); - - ProtectedRegion regionExists = mgr.getRegion(region); - - if (regionExists != null) { - return CBoolean.TRUE; - } - - return CBoolean.FALSE; - } - - @Override - public CHVersion since() { - return CHVersion.V3_3_1; - } - } - - @api - public static class sk_region_addowner extends SKFunction { - - @Override - public String getName() { - return "sk_region_addowner"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{1, 2, 3}; - } - - @Override - public String docs() { - return "void {region, [world], [owner1] | region, [world], [array(owner1, ownerN, ...)]} Add owner(s) to given region."; - } - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException, ExceptionType.PluginInternalException}; - } - - @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { - Static.checkPlugin("WorldGuard", t); - - World world = null; - MCPlayer m = null; - String[] owners = null; - String region = args[0].val(); - - if (args.length == 1) { - - if (env.getEnv(CommandHelperEnvironment.class).GetCommandSender() instanceof MCPlayer) { - m = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); - } - - if (m != null) { - world = Bukkit.getServer().getWorld(m.getWorld().getName()); - owners = new String[1]; - owners[0] = m.getName(); - } - - } else if (args.length == 2) { - - world = Bukkit.getServer().getWorld(args[0].val()); - - if (env.getEnv(CommandHelperEnvironment.class).GetCommandSender() instanceof MCPlayer) { - m = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); - } - - if (m != null) { - owners = new String[1]; - owners[0] = m.getName(); - } - - } else { - - world = Bukkit.getServer().getWorld(args[1].val()); - - if (args[2] instanceof CArray) { - - CArray arg = (CArray) args[2]; - owners = new String[(int)arg.size()]; - - for (int i = 0; i < arg.size(); i++) { - owners[i] = arg.get(i, t).val(); - } - } else { - owners = new String[1]; - owners[0] = args[2].val(); - } - - } - - if (world == null) { - throw new ConfigRuntimeException("Unknown world specified", ExceptionType.InvalidWorldException, t); - } - - RegionManager mgr = Static.getWorldGuardPlugin(t).getGlobalRegionManager().get(world); - - ProtectedRegion regionExists = mgr.getRegion(region); - - if (regionExists == null) { - if ("__global__".equalsIgnoreCase(region)) { - regionExists = new GlobalProtectedRegion(region); - mgr.addRegion(regionExists); - } else { - throw new ConfigRuntimeException(String.format("The region (%s) does not exist in world (%s).", region, world.getName()), ExceptionType.PluginInternalException, t); - } - } - - RegionDBUtil.addToDomain(regionExists.getOwners(), owners, 0); - - try { - mgr.save(); - } catch (Exception e) { - throw new ConfigRuntimeException("Error while adding owner(s) to protected region", ExceptionType.PluginInternalException, t, e); - } - - return CVoid.VOID; - } - - @Override - public CHVersion since() { - return CHVersion.V3_3_1; - } - } - - @api - public static class sk_region_remowner extends SKFunction { - - @Override - public String getName() { - return "sk_region_remowner"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{1, 2, 3}; - } - - @Override - public String docs() { - return "void {region, [world], [owner1] | region, [world], [array(owner1, ownerN, ...)]} Remove owner(s) from given region."; - } - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException, ExceptionType.PluginInternalException}; - } - - @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { - Static.checkPlugin("WorldGuard", t); - - World world = null; - MCPlayer m = null; - String[] owners = null; - String region = args[0].val(); - - if (args.length == 1) { - - if (env.getEnv(CommandHelperEnvironment.class).GetCommandSender() instanceof MCPlayer) { - m = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); - } - - if (m != null) { - world = Bukkit.getServer().getWorld(m.getWorld().getName()); - owners = new String[1]; - owners[0] = m.getName(); - } - - } else if (args.length == 2) { - - world = Bukkit.getServer().getWorld(args[0].val()); - - if (env.getEnv(CommandHelperEnvironment.class).GetCommandSender() instanceof MCPlayer) { - m = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); - } - - if (m != null) { - owners = new String[1]; - owners[0] = m.getName(); - } - - } else { - - world = Bukkit.getServer().getWorld(args[1].val()); - - if (args[2] instanceof CArray) { - - CArray arg = (CArray) args[2]; - owners = new String[(int)arg.size()]; - - for (int i = 0; i < arg.size(); i++) { - owners[i] = arg.get(i, t).val(); - } - } else { - owners = new String[1]; - owners[0] = args[2].val(); - } - - } - - if (world == null) { - throw new ConfigRuntimeException("Unknown world specified", ExceptionType.InvalidWorldException, t); - } - - RegionManager mgr = Static.getWorldGuardPlugin(t).getGlobalRegionManager().get(world); - - ProtectedRegion regionExists = mgr.getRegion(region); - - if (regionExists == null) { - throw new ConfigRuntimeException(String.format("The region (%s) does not exist in world (%s).", region, world.getName()), ExceptionType.PluginInternalException, t); - } - - RegionDBUtil.removeFromDomain(regionExists.getOwners(), owners, 0); - - try { - mgr.save(); - } catch (Exception e) { - throw new ConfigRuntimeException("Error while deleting owner(s) from protected region", ExceptionType.PluginInternalException, t, e); - } - - return CVoid.VOID; - } - - @Override - public CHVersion since() { - return CHVersion.V3_3_1; - } - } - - @api - public static class sk_region_owners extends SKFunction { - - @Override - public String getName() { - return "sk_region_owners"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{2}; - } - - @Override - public String docs() { - return "array {region, world} Returns an array of owners of this region. If the world" - + " or region cannot be found, a PluginInternalException is thrown."; - } - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PluginInternalException}; - } - - @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { - try { - String regionName = args[0].val(); - String worldName = args[1].val(); - List ownersPlayers = new ArrayList(); - List ownersGroups = new ArrayList(); - World world = Bukkit.getServer().getWorld(worldName); - if (world == null) { - throw new ConfigRuntimeException("Unknown world specified", ExceptionType.PluginInternalException, t); - } - RegionManager mgr = Static.getWorldGuardPlugin(t).getGlobalRegionManager().get(world); - ProtectedRegion region = mgr.getRegion(regionName); - if (region == null) { - throw new ConfigRuntimeException("Region could not be found!", ExceptionType.PluginInternalException, t); - } - - ownersPlayers.addAll(region.getOwners().getPlayers()); - ownersGroups.addAll(region.getOwners().getGroups()); - - CArray owners = new CArray(t); - for (String owner : ownersPlayers) { - owners.push(new CString(owner, t)); - } - for (String owner : ownersGroups) { - owners.push(new CString("*" + owner, t)); - } - return owners; - - } catch (NoClassDefFoundError e) { - throw new ConfigRuntimeException("It does not appear as though the WorldEdit or WorldGuard plugin is loaded properly. Execution of " + this.getName() + " cannot continue.", ExceptionType.InvalidPluginException, t, e); - } - } - } - - @api - public static class sk_region_addmember extends SKFunction { - - @Override - public String getName() { - return "sk_region_addmember"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{1, 2, 3}; - } - - @Override - public String docs() { - return "void {region, [world], [member1] | region, [world], [array(member1, memberN, ...)]} Add member(s) to given region."; - } - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException, ExceptionType.PluginInternalException}; - } - - @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { - Static.checkPlugin("WorldGuard", t); - - World world = null; - MCPlayer m = null; - String[] members = null; - String region = args[0].val(); - - if (args.length == 1) { - - if (env.getEnv(CommandHelperEnvironment.class).GetCommandSender() instanceof MCPlayer) { - m = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); - } - - if (m != null) { - world = Bukkit.getServer().getWorld(m.getWorld().getName()); - members = new String[1]; - members[0] = m.getName(); - } - - } else if (args.length == 2) { - - world = Bukkit.getServer().getWorld(args[0].val()); - - if (env.getEnv(CommandHelperEnvironment.class).GetCommandSender() instanceof MCPlayer) { - m = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); - } - - if (m != null) { - members = new String[1]; - members[0] = m.getName(); - } - - } else { - - world = Bukkit.getServer().getWorld(args[1].val()); - - if (args[2] instanceof CArray) { - - CArray arg = (CArray) args[2]; - members = new String[(int)arg.size()]; - - for (int i = 0; i < arg.size(); i++) { - members[i] = arg.get(i, t).val(); - } - } else { - members = new String[1]; - members[0] = args[2].val(); - } - - } - - if (world == null) { - throw new ConfigRuntimeException("Unknown world specified", ExceptionType.InvalidWorldException, t); - } - - RegionManager mgr = Static.getWorldGuardPlugin(t).getGlobalRegionManager().get(world); - - ProtectedRegion regionExists = mgr.getRegion(region); - - if (regionExists == null) { - if ("__global__".equalsIgnoreCase(region)) { - regionExists = new GlobalProtectedRegion(region); - mgr.addRegion(regionExists); - } else { - throw new ConfigRuntimeException(String.format("The region (%s) does not exist in world (%s).", region, world.getName()), ExceptionType.PluginInternalException, t); - } - } - - RegionDBUtil.addToDomain(regionExists.getMembers(), members, 0); - - try { - mgr.save(); - } catch (Exception e) { - throw new ConfigRuntimeException("Error while adding member(s) to protected region", ExceptionType.PluginInternalException, t, e); - } - - return CVoid.VOID; - } - - @Override - public CHVersion since() { - return CHVersion.V3_3_1; - } - } - - @api - public static class sk_region_remmember extends SKFunction { - - @Override - public String getName() { - return "sk_region_remmember"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{1, 2, 3}; - } - - @Override - public String docs() { - return "void {region, [world], [member1] | region, [world], [array(member1, memberN, ...)]} Remove member(s) from given region."; - } - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException, ExceptionType.PluginInternalException}; - } - - @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { - Static.checkPlugin("WorldGuard", t); - - World world = null; - MCPlayer m = null; - String[] members = null; - String region = args[0].val(); - - if (args.length == 1) { - - if (env.getEnv(CommandHelperEnvironment.class).GetCommandSender() instanceof MCPlayer) { - m = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); - } - - if (m != null) { - world = Bukkit.getServer().getWorld(m.getWorld().getName()); - members = new String[1]; - members[0] = m.getName(); - } - - } else if (args.length == 2) { - - world = Bukkit.getServer().getWorld(args[0].val()); - - if (env.getEnv(CommandHelperEnvironment.class).GetCommandSender() instanceof MCPlayer) { - m = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); - } - - if (m != null) { - members = new String[1]; - members[0] = m.getName(); - } - - } else { - - world = Bukkit.getServer().getWorld(args[1].val()); - - if (args[2] instanceof CArray) { - - CArray arg = (CArray) args[2]; - members = new String[(int)arg.size()]; - - for (int i = 0; i < arg.size(); i++) { - members[i] = arg.get(i, t).val(); - } - } else { - members = new String[1]; - members[0] = args[2].val(); - } - - } - - if (world == null) { - throw new ConfigRuntimeException("Unknown world specified", ExceptionType.InvalidWorldException, t); - } - - RegionManager mgr = Static.getWorldGuardPlugin(t).getGlobalRegionManager().get(world); - - ProtectedRegion regionExists = mgr.getRegion(region); - - if (regionExists == null) { - throw new ConfigRuntimeException(String.format("The region (%s) does not exist in world (%s).", region, world.getName()), ExceptionType.PluginInternalException, t); - } - - RegionDBUtil.removeFromDomain(regionExists.getMembers(), members, 0); - - try { - mgr.save(); - } catch (Exception e) { - throw new ConfigRuntimeException("Error while deleting members(s) from protected region", ExceptionType.PluginInternalException, t, e); - } - - return CVoid.VOID; - } - - @Override - public CHVersion since() { - return CHVersion.V3_3_1; - } - } - - @api - public static class sk_region_members extends SKFunction { - - @Override - public String getName() { - return "sk_region_members"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{2}; - } - - @Override - public String docs() { - return "array {region, world} Returns an array of members of this region. If the world" - + " or region cannot be found, a PluginInternalException is thrown."; - } - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PluginInternalException}; - } - - @Override - public Construct exec(Target t, Environment env, Construct... args) throws ConfigRuntimeException { - try { - String regionName = args[0].val(); - String worldName = args[1].val(); - List membersPlayers = new ArrayList(); - List membersGroups = new ArrayList(); - World world = Bukkit.getServer().getWorld(worldName); - if (world == null) { - throw new ConfigRuntimeException("Unknown world specified", ExceptionType.PluginInternalException, t); - } - RegionManager mgr = Static.getWorldGuardPlugin(t).getGlobalRegionManager().get(world); - ProtectedRegion region = mgr.getRegion(regionName); - if (region == null) { - throw new ConfigRuntimeException("Region could not be found!", ExceptionType.PluginInternalException, t); - } - - membersPlayers.addAll(region.getMembers().getPlayers()); - membersGroups.addAll(region.getMembers().getGroups()); - - CArray members = new CArray(t); - for (String member : membersPlayers) { - members.push(new CString(member, t)); - } - for (String member : membersGroups) { - members.push(new CString("*" + member, t)); - } - return members; - - } catch (NoClassDefFoundError e) { - throw new ConfigRuntimeException("It does not appear as though the WorldEdit or WorldGuard plugin is loaded properly. Execution of " + this.getName() + " cannot continue.", ExceptionType.InvalidPluginException, t, e); - } - } - } - - @api - public static class sk_region_flag extends SKFunction { - - @Override - public String getName() { - return "sk_region_flag"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{4, 5}; - } - - @Override - public String docs() { - return "void {world, region, flagName, flagValue, [group]} Add/change/remove flag for selected region. FlagName should be any" - + " supported flag from [http://wiki.sk89q.com/wiki/WorldGuard/Regions/Flags this list]. For the flagValue, use types which" - + " are supported by WorldGuard. Add group argument if you want to add WorldGuard group flag (read more about group" - + " flag types [http://wiki.sk89q.com/wiki/WorldGuard/Regions/Flags#Group here]). Set flagValue as null (and don't set" - + " group) to delete the flag from the region."; - } - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException, ExceptionType.PluginInternalException}; - } - - @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { - Static.checkPlugin("WorldGuard", t); - - World world = Bukkit.getServer().getWorld(args[0].val()); - - if (world == null) { - throw new ConfigRuntimeException("Unknown world specified", ExceptionType.InvalidWorldException, t); - } - - String regionName = args[1].val(); - String flagName = args[2].val(); - String flagValue = null; - RegionGroup groupValue = null; - - if (args.length >= 4 && !(args[3] instanceof CNull) && args[3].val() != "") { - flagValue = args[3].val(); - } - - RegionManager mgr = Static.getWorldGuardPlugin(t).getGlobalRegionManager().get(world); - - ProtectedRegion region = mgr.getRegion(regionName); - - if (region == null) { - if ("__global__".equalsIgnoreCase(regionName)) { - region = new GlobalProtectedRegion(regionName); - mgr.addRegion(region); - } else { - throw new ConfigRuntimeException(String.format("The region (%s) does not exist in world (%s).", regionName, world.getName()), ExceptionType.PluginInternalException, t); - } - } - - Flag foundFlag = null; - - for (Flag flag : DefaultFlag.getFlags()) { - if (flag.getName().replace("-", "").equalsIgnoreCase(flagName.replace("-", ""))) { - foundFlag = flag; - break; - } - } - - if (foundFlag == null) { - throw new ConfigRuntimeException(String.format("Unknown flag specified: (%s).", flagName), ExceptionType.PluginInternalException, t); - } - - if (args.length == 5) { - String group = args[4].val(); - RegionGroupFlag groupFlag = foundFlag.getRegionGroupFlag(); - if (groupFlag == null) { - throw new ConfigRuntimeException(String.format("Region flag (%s) does not have a group flag.", flagName), ExceptionType.PluginInternalException, t); - } - - try { - groupValue = groupFlag.parseInput(Static.getWorldGuardPlugin(t), new BukkitMCCommandSender(env.getEnv(CommandHelperEnvironment.class).GetCommandSender())._CommandSender(), group); - } catch (InvalidFlagFormat e) { - throw new ConfigRuntimeException(String.format("Unknown group (%s).", group), ExceptionType.PluginInternalException, t); - } - - } - - if (flagValue != null) { - try { - setFlag(t, region, foundFlag, new BukkitMCCommandSender(env.getEnv(CommandHelperEnvironment.class).GetCommandSender())._CommandSender(), flagValue); - - } catch (Exception e) { - throw new ConfigRuntimeException(String.format("Unknown flag value specified: (%s).", flagValue), ExceptionType.PluginInternalException, t); - } - } else if (args.length < 5) { - region.setFlag(foundFlag, null); - - RegionGroupFlag groupFlag = foundFlag.getRegionGroupFlag(); - if (groupFlag != null) { - region.setFlag(groupFlag, null); - } - } - - if (groupValue != null) { - RegionGroupFlag groupFlag = foundFlag.getRegionGroupFlag(); - - if (groupValue == groupFlag.getDefault()) { - region.setFlag(groupFlag, null); - } else { - region.setFlag(groupFlag, groupValue); - } - } - - try { - mgr.save(); - } catch (Exception e) { - throw new ConfigRuntimeException("Error while defining flags", ExceptionType.PluginInternalException, t, e); - } - - return CVoid.VOID; - } - - private void setFlag(Target t, ProtectedRegion region, - Flag flag, CommandSender sender, String value) - throws InvalidFlagFormat { - region.setFlag(flag, flag.parseInput(Static.getWorldGuardPlugin(t), sender, value)); - } - - @Override - public CHVersion since() { - return CHVersion.V3_3_1; - } - } - - @api - public static class sk_region_check_flag extends SKFunction { - - @Override - public String getName() { - return "sk_region_check_flag"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{2, 3}; - } - - @Override - public String docs() { - return "mixed {locationArray, flagName, [player]} Check state of selected flag in defined location." - + " FlagName should be any supported flag from [http://wiki.sk89q.com/wiki/WorldGuard/Regions/Flags this list]." - + " Player defaults to the current player."; - } - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException, ExceptionType.FormatException, - ExceptionType.PluginInternalException, ExceptionType.NotFoundException}; - } - - @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { - Static.checkPlugin("WorldGuard", t); - - if ("build".equalsIgnoreCase(args[1].val())) { - throw new ConfigRuntimeException(String.format("Can't use build flag with %s method. This is an limitation of WorldGuard.", this.getName()), ExceptionType.PluginInternalException, t); - } - - MCPlayer p = null; - MCWorld w = null; - - if (args.length == 2) { - if (env.getEnv(CommandHelperEnvironment.class).GetCommandSender() instanceof MCPlayer) { - p = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); - } - } else { - p = Static.GetPlayer(args[2].val(), t); - } - - if (p != null) { - w = p.getWorld(); - } - - MCLocation l = ObjectGenerator.GetGenerator().location(args[0], w, t); - - Flag foundFlag = null; - - for (Flag flag : DefaultFlag.getFlags()) { - if (flag.getName().replace("-", "").equalsIgnoreCase(args[1].val().replace("-", ""))) { - foundFlag = flag; - break; - } - } - - if (foundFlag == null) { - throw new ConfigRuntimeException(String.format("Unknown flag specified: (%s).", args[1].val()), ExceptionType.PluginInternalException, t); - } - - RegionManager mgr = Static.getWorldGuardPlugin(t).getGlobalRegionManager().get(Bukkit.getServer().getWorld(l.getWorld().getName())); - - ApplicableRegionSet set = mgr.getApplicableRegions(((BukkitMCLocation) l)._Location()); - - if (foundFlag instanceof StateFlag) { - if (p == null) { - return CBoolean.get(set.allows((StateFlag) foundFlag)); - } else { - return CBoolean.get(set.allows((StateFlag) foundFlag, Static.getWorldGuardPlugin(t).wrapPlayer(((BukkitMCPlayer) p)._Player()))); - } - } else { - Object getFlag; - - if (p == null) { - getFlag = set.getFlag((Flag) foundFlag); - } else { - getFlag = set.getFlag((Flag) foundFlag, Static.getWorldGuardPlugin(t).wrapPlayer(((BukkitMCPlayer) p)._Player())); - } - - if (foundFlag instanceof BooleanFlag) { - Boolean value = ((BooleanFlag) foundFlag).unmarshal(getFlag); - if (value != null) { - return CBoolean.get(value); - } else { - return CNull.NULL; - } - } else if (foundFlag instanceof DoubleFlag) { - Double value = ((DoubleFlag) foundFlag).unmarshal(getFlag); - if (value != null) { - return new CDouble(value, t); - } else { - return CNull.NULL; - } - } else if (foundFlag instanceof EnumFlag) { - String value = ((EnumFlag) foundFlag).unmarshal(getFlag).name(); - if (value != null) { - return new CString(value, t); - } else { - return CNull.NULL; - } - } else if (foundFlag instanceof IntegerFlag) { - Integer value = ((IntegerFlag) foundFlag).unmarshal(getFlag); - if (value != null) { - return new CInt(value, t); - } else { - return CNull.NULL; - } - } else if (foundFlag instanceof LocationFlag) { - com.sk89q.worldedit.Location value = ((LocationFlag) foundFlag).unmarshal(getFlag); - if (value != null) { - return new CArray(t, - new CDouble(value.getPosition().getX(), t), - new CDouble(value.getPosition().getY(), t), - new CDouble(value.getPosition().getZ(), t), - new CString(l.getWorld().getName(), t)); - } else { - return CNull.NULL; - } - } else if (foundFlag instanceof RegionGroupFlag) { - String value = ((RegionGroupFlag) foundFlag).unmarshal(getFlag).name(); - if (value != null) { - return new CString(value, t); - } else { - return CNull.NULL; - } - } else if (foundFlag instanceof SetFlag) { - - CArray values = new CArray(t); - - Set setValue = ((SetFlag) foundFlag).unmarshal(getFlag); - - if (setValue != null) { - for (Object setFlag : setValue) { - if (setFlag instanceof String) { - String value = (String) setFlag; - values.push(new CString(value, t)); - } else if (setFlag instanceof EntityType) { - String value = ((EntityType) setFlag).getName(); - values.push(new CString(value, t)); - } else { - ConfigRuntimeException.DoWarning("One of the element of flag has unknown type. This is a developer mistake, please file a ticket."); - } - } - } - - return values; - - } else if (foundFlag instanceof StringFlag) { - String value = ((StringFlag) foundFlag).unmarshal(getFlag); - if (value != null) { - return new CString(value, t); - } else { - return CNull.NULL; - } - } - - throw new ConfigRuntimeException("The flag type is unknown. This is a developer mistake, please file a ticket.", ExceptionType.NotFoundException, t); - } - - } - - @Override - public CHVersion since() { - return CHVersion.V3_3_1; - } - } - - @api - public static class sk_region_setpriority extends SKFunction { - - @Override - public String getName() { - return "sk_region_setpriority"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{2, 3}; - } - - @Override - public String docs() { - return "void {[world], region, priority} Sets priority for a given region."; - } - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException, ExceptionType.PluginInternalException}; - } - - @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { - Static.checkPlugin("WorldGuard", t); - - World world = null; - String region; - int priority = 0; - - if (args.length == 2) { - - region = args[0].val(); - - MCPlayer m = null; - - if (env.getEnv(CommandHelperEnvironment.class).GetCommandSender() instanceof MCPlayer) { - m = env.getEnv(CommandHelperEnvironment.class).GetPlayer(); - } - - if (m != null) { - world = Bukkit.getServer().getWorld(m.getWorld().getName()); - } - - priority = Static.getInt32(args[1], t); - - } else { - region = args[1].val(); - world = Bukkit.getServer().getWorld(args[0].val()); - - priority = Static.getInt32(args[2], t); - } - - if (world == null) { - throw new ConfigRuntimeException("Unknown world specified", ExceptionType.InvalidWorldException, t); - } - - if ("__global__".equalsIgnoreCase(region)) { - throw new ConfigRuntimeException("The region cannot be named __global__.", ExceptionType.PluginInternalException, t); - } - - RegionManager mgr = Static.getWorldGuardPlugin(t).getGlobalRegionManager().get(world); - - ProtectedRegion regionExists = mgr.getRegion(region); - - if (regionExists == null) { - throw new ConfigRuntimeException(String.format("The region (%s) does not exist in world (%s).", region, world.getName()), ExceptionType.PluginInternalException, t); - } - - regionExists.setPriority(priority); - - try { - mgr.save(); - } catch (Exception e) { - throw new ConfigRuntimeException("Error while setting priority for protected region", ExceptionType.PluginInternalException, t, e); - } - - return CVoid.VOID; - } - - @Override - public CHVersion since() { - return CHVersion.V3_3_1; - } - } - - @api - public static class sk_region_setparent extends SKFunction { - - @Override - public String getName() { - return "sk_region_setparent"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{2, 3}; - } - - @Override - public String docs() { - return "void {world, region, [parentRegion]} Sets parent region for a given region."; - } - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException, ExceptionType.PluginInternalException}; - } - - @Override - public Construct exec(Target t, Environment env, Construct... args) throws CancelCommandException, ConfigRuntimeException { - Static.checkPlugin("WorldGuard", t); - - String regionName; - String parentName; - - - World world = Bukkit.getServer().getWorld(args[0].val()); - - if (world == null) { - throw new ConfigRuntimeException("Unknown world specified", ExceptionType.InvalidWorldException, t); - } - - regionName = args[1].val(); - - if ("__global__".equalsIgnoreCase(regionName)) { - throw new ConfigRuntimeException("You cannot set parents for a __global__ cuboid.", ExceptionType.PluginInternalException, t); - } - - RegionManager mgr = Static.getWorldGuardPlugin(t).getGlobalRegionManager().get(world); - - ProtectedRegion child = mgr.getRegion(regionName); - - if (child == null) { - throw new ConfigRuntimeException(String.format("The region (%s) does not exist in world (%s).", regionName, world.getName()), ExceptionType.PluginInternalException, t); - } - - if (args.length == 2) { - try { - child.setParent(null); - } catch (ProtectedRegion.CircularInheritanceException ignore) { - } - } else { - parentName = args[2].val(); - ProtectedRegion parent = mgr.getRegion(parentName); - - if (parent == null) { - throw new ConfigRuntimeException(String.format("The region (%s) does not exist in world (%s).", parentName, world.getName()), ExceptionType.PluginInternalException, t); - } - - try { - child.setParent(parent); - } catch (ProtectedRegion.CircularInheritanceException e) { - throw new ConfigRuntimeException(String.format("Circular inheritance detected."), ExceptionType.PluginInternalException, t); - } - } - - try { - mgr.save(); - } catch (Exception e) { - throw new ConfigRuntimeException("Error while setting parent for protected region", ExceptionType.PluginInternalException, t, e); - } - - return CVoid.VOID; - } - - @Override - public CHVersion since() { - return CHVersion.V3_3_1; - } - } - - @api(environments=CommandHelperEnvironment.class) - public static class sk_can_build extends SKFunction { - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidPluginException, ExceptionType.PlayerOfflineException, - ExceptionType.FormatException, ExceptionType.InvalidWorldException}; - } - - @Override - public Construct exec(Target t, Environment environment, - Construct... args) throws ConfigRuntimeException { - Static.checkPlugin("WorldGuard", t); - MCPlayer p; - MCLocation loc; - if (args.length == 1) { - p = environment.getEnv(CommandHelperEnvironment.class).GetPlayer(); - if (p == null) { - throw new ConfigRuntimeException("A player was expected.", ExceptionType.PlayerOfflineException, t); - } - loc = ObjectGenerator.GetGenerator().location(args[0], p.getWorld(), t); - } else { - - p = Static.GetPlayer(args[0], t); - loc = ObjectGenerator.GetGenerator().location(args[1], p.getWorld(), t); - } - return CBoolean.get(Static.getWorldGuardPlugin(t).canBuild(((BukkitMCPlayer) p)._Player(), - ((BukkitMCLocation) loc)._Location())); - } - - @Override - public String getName() { - return "sk_can_build"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{1, 2}; - } - - @Override - public String docs() { - return "boolean {[player,] locationArray} Returns whether or not player can build at the location," - + " according to WorldGuard. If player is not given, the current player is used."; - } - - @Override - public Version since() { - return CHVersion.V3_3_1; - } - } - - public static abstract class SKFunction extends AbstractFunction { - - @Override - public boolean isRestricted() { - return true; - } - - @Override - public Version since() { - return CHVersion.V3_2_0; - } - - @Override - public Boolean runAsync() { - return false; - } - } - - /* **************************** Clipboard functions below this line ****************************** */ - - // Class required for working with loggers - public static class CHCommandSender extends com.sk89q.worldedit.bukkit.BukkitCommandSender { - - public CHCommandSender(com.sk89q.worldedit.bukkit.WorldEditPlugin wep) { - super(wep, wep.getServerInterface(), wep.getServer().getConsoleSender()); - } - - public CHCommandSender(Target t) { - this(Static.getWorldEditPlugin(t)); - } - - private LocalWorld world; - - @Override - public LocalWorld getWorld() { - return world; - } - - public void setWorld(LocalWorld w) { - world = w; - } - } - - // CH's local player, based from console - private static CHCommandSender player; - // CH's console-based session - private static LocalSession session; - - public static CHCommandSender getLocalPlayer(Target t) { - if (player == null) { - player = new CHCommandSender(t); - } - return player; - } - - public static LocalSession getLocalSession(Target t) { - if (session == null) { - session = Static.getWorldEditPlugin(t).getWorldEdit().getSession(getLocalPlayer(t)); - } - return session; - } - - public static LocalWorld getLocalWorld(String name, Target t) { - for (LocalWorld w : Static.getWorldEditPlugin(t).getWorldEdit().getServer().getWorlds()) { - if (w.getName().equals(name)) { - return w; - } - } - return null; - } - - // modified from com.sk89q.worldedit.LocalSession.createEditSession - public static EditSession createEditSession(CHCommandSender player, String world, boolean fastMode, Target t) { - LocalWorld w = getLocalWorld(world, t); - player.setWorld(w); - EditSession editSession = Static.getWorldEditPlugin(t).getWorldEdit().getEditSessionFactory() - .getEditSession(getLocalWorld(world, t), -1, null, player); - editSession.setFastMode(fastMode); - - return editSession; - } - - @api - public static class skcb_load extends SKFunction { - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.PluginInternalException, ExceptionType.IOException, - ExceptionType.InvalidPluginException}; - } - - @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - Static.checkPlugin("WorldEdit", t); - // Based on: com.sk89q.worldedit.commands.SchematicCommands.load - String filename = args[0].val(); - File savedir = Static.getWorldEditPlugin(t).getWorldEdit().getWorkingDirectoryFile( - Static.getWorldEditPlugin(t).getWorldEdit().getConfiguration().saveDir); - try { - File schematic = Static.getWorldEditPlugin(t).getWorldEdit().getSafeOpenFile( - getLocalPlayer(t), savedir, filename, "schematic", "schematic"); - if (!schematic.exists()) { - throw new IOException("Schematic " + filename + " could not be found."); - } - getLocalSession(t).setClipboard(SchematicFormat.getFormat(schematic).load(schematic)); - } catch (WorldEditException e) { - throw new ConfigRuntimeException(e.getMessage(), ExceptionType.PluginInternalException, t); - } catch (IOException e) { - throw new ConfigRuntimeException(e.getMessage(), ExceptionType.IOException, t); - } catch (DataException e) { - throw new ConfigRuntimeException(e.getMessage(), ExceptionType.IOException, t); - } - return CVoid.VOID; - } - - @Override - public String getName() { - return "skcb_load"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{1}; - } - - @Override - public String docs() { - return "void {filename} Loads a schematic into the clipboard from file." - + " It will use the directory specified in WorldEdit's config."; - } - - @Override - public Version since() { - return CHVersion.V3_3_1; - } - } - - @api - public static class skcb_rotate extends SKFunction { - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.RangeException, ExceptionType.NotFoundException, - ExceptionType.InvalidPluginException, ExceptionType.CastException}; - } - - @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - Static.checkPlugin("WorldEdit", t); - int angle = Static.getInt32(args[0], t); - if (angle % 90 != 0) { - throw new ConfigRuntimeException("Expected rotation angle to be a multiple of 90", - ExceptionType.RangeException, t); - } - try { - getLocalSession(t).getClipboard().rotate2D(angle); - } catch (EmptyClipboardException e) { - throw new ConfigRuntimeException("The clipboard is empty, copy something to it first!", - ExceptionType.NotFoundException, t); - } - return CVoid.VOID; - } - - @Override - public String getName() { - return "skcb_rotate"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{1}; - } - - @Override - public String docs() { - return "void {int} Given a multiple of 90, rotates the clipboard by that number."; - } - - @Override - public Version since() { - return CHVersion.V3_3_1; - } - } - - @api - public static class skcb_paste extends SKFunction { - - @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{ExceptionType.InvalidWorldException, ExceptionType.InvalidPluginException, - ExceptionType.NotFoundException, ExceptionType.RangeException, ExceptionType.CastException}; - } - - @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - Static.checkPlugin("WorldEdit", t); - boolean noAir = false; - boolean fastmode = false; - boolean entities = false; - if (args.length >= 2) { - noAir = Static.getBoolean(args[1]); - } - if (args.length >= 3) { - fastmode = Static.getBoolean(args[2]); - } - if (args.length == 4) { - entities = Static.getBoolean(args[3]); - } - MCLocation loc = ObjectGenerator.GetGenerator().location(args[0], null, t); - Vector vec = new Vector(loc.getX(), loc.getY(), loc.getZ()); - EditSession editor = createEditSession(getLocalPlayer(t), loc.getWorld().getName(), fastmode, t); - - try { - getLocalSession(t).getClipboard().paste(editor, vec, noAir, entities); - } catch (MaxChangedBlocksException e) { - throw new ConfigRuntimeException("Attempted to change more blocks than allowed.", - ExceptionType.RangeException, t); - } catch (EmptyClipboardException e) { - throw new ConfigRuntimeException("The clipboard is empty, copy something to it first!", - ExceptionType.NotFoundException, t); - } - - return CVoid.VOID; - } - - @Override - public String getName() { - return "skcb_paste"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{1, 2, 3, 4}; - } - - @Override - public String docs() { - return "void {location, [ignoreAir], [fastmode], [entities]} Pastes a schematic from the clipboard" - + " as if a player was standing at the location. If ignoreAir is true," - + " air blocks from the schematic will not replace blocks in the world." - + " If fastmode is true, the function will use WorldEdit's 'fastmode' to paste." - + " If entities is true, any entities stored in the clipboard will be pasted as well." - + " Both ignoreAir and entities default to false."; - } - - @Override - public Version since() { - return CHVersion.V3_3_1; - } - } -} diff --git a/src/main/java/com/laytonsmith/core/functions/XGUI.java b/src/main/java/com/laytonsmith/core/functions/XGUI.java index cfca57f544..326cb11135 100644 --- a/src/main/java/com/laytonsmith/core/functions/XGUI.java +++ b/src/main/java/com/laytonsmith/core/functions/XGUI.java @@ -1,23 +1,29 @@ - package com.laytonsmith.core.functions; +import com.laytonsmith.PureUtilities.Common.OSUtils; import com.laytonsmith.PureUtilities.Version; import com.laytonsmith.abstraction.StaticLayer; import com.laytonsmith.annotations.api; import com.laytonsmith.annotations.core; import com.laytonsmith.annotations.hide; import com.laytonsmith.annotations.noboilerplate; -import com.laytonsmith.core.CHVersion; -import com.laytonsmith.core.Static; +import com.laytonsmith.core.ArgumentValidation; +import com.laytonsmith.core.MSVersion; import com.laytonsmith.core.constructs.CInt; import com.laytonsmith.core.constructs.CVoid; -import com.laytonsmith.core.constructs.Construct; import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.environments.Environment; +import com.laytonsmith.core.exceptions.CRE.CREFormatException; +import com.laytonsmith.core.exceptions.CRE.CREIOException; +import com.laytonsmith.core.exceptions.CRE.CREThrowable; import com.laytonsmith.core.exceptions.ConfigRuntimeException; -import com.laytonsmith.core.functions.Exceptions.ExceptionType; +import com.laytonsmith.core.natives.interfaces.Mixed; import java.awt.Color; +import java.awt.Desktop; import java.awt.Window; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; @@ -29,35 +35,36 @@ */ @core public class XGUI { - public static String docs(){ + + public static String docs() { return "This provides extremely limited gui control functions. This entire class is experimental, and will probably be removed at" + " some point."; } - + private static Map windows = new HashMap<>(); - private static AtomicInteger windowIDs = new AtomicInteger(0); - + private static final AtomicInteger WINDOW_IDS = new AtomicInteger(0); + static { - StaticLayer.GetConvertor().addShutdownHook(new Runnable() { + StaticLayer.GetConvertor().addPersistentShutdownHook(new Runnable() { @Override public void run() { - for(Window w : windows.values()){ + for(Window w : windows.values()) { w.dispose(); } windows.clear(); } }); } - + @api @hide("experimental") @noboilerplate public static class x_create_window extends AbstractFunction { @Override - public Exceptions.ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{}; } @Override @@ -71,20 +78,20 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { JFrame frame = new JFrame(); - int id = windowIDs.incrementAndGet(); + int id = WINDOW_IDS.incrementAndGet(); String title = ""; int width = 300; int height = 300; - if(args.length > 0){ + if(args.length > 0) { title = args[0].val(); } - if(args.length > 1){ - width = Static.getInt32(args[1], t); + if(args.length > 1) { + width = ArgumentValidation.getInt32(args[1], t); } - if(args.length > 2){ - height = Static.getInt32(args[2], t); + if(args.length > 2) { + height = ArgumentValidation.getInt32(args[2], t); } frame.setTitle(title); frame.setSize(width, height); @@ -115,19 +122,19 @@ public String docs() { @Override public Version since() { - return CHVersion.V0_0_0; + return MSVersion.V0_0_0; } - + } - + @api - @hide("Expreimental") + @hide("Experimental") @noboilerplate public static class x_show_window extends AbstractFunction { @Override - public Exceptions.ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{}; } @Override @@ -141,11 +148,11 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - int id = Static.getInt32(args[0], t); + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + int id = ArgumentValidation.getInt32(args[0], t); boolean show = true; - if(args.length > 1){ - show = Static.getBoolean(args[1]); + if(args.length > 1) { + show = ArgumentValidation.getBoolean(args[1], t); } Window w = windows.get(id); w.setVisible(show); @@ -169,19 +176,19 @@ public String docs() { @Override public Version since() { - return CHVersion.V0_0_0; + return MSVersion.V0_0_0; } - + } - + @api @hide("experimental") @noboilerplate public static class x_set_window_pixel extends AbstractFunction { @Override - public ExceptionType[] thrown() { - return new ExceptionType[]{}; + public Class[] thrown() { + return new Class[]{}; } @Override @@ -195,21 +202,21 @@ public Boolean runAsync() { } @Override - public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { - int windowID = Static.getInt32(args[0], t); - int x = Static.getInt32(args[1], t); - int y = Static.getInt32(args[2], t); - int red = Static.getInt32(args[3], t); - int green = Static.getInt32(args[4], t); - int blue = Static.getInt32(args[5], t); + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + int windowID = ArgumentValidation.getInt32(args[0], t); + int x = ArgumentValidation.getInt32(args[1], t); + int y = ArgumentValidation.getInt32(args[2], t); + int red = ArgumentValidation.getInt32(args[3], t); + int green = ArgumentValidation.getInt32(args[4], t); + int blue = ArgumentValidation.getInt32(args[5], t); Window w = windows.get(windowID); - while(true){ - try{ - JPanel panel = (JPanel)w.findComponentAt(x, y); + while(true) { + try { + JPanel panel = (JPanel) w.findComponentAt(x, y); panel.getGraphics().setColor(new Color(red, green, blue)); panel.getGraphics().draw3DRect(x, y, 1, 1, true); return CVoid.VOID; - } catch(ClassCastException ex){ + } catch (ClassCastException ex) { //? return CVoid.VOID; } @@ -234,9 +241,93 @@ public String docs() { @Override public Version since() { - return CHVersion.V0_0_0; + return MSVersion.V0_0_0; } - + } - + + @api + @hide("experimental") + @noboilerplate + public static class x_launch_browser extends AbstractFunction { + + @Override + public Class[] thrown() { + return new Class[]{CREIOException.class}; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Boolean runAsync() { + return null; + } + + @Override + public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException { + String url = args[0].val(); + try { + if(Desktop.isDesktopSupported()) { + Desktop.getDesktop().browse(new URI(url)); + } + } catch (URISyntaxException ex1) { + throw new CREFormatException(ex1.getMessage(), t); + } catch (IOException ex) { + try { + // Last ditch effort + Runtime rt = Runtime.getRuntime(); + switch(OSUtils.GetOS()) { + case WINDOWS: + rt.exec("rundll32 url.dll,FileProtocolHandler " + url); + break; + case MAC: + rt.exec("open " + url); + break; + case LINUX: + default: + // Try the other OSes as linux + String[] browsers = {"epiphany", "firefox", "mozilla", "konqueror", + "netscape", "opera", "links", "lynx"}; + + StringBuilder cmd = new StringBuilder(); + for(int i = 0; i < browsers.length; i++) { + cmd.append(i == 0 ? "" : " || ").append(browsers[i]).append(" \"").append(url).append("\" "); + } + + rt.exec(new String[]{"sh", "-c", cmd.toString()}); + break; + } + } catch (IOException ex1) { + throw new CREIOException(ex1.getMessage(), t, ex1); + } + } + return CVoid.VOID; + } + + @Override + public String getName() { + return "x_launch_browser"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1}; + } + + @Override + public String docs() { + return "void {url} Launches the desktop's default browser with the given url. On headless systems, this" + + " will throw an exception."; + } + + @Override + public Version since() { + return MSVersion.V3_3_2; + } + + } + } diff --git a/src/main/java/com/laytonsmith/core/functions/asm/Cmdline.java b/src/main/java/com/laytonsmith/core/functions/asm/Cmdline.java new file mode 100644 index 0000000000..3daf163263 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/functions/asm/Cmdline.java @@ -0,0 +1,109 @@ +package com.laytonsmith.core.functions.asm; + +import com.laytonsmith.PureUtilities.Common.OSUtils; +import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.annotations.api; +import com.laytonsmith.core.ParseTree; +import com.laytonsmith.core.asm.AsmCommonLibTemplates; +import com.laytonsmith.core.asm.IRBuilder; +import com.laytonsmith.core.asm.IRData; +import com.laytonsmith.core.asm.IRDataBuilder; +import com.laytonsmith.core.asm.IRType; +import com.laytonsmith.core.asm.LLVMArgumentValidation; +import com.laytonsmith.core.asm.LLVMEnvironment; +import com.laytonsmith.core.asm.LLVMFunction; +import com.laytonsmith.core.asm.LLVMVersion; +import com.laytonsmith.core.compiler.CompilerEnvironment; +import com.laytonsmith.core.constructs.Target; +import com.laytonsmith.core.environments.Environment; +import com.laytonsmith.core.exceptions.CRE.CREThrowable; +import com.laytonsmith.core.exceptions.ConfigCompileException; +import java.util.ArrayList; +import java.util.List; + +/** + * + */ +public class Cmdline { + + @api(environments = LLVMEnvironment.class, platform = api.Platforms.COMPILER_LLVM) + public static class exit extends LLVMFunction { + + @Override + public IRData buildIR(IRBuilder builder, Target t, Environment env, ParseTree... nodes) throws ConfigCompileException { + OSUtils.OS os = env.getEnv(CompilerEnvironment.class).getTargetOS(); + LLVMEnvironment llvmenv = env.getEnv(LLVMEnvironment.class); + String code = "i32 0"; + List lines = new ArrayList<>(); + if(nodes.length > 0) { + IRData data = LLVMArgumentValidation.getInt32(builder, env, nodes[0], t); + code = data.getReference(); + } + llvmenv.addGlobalDeclaration(AsmCommonLibTemplates.EXIT, env); + lines.add("call void @exit(" + code + ") noreturn nounwind"); + lines.add("unreachable"); + builder.appendLines(t, lines); + return IRDataBuilder.asUnreachable(); + } + + @Override + public String getName() { + return "exit"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{0, 1}; + } + + @Override + public Class[] thrown() { + return null; + } + + @Override + public Version since() { + return LLVMVersion.V0_0_1; + } + + } + + @api(environments = LLVMEnvironment.class, platform = api.Platforms.COMPILER_LLVM) + public static class sys_out extends LLVMFunction { + + @Override + public IRData buildIR(IRBuilder builder, Target t, Environment env, ParseTree... nodes) throws ConfigCompileException { + OSUtils.OS os = env.getEnv(CompilerEnvironment.class).getTargetOS(); + LLVMEnvironment llvmenv = env.getEnv(LLVMEnvironment.class); + List lines = new ArrayList<>(); + IRData string = LLVMArgumentValidation.getString(builder, env, nodes[0], t); + llvmenv.addGlobalDeclaration(AsmCommonLibTemplates.PUTS, env); + int ret = llvmenv.getNewLocalVariableReference(IRType.INTEGER32); // returned value + lines.add("%" + ret + " = call i32 @puts(" + string.getReference() + ")"); + builder.appendLines(t, lines); + // TODO: Use the return value, puts doesn't actually return void. + return IRDataBuilder.asVoid(); + } + + @Override + public String getName() { + return "sys_out"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1}; + } + + @Override + public Class[] thrown() { + return null; + } + + @Override + public Version since() { + return LLVMVersion.V0_0_1; + } + + } +} diff --git a/src/main/java/com/laytonsmith/core/functions/asm/Compiler.java b/src/main/java/com/laytonsmith/core/functions/asm/Compiler.java new file mode 100644 index 0000000000..a0e73f70a3 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/functions/asm/Compiler.java @@ -0,0 +1,88 @@ +package com.laytonsmith.core.functions.asm; + +import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.annotations.api; +import com.laytonsmith.core.ParseTree; +import com.laytonsmith.core.asm.AsmCompiler; +import com.laytonsmith.core.asm.IRBuilder; +import com.laytonsmith.core.asm.IRData; +import com.laytonsmith.core.asm.IRDataBuilder; +import com.laytonsmith.core.asm.LLVMEnvironment; +import com.laytonsmith.core.asm.LLVMFunction; +import com.laytonsmith.core.asm.LLVMVersion; +import com.laytonsmith.core.constructs.Target; +import com.laytonsmith.core.environments.Environment; +import com.laytonsmith.core.exceptions.CRE.CREThrowable; +import com.laytonsmith.core.exceptions.ConfigCompileException; + +/** + * + */ +public class Compiler { + @api(environments = LLVMEnvironment.class, platform = api.Platforms.COMPILER_LLVM) + public static class __statements__ extends LLVMFunction { + + @Override + public IRData buildIR(IRBuilder builder, Target t, Environment env, ParseTree... nodes) throws ConfigCompileException { + for(ParseTree node : nodes) { + AsmCompiler.getIR(builder, node, env); + } + return IRDataBuilder.asVoid(); + } + + @Override + public String getName() { + return "__statements__"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1}; + } + + @Override + public Class[] thrown() { + return null; + } + + @Override + public Version since() { + return LLVMVersion.V0_0_1; + } + + } + + @api(environments = LLVMEnvironment.class, platform = api.Platforms.COMPILER_LLVM) + public static class dyn extends LLVMFunction { + + @Override + public IRData buildIR(IRBuilder builder, Target t, Environment env, ParseTree... nodes) throws ConfigCompileException { + IRData data = AsmCompiler.getIR(builder, nodes[0], env); + LLVMEnvironment llvmenv = env.getEnv(LLVMEnvironment.class); + int alloca = llvmenv.getNewLocalVariableReference(data.getResultType()); + int load = llvmenv.getNewLocalVariableReference(data.getResultType()); // returned value + builder.generator(t, env).allocaStoreAndLoad(alloca, data.getResultType(), data.getReference(), load); + return IRDataBuilder.setReturnVariable(load, data.getResultType()); + } + + @Override + public String getName() { + return "dyn"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{1}; + } + + @Override + public Class[] thrown() { + return null; + } + + @Override + public Version since() { + return LLVMVersion.V0_0_1; + } + } +} diff --git a/src/main/java/com/laytonsmith/core/functions/asm/ControlFlow.java b/src/main/java/com/laytonsmith/core/functions/asm/ControlFlow.java new file mode 100644 index 0000000000..c04f49c88f --- /dev/null +++ b/src/main/java/com/laytonsmith/core/functions/asm/ControlFlow.java @@ -0,0 +1,81 @@ +package com.laytonsmith.core.functions.asm; + +/** + * + */ +public class ControlFlow { +// @api(environments = LLVMEnvironment.class, platform = api.Platforms.COMPILER_LLVM) +// public static class _if extends LLVMFunction { +// +// @Override +// public boolean usePreExecution() { +// return true; +// } +// +// @Override +// public IRData getIR(Target t, Environment env, Script parent, IRData... nodes) throws ConfigCompileException { +// throw new Error(); +// } +// +// +// @Override +// public IRData preGetIR(Target t, Environment env, Script parent, ParseTree... nodes) throws ConfigCompileException { +// StringBuilder output = new StringBuilder(); +// LLVMEnvironment llvmenv = env.getEnv(LLVMEnvironment.class); +// IRData conditionIR = AsmCompiler.getIR(nodes[0], env); +// output.append(conditionIR.getIr()); +// int condition; +// if(conditionIR.getReturnCategory() == IRReturnCategory.VOID) { +// condition = llvmenv.getNewLocalVariableReference(); +// output.append(AsmUtil.formatLine(t, llvmenv, "%" + condition + " = i32 0")); +// } else { +// condition = conditionIR.getResultVariable(); +// } +// +// int firstJmp = llvmenv.getNewLocalVariableReference(); +// int secondJmp = llvmenv.getNewLocalVariableReference(); +// int finalJmp = llvmenv.getNewLocalVariableReference(); +// +// // TODO: Need to potentially (probably?) cast the condition to a i1 +// String jmpLine = "br i1 %" + condition + ", label %" + firstJmp; +// if(nodes.length == 2) { +// jmpLine += ", label %" + finalJmp; +// } else if(nodes.length > 2) { +// jmpLine += ", label %" + secondJmp; +// } +// +// output.append(AsmUtil.formatLine(t, llvmenv, jmpLine)); +// output.append(AsmUtil.formatLine(t, llvmenv, firstJmp + ":")); +// output.append(AsmCompiler.getIR(nodes[1], env).getIr()); +// output.append(AsmUtil.formatLine(t, llvmenv, "br label %" + finalJmp)); +// if(nodes.length > 2) { +// output.append(AsmUtil.formatLine(t, llvmenv, secondJmp + ":")); +// output.append(AsmCompiler.getIR(nodes[2], env).getIr()); +// } +// output.append(AsmUtil.formatLine(t, llvmenv, finalJmp + ":")); +// // TODO This shouldn't return void +// return IRDataBuilder.setRawIR(output.toString()).asVoid(); +// } +// +// @Override +// public String getName() { +// return "if"; +// } +// +// @Override +// public Integer[] numArgs() { +// return new Integer[]{2, 3}; +// } +// +// @Override +// public Class[] thrown() { +// return null; +// } +// +// @Override +// public Version since() { +// return LLVMVersion.V0_0_1; +// } +// +// } +} diff --git a/src/main/java/com/laytonsmith/core/functions/asm/DataHandling.java b/src/main/java/com/laytonsmith/core/functions/asm/DataHandling.java new file mode 100644 index 0000000000..83c1f36237 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/functions/asm/DataHandling.java @@ -0,0 +1,92 @@ +package com.laytonsmith.core.functions.asm; + +import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.annotations.api; +import com.laytonsmith.core.ArgumentValidation; +import com.laytonsmith.core.ParseTree; +import com.laytonsmith.core.asm.IRBuilder; +import com.laytonsmith.core.asm.IRData; +import com.laytonsmith.core.asm.IRDataBuilder; +import com.laytonsmith.core.asm.LLVMArgumentValidation; +import com.laytonsmith.core.asm.LLVMEnvironment; +import com.laytonsmith.core.asm.LLVMFunction; +import com.laytonsmith.core.asm.LLVMVersion; +import com.laytonsmith.core.constructs.CClassType; +import com.laytonsmith.core.constructs.IVariable; +import com.laytonsmith.core.constructs.Target; +import com.laytonsmith.core.environments.Environment; +import com.laytonsmith.core.exceptions.CRE.CRECastException; +import com.laytonsmith.core.exceptions.CRE.CREThrowable; +import com.laytonsmith.core.exceptions.ConfigCompileException; + +/** + * + */ +public class DataHandling { + + @api(environments = LLVMEnvironment.class, platform = api.Platforms.COMPILER_LLVM) + public static class assign extends LLVMFunction { + + @Override + public IRData buildIR(IRBuilder builder, Target t, Environment env, ParseTree... nodes) throws ConfigCompileException { + LLVMEnvironment llvmenv = env.getEnv(LLVMEnvironment.class); + int offset; + CClassType type; + String name; + if(nodes.length == 3) { + offset = 1; + if(!(nodes[offset].getData() instanceof IVariable)) { + throw new CRECastException(getName() + " with 3 arguments only accepts an ivariable as the second argument.", t); + } + name = ((IVariable) nodes[offset].getData()).getVariableName(); + type = ArgumentValidation.getClassType(nodes[0].getData(), t); + // TODO: Add duplicate check here, or remove if not needed +// if(list.has(name) && env.getEnv(GlobalEnv.class).GetFlag(GlobalEnv.FLAG_NO_CHECK_DUPLICATE_ASSIGN) == null) { +// if(env.getEnv(GlobalEnv.class).GetFlag(GlobalEnv.FLAG_CLOSURE_WARN_OVERWRITE) != null) { +// MSLog.GetLogger().Log(MSLog.Tags.RUNTIME, LogLevel.ERROR, +// "The variable " + name + " is hiding another value of the" +// + " same name in the main scope.", t); +// } else if(t != list.get(name, t, true, env).getDefinedTarget()) { +// MSLog.GetLogger().Log(MSLog.Tags.RUNTIME, LogLevel.ERROR, name + " was already defined at " +// + list.get(name, t, true, env).getDefinedTarget() + " but is being redefined.", t); +// } +// } + } else { + offset = 0; + if(!(nodes[offset].getData() instanceof IVariable)) { + throw new CRECastException(getName() + " with 2 arguments only accepts an ivariable as the first argument.", t); + } + name = ((IVariable) nodes[offset].getData()).getVariableName(); + type = llvmenv.getVariableType(name); + if(type == null) { + type = CClassType.AUTO; + } + } + + IRData data = LLVMArgumentValidation.getAny(builder, env, nodes[offset + 1], t); + llvmenv.addVariableMapping(name, data.getResultVariable(), type); + return IRDataBuilder.asVoid(); + } + + @Override + public String getName() { + return "assign"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{2, 3}; + } + + @Override + public Class[] thrown() { + return null; + } + + @Override + public Version since() { + return LLVMVersion.V0_0_1; + } + + } +} diff --git a/src/main/java/com/laytonsmith/core/functions/asm/Math.java b/src/main/java/com/laytonsmith/core/functions/asm/Math.java new file mode 100644 index 0000000000..40d88372b1 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/functions/asm/Math.java @@ -0,0 +1,162 @@ +package com.laytonsmith.core.functions.asm; + +import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.annotations.api; +import com.laytonsmith.core.ArgumentValidation; +import com.laytonsmith.core.ParseTree; +import com.laytonsmith.core.asm.AsmCommonLibTemplates; +import com.laytonsmith.core.asm.IRBuilder; +import com.laytonsmith.core.asm.IRData; +import com.laytonsmith.core.asm.IRDataBuilder; +import com.laytonsmith.core.asm.IRType; +import com.laytonsmith.core.asm.LLVMArgumentValidation; +import com.laytonsmith.core.asm.LLVMEnvironment; +import com.laytonsmith.core.asm.LLVMFunction; +import com.laytonsmith.core.asm.LLVMVersion; +import com.laytonsmith.core.compiler.CompilerEnvironment; +import com.laytonsmith.core.constructs.Target; +import com.laytonsmith.core.environments.Environment; +import com.laytonsmith.core.exceptions.CRE.CREThrowable; +import com.laytonsmith.core.exceptions.ConfigCompileException; + +/** + * + */ +public class Math { + @api(environments = LLVMEnvironment.class, platform = api.Platforms.COMPILER_LLVM) + public static class rand extends LLVMFunction { + + @Override + public void addStartupCode(IRBuilder builder, Environment startupEnv, Target t) { + LLVMEnvironment startupllvmenv = startupEnv.getEnv(LLVMEnvironment.class); + int callTime = startupllvmenv.getNewLocalVariableReference(IRType.INTEGER32); + int rand1 = startupllvmenv.getNewLocalVariableReference(IRType.INTEGER32); + startupllvmenv.addGlobalDeclaration(AsmCommonLibTemplates.SRAND, startupEnv); + startupllvmenv.addGlobalDeclaration(AsmCommonLibTemplates.TIME, startupEnv); + builder.appendLines(t, + "%" + callTime + " = call i32 bitcast (i32 (...)* @time to i32 (i8*)*)(i8* null)", + "call void @srand(i32 %" + callTime + ")", + // burn one rand, so the second ("first" from the user perspective) + // gets a bit of the bias out of the way + "%" + rand1 + " = call i32 @rand()" + ); + } + + @Override + public IRData buildIR(IRBuilder builder, Target t, Environment env, ParseTree... nodes) throws ConfigCompileException { + LLVMEnvironment llvmenv = env.getEnv(LLVMEnvironment.class); + CompilerEnvironment cEnv = env.getEnv(CompilerEnvironment.class); + llvmenv.addGlobalDeclaration(AsmCommonLibTemplates.RAND, env); + + String RAND_MAX = "2147483647"; // 2**31-1 + int callRand; + String conversionInstruction; + // callRand is the output of this block, for windows this is the output of the or + if(cEnv.getTargetOS().isWindows()) { + // On Windows, RAND_MAX is 2**15-1, and we just generally want much higher granularity than + // that. In order to match POSIX's 2**31-1 range, we do rand() | rand() << 16, and use that + // as our random number. + int callRand1 = llvmenv.getNewLocalVariableReference(IRType.INTEGER32); + int callRand2 = llvmenv.getNewLocalVariableReference(IRType.INTEGER32); + int shl = llvmenv.getNewLocalVariableReference(IRType.INTEGER32); + callRand = llvmenv.getNewLocalVariableReference(IRType.INTEGER32); + builder.appendLine(t, "%" + callRand1 + " = call i32 @rand()"); + builder.appendLine(t, "%" + callRand2 + " = call i32 @rand()"); + builder.appendLine(t, "%" + shl + " = shl i32 %" + callRand2 + ", 16"); + builder.appendLine(t, "%" + callRand + " = or i32 %" + callRand1 + ", %" + shl); + conversionInstruction = "uitofp"; + } else { + callRand = llvmenv.getNewLocalVariableReference(IRType.INTEGER32); + builder.appendLine(t, "%" + callRand + " = call i32 @rand()"); + conversionInstruction = "sitofp"; + } + // First, generate our random number, scaled to 0-1 + + int sitofp = llvmenv.getNewLocalVariableReference(IRType.INTEGER32); + int fdiv = llvmenv.getNewLocalVariableReference(IRType.DOUBLE); // returned value for 0 arg + + builder.appendLine(t, "%" + sitofp + " = " + conversionInstruction + " i32 %" + callRand + " to double"); + builder.appendLine(t, "%" + fdiv + " = fdiv double %" + sitofp + ", " + RAND_MAX + ".0"); + + if(nodes.length == 0) { + return IRDataBuilder.setReturnVariable(fdiv, IRType.DOUBLE); + } else { + String min; + String max; + if(nodes[0].isConst()) { + long vMax = ArgumentValidation.getInt(nodes[0].getData(), t); + if(vMax > Integer.MAX_VALUE) { + throw new ConfigCompileException("max and min must be below int max, defined as " + + Integer.MAX_VALUE, t); + } + } else { + // TODO: Write out code to runtime check if the value is correct + } + if(nodes.length == 1) { + int minReference = llvmenv.getNewLocalVariableReference(IRType.INTEGER32); + int loadReference = llvmenv.getNewLocalVariableReference(IRType.INTEGER32); + builder.generator(t, env).allocaStoreAndLoad(minReference, IRType.INTEGER32, "i32 0", loadReference); + min = "%" + loadReference; + IRData dmax = LLVMArgumentValidation.getInt32(builder, env, nodes[0], t); + max = dmax.getReference(); + } else { + IRData dmin = LLVMArgumentValidation.getInt32(builder, env, nodes[0], t); + IRData dmax = LLVMArgumentValidation.getInt32(builder, env, nodes[1], t); + int minReference = llvmenv.getNewLocalVariableReference(IRType.INTEGER32); + int loadReference = llvmenv.getNewLocalVariableReference(IRType.INTEGER32); + builder.generator(t, env).allocaStoreAndLoad(minReference, IRType.INTEGER32, dmin.getReference(), loadReference); + min = "%" + loadReference; + max = dmax.getReference(); + + if(nodes[1].isConst()) { + long vMax = ArgumentValidation.getInt(nodes[1].getData(), t); + if(vMax > Integer.MAX_VALUE) { + throw new ConfigCompileException("max and min must be below int max, defined as " + + Integer.MAX_VALUE, t); + } + } else { + // TODO: Write out code to runtime check if the value is correct + } + } + +// int allocaRandScaled = llvmenv.getNewLocalVariableReference(); +// int allocaMin = llvmenv.getNewLocalVariableReference(); + int fmul = llvmenv.getNewLocalVariableReference(IRType.DOUBLE); + int fptosi = llvmenv.getNewLocalVariableReference(IRType.DOUBLE); + int range = llvmenv.getNewLocalVariableReference(IRType.INTEGER32); + int srem = llvmenv.getNewLocalVariableReference(IRType.INTEGER32); + int add = llvmenv.getNewLocalVariableReference(IRType.INTEGER32); + +// builder.generator(t, env).allocaAndStore(allocaMin, IRType.INTEGER32, min); + + // Scale the double into int max + builder.appendLine(t, "%" + fmul + " = fmul double %" + fdiv + ", 2147483647.0"); + builder.appendLine(t, "%" + fptosi + " = fptosi double %" + fmul + " to i32"); + builder.appendLine(t, "%" + range + " = sub nsw " + max + ", " + min); + builder.appendLine(t, "%" + srem + " = srem i32 %" + fptosi + ", %" + range); + builder.appendLine(t, "%" + add + " = add nsw i32 %" + srem + ", " + min); + return IRDataBuilder.setReturnVariable(add, IRType.INTEGER32); + } + } + + @Override + public String getName() { + return "rand"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{0, 1, 2}; + } + + @Override + public Class[] thrown() { + return null; + } + + @Override + public Version since() { + return LLVMVersion.V0_0_1; + } + } +} diff --git a/src/main/java/com/laytonsmith/core/functions/asm/Meta.java b/src/main/java/com/laytonsmith/core/functions/asm/Meta.java new file mode 100644 index 0000000000..16f61e6483 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/functions/asm/Meta.java @@ -0,0 +1,52 @@ +package com.laytonsmith.core.functions.asm; + +import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.annotations.api; +import com.laytonsmith.core.ParseTree; +import com.laytonsmith.core.asm.IRBuilder; +import com.laytonsmith.core.asm.IRData; +import com.laytonsmith.core.asm.IRDataBuilder; +import com.laytonsmith.core.asm.LLVMEnvironment; +import com.laytonsmith.core.asm.LLVMFunction; +import com.laytonsmith.core.asm.LLVMVersion; +import com.laytonsmith.core.constructs.Target; +import com.laytonsmith.core.environments.Environment; +import com.laytonsmith.core.exceptions.CRE.CREThrowable; +import com.laytonsmith.core.exceptions.ConfigCompileException; + +/** + * + */ +public class Meta { + @api(environments = LLVMEnvironment.class, platform = api.Platforms.COMPILER_LLVM) + public static class noop extends LLVMFunction { + + @Override + public IRData buildIR(IRBuilder builder, Target t, Environment env, ParseTree... nodes) + throws ConfigCompileException { + builder.appendLine(t, "add i1 0, 0 ; noop()"); + return IRDataBuilder.asVoid(); + } + + @Override + public String getName() { + return "noop"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{0}; + } + + @Override + public Class[] thrown() { + return null; + } + + @Override + public Version since() { + return LLVMVersion.V0_0_1; + } + + } +} diff --git a/src/main/java/com/laytonsmith/core/functions/bash/BashFunction.java b/src/main/java/com/laytonsmith/core/functions/bash/BashFunction.java deleted file mode 100644 index 3120c66a83..0000000000 --- a/src/main/java/com/laytonsmith/core/functions/bash/BashFunction.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.laytonsmith.core.functions.bash; - -import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery; -import com.laytonsmith.annotations.core; -import com.laytonsmith.core.Documentation; -import com.laytonsmith.core.functions.CompiledFunction; -import com.laytonsmith.core.functions.FunctionBase; -import com.laytonsmith.core.snapins.PackagePermission; -import java.net.URL; - -/** - * This is a marker interface to make Bash functions separate. - * - */ -public abstract class BashFunction implements FunctionBase, CompiledFunction, Documentation { - - @Override - public boolean appearInDocumentation() { - return true; - } - - @Override - public PackagePermission getPermission() { - return PackagePermission.NO_PERMISSIONS_NEEDED; - } - - @Override - public URL getSourceJar() { - return ClassDiscovery.GetClassContainer(this.getClass()); - } - - private static final Class[] EMPTY_CLASS = new Class[0]; - - @Override - public Class[] seeAlso() { - return EMPTY_CLASS; - } - - @Override - public final boolean isCore() { - Class c = this.getClass(); - do{ - if(c.getAnnotation(core.class) != null){ - return true; - } - c = c.getDeclaringClass(); - } while(c != null); - return false; - } - -} diff --git a/src/main/java/com/laytonsmith/core/functions/bash/BashPlatformResolver.java b/src/main/java/com/laytonsmith/core/functions/bash/BashPlatformResolver.java deleted file mode 100644 index 5ac60cb9a6..0000000000 --- a/src/main/java/com/laytonsmith/core/functions/bash/BashPlatformResolver.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.laytonsmith.core.functions.bash; - -import com.laytonsmith.core.PlatformResolver; -import com.laytonsmith.core.constructs.CArray; -import com.laytonsmith.core.constructs.CString; -import com.laytonsmith.core.constructs.Construct; - -/** - * - * - */ -public class BashPlatformResolver implements PlatformResolver{ - - @Override - public String outputConstant(Construct c) { - if(c instanceof CString){ - return "\"" + c.val() + "\""; - } else if(c instanceof CArray){ - throw new RuntimeException("Not implemented yet"); - } else { - return c.val(); - } - } - -} diff --git a/src/main/java/com/laytonsmith/core/functions/bash/BasicLogic.java b/src/main/java/com/laytonsmith/core/functions/bash/BasicLogic.java deleted file mode 100644 index 0471be66a4..0000000000 --- a/src/main/java/com/laytonsmith/core/functions/bash/BasicLogic.java +++ /dev/null @@ -1,115 +0,0 @@ -package com.laytonsmith.core.functions.bash; - -import com.laytonsmith.annotations.api; -import com.laytonsmith.core.CHVersion; -import com.laytonsmith.core.constructs.Target; - -/** - * - */ -public class BasicLogic { - public static String docs(){ - return "Contains basic logic functions for bash"; - } - @api(platform=api.Platforms.COMPILER_BASH) - public static class _if extends BashFunction{ - - @Override - public String getName() { - return "ifelse"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{2, 3}; - } - - @Override - public String docs() { - return "void {condition, ifcode, elsecode} Runs the ifcode if condition is true, otherwise, " - + "runs the false code. Note that nothing is ever returned."; - } - - @Override - public String compile(Target t, String ... args) { - return new _ifelse().compile(t, args); - } - - @Override - public CHVersion since() { - return CHVersion.V3_3_1; - } - - } - - @api(platform=api.Platforms.COMPILER_BASH) - public static class _ifelse extends BashFunction{ - - @Override - public String getName() { - return "ifelse"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{2, 3}; - } - - @Override - public String docs() { - return "void {condition, ifcode, elsecode} Runs the ifcode if condition is true, otherwise, " - + "runs the false code. Note that nothing is ever returned."; - } - - @Override - public String compile(Target t, String ... args) { - String s = "if [ " + args[0] + " ]; then\n" - + args[1] + "\n"; - - if(args.length == 3){ - s += "else\n"; - s += args[2] + "\n"; - } - - s += "fi\n"; - - return s; - } - - @Override - public CHVersion since() { - return CHVersion.V3_3_1; - } - - } - - @api(platform=api.Platforms.COMPILER_BASH) - public static class equals extends BashFunction{ - - @Override - public String getName() { - return "equals"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{2}; - } - - @Override - public String docs() { - return "boolean {arg1, arg2} Compares two arguments for equality"; - } - - @Override - public String compile(Target t, String... args) { - return args[0] + " == " + args[1]; - } - - @Override - public CHVersion since() { - return CHVersion.V3_3_1; - } - - } -} diff --git a/src/main/java/com/laytonsmith/core/functions/bash/Compiler.java b/src/main/java/com/laytonsmith/core/functions/bash/Compiler.java deleted file mode 100644 index b59e3a640b..0000000000 --- a/src/main/java/com/laytonsmith/core/functions/bash/Compiler.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.laytonsmith.core.functions.bash; - -import com.laytonsmith.annotations.api; -import com.laytonsmith.core.CHVersion; -import com.laytonsmith.core.constructs.Target; - -/** - * - * - */ -public class Compiler { - - public static String docs(){ - return "Bash compiler internal functions"; - } - - @api(platform=api.Platforms.COMPILER_BASH) - public static class dyn extends BashFunction{ - - @Override - public String getName() { - return "dyn"; - } - - @Override - public Integer[] numArgs() { - return new Integer[]{0, 1}; - } - - @Override - public String docs() { - return "mixed {p} "; - } - - @Override - public String compile(Target t, String... args) { - if(args.length == 0){ - return "0"; - } - return args[0]; - } - - @Override - public CHVersion since() { - return CHVersion.V3_3_1; - } - - } -} diff --git a/src/main/java/com/laytonsmith/core/functions/bash/DataHandling.java b/src/main/java/com/laytonsmith/core/functions/bash/DataHandling.java deleted file mode 100644 index fbc55fada6..0000000000 --- a/src/main/java/com/laytonsmith/core/functions/bash/DataHandling.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.laytonsmith.core.functions.bash; - -/** - * - * - */ -public class DataHandling { - -} diff --git a/src/main/java/com/laytonsmith/core/mobjects/MObject.java b/src/main/java/com/laytonsmith/core/mobjects/MObject.java index 9af241a9c4..1083d92c30 100644 --- a/src/main/java/com/laytonsmith/core/mobjects/MObject.java +++ b/src/main/java/com/laytonsmith/core/mobjects/MObject.java @@ -1,4 +1,3 @@ - package com.laytonsmith.core.mobjects; import com.laytonsmith.annotations.mobject; @@ -8,9 +7,9 @@ */ @mobject("Object") public class MObject { - + @Override - public String toString(){ + public String toString() { return super.toString(); } } diff --git a/src/main/java/com/laytonsmith/core/mobjects/MObjects.java b/src/main/java/com/laytonsmith/core/mobjects/MObjects.java index a99ca141ef..33ec765f49 100644 --- a/src/main/java/com/laytonsmith/core/mobjects/MObjects.java +++ b/src/main/java/com/laytonsmith/core/mobjects/MObjects.java @@ -5,17 +5,18 @@ /** * This is a utility class that contains methods for manipulating MObjects. */ -public class MObjects { +public final class MObjects { private MObjects() { } - + /** * Returns the name of the object, as provided by the annotation. + * * @param object - * @return + * @return */ - public static String getObjectName(MObject object){ + public static String getObjectName(MObject object) { return object.getClass().getAnnotation(mobject.class).value(); } } diff --git a/src/main/java/com/laytonsmith/core/natives/interfaces/AbstractMixed.java b/src/main/java/com/laytonsmith/core/natives/interfaces/AbstractMixed.java new file mode 100644 index 0000000000..67150e8dd5 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/natives/interfaces/AbstractMixed.java @@ -0,0 +1,107 @@ +package com.laytonsmith.core.natives.interfaces; + +import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery; +import com.laytonsmith.annotations.seealso; +import com.laytonsmith.core.Documentation; +import com.laytonsmith.core.constructs.CClassType; +import com.laytonsmith.core.constructs.Construct; +import com.laytonsmith.core.constructs.Target; +import com.laytonsmith.core.objects.AccessModifier; +import com.laytonsmith.core.objects.ObjectModifier; +import java.net.URL; +import java.util.EnumSet; +import java.util.Set; + +/** + * Provides a basic Mixed implementation. This assumes that the object is a public, top level object. If it's also + * a class, instead use AbstractMixedClass. + */ +public abstract class AbstractMixed implements Mixed { + + private Target t = Target.UNKNOWN; + + @Override + public Mixed clone() throws CloneNotSupportedException { + return (Mixed) super.clone(); + } + + @Override + public void setTarget(Target target) { + t = target; + } + + @Override + public Target getTarget() { + return t; + } + + @Override + public String val() { + return this.toString(); + } + + @Override + public String getName() { + return typeof().getName(); + } + + @Override + public Set getObjectModifiers() { + return EnumSet.noneOf(ObjectModifier.class); + } + + @Override + public AccessModifier getAccessModifier() { + return AccessModifier.PUBLIC; + } + + @Override + public CClassType getContainingClass() { + return null; + } + + @Override + public boolean isInstanceOf(CClassType type) { + if(type.getNativeType() != null) { + return type.getNativeType().isAssignableFrom(this.getClass()); + } + return Construct.isInstanceof(this, type); + } + + @Override + public boolean isInstanceOf(Class type) { + return type.isAssignableFrom(this.getClass()); + } + + /** + * Returns the typeof this Mixed, as a CClassType. Not all constructs are annotated with the @typeof annotation, + * in which case this is considered a "private" object, which can't be directly accessed via MethodScript. In this + * case, an IllegalArgumentException is thrown. + * + * This method may be overridden in special cases, such as dynamic types, but for most types, this + * @return + * @throws IllegalArgumentException If the class isn't public facing. + */ + @Override + public CClassType typeof() { + return Construct.typeof(this); + } + + @Override + public URL getSourceJar() { + return ClassDiscovery.GetClassContainer(this.getClass()); + } + + private static final Class[] EMPTY_CLASS = new Class[0]; + + @Override + @SuppressWarnings("unchecked") + public Class[] seeAlso() { + seealso seealso = this.getClass().getAnnotation(seealso.class); + if(seealso != null) { + return seealso.value(); + } else { + return EMPTY_CLASS; + } + } +} diff --git a/src/main/java/com/laytonsmith/core/natives/interfaces/AbstractMixedClass.java b/src/main/java/com/laytonsmith/core/natives/interfaces/AbstractMixedClass.java new file mode 100644 index 0000000000..4e11544780 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/natives/interfaces/AbstractMixedClass.java @@ -0,0 +1,16 @@ +package com.laytonsmith.core.natives.interfaces; + +import com.laytonsmith.core.objects.ObjectType; + +/** + * Provides a basic implementation for Mixed. This assumes the object is a class, along with the additional assumptions + * provided by AbstractMixed. + */ +public abstract class AbstractMixedClass extends AbstractMixed { + + @Override + public ObjectType getObjectType() { + return ObjectType.CLASS; + } + +} diff --git a/src/main/java/com/laytonsmith/core/natives/interfaces/AbstractMixedInterfaceRunner.java b/src/main/java/com/laytonsmith/core/natives/interfaces/AbstractMixedInterfaceRunner.java new file mode 100644 index 0000000000..658ba2c1a0 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/natives/interfaces/AbstractMixedInterfaceRunner.java @@ -0,0 +1,132 @@ +package com.laytonsmith.core.natives.interfaces; + +import com.laytonsmith.core.objects.ObjectType; +import com.laytonsmith.core.objects.ObjectModifier; +import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery; +import com.laytonsmith.PureUtilities.Common.Annotations.InterfaceRunnerFor; +import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.annotations.typeof; +import com.laytonsmith.core.Documentation; +import com.laytonsmith.core.constructs.CClassType; +import com.laytonsmith.core.constructs.Construct; +import com.laytonsmith.core.constructs.Target; +import com.laytonsmith.core.objects.AccessModifier; +import java.net.URL; +import java.util.EnumSet; +import java.util.Set; + +/** + * + * @author cailin + */ +public abstract class AbstractMixedInterfaceRunner implements MixedInterfaceRunner { + + /** + * This returns the class file to which this InterfaceRunner is attached. + * + * @return + */ + @SuppressWarnings("unchecked") + public Class getSponsorClass() { + return (Class) this.getClass().getAnnotation(InterfaceRunnerFor.class).value(); + } + + @Override + public URL getSourceJar() { + return ClassDiscovery.GetClassContainer(getSponsorClass()); + } + + @Override + public String getName() { + return ClassDiscovery.GetClassAnnotation(getSponsorClass(), typeof.class).value(); + } + + @Override + public String docs() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Version since() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public CClassType[] getInterfaces() { + return new CClassType[0]; + } + + @Override + public CClassType[] getSuperclasses() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public ObjectType getObjectType() { + return ObjectType.INTERFACE; + } + + @Override + public Set getObjectModifiers() { + return EnumSet.noneOf(ObjectModifier.class); + } + + @Override + public AccessModifier getAccessModifier() { + return AccessModifier.PUBLIC; + } + + @Override + public Mixed clone() throws CloneNotSupportedException { + throw new CloneNotSupportedException(); + } + + @Override + public void setTarget(Target target) { + throw new UnsupportedOperationException(); + } + + @Override + public Target getTarget() { + throw new UnsupportedOperationException(); + } + + @Override + public String val() { + throw new UnsupportedOperationException(); + } + + @Override + public Class[] seeAlso() { + return new Class[]{}; + } + + @Override + public CClassType getContainingClass() { + return null; + } + + /** + * Returns the typeof this Construct, as a string. Not all constructs are annotated with the @typeof annotation, in + * which case this is considered a "private" object, which can't be directly accessed via MethodScript. In this + * case, an IllegalArgumentException is thrown. + * + * @return + * @throws IllegalArgumentException If the class isn't public facing. + */ + @Override + public final CClassType typeof() { + return Construct.typeof(this); + } + + @Override + public boolean isInstanceOf(CClassType type) { + return Construct.isInstanceof(this, type); + } + + @Override + public boolean isInstanceOf(Class type) { + return Construct.isInstanceof(this, type); + } + +} diff --git a/src/main/java/com/laytonsmith/core/natives/interfaces/ArrayAccess.java b/src/main/java/com/laytonsmith/core/natives/interfaces/ArrayAccess.java index 34e147e5fd..b9cdf4caa3 100644 --- a/src/main/java/com/laytonsmith/core/natives/interfaces/ArrayAccess.java +++ b/src/main/java/com/laytonsmith/core/natives/interfaces/ArrayAccess.java @@ -1,195 +1,107 @@ - - package com.laytonsmith.core.natives.interfaces; -import com.laytonsmith.core.constructs.Construct; +import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.annotations.typeof; +import com.laytonsmith.core.constructs.CClassType; import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.exceptions.ConfigRuntimeException; -import java.util.Arrays; import java.util.Set; /** * Things that implement this can be accessed like an array, with array_get, or []. */ -public interface ArrayAccess extends Mixed, Sizable { - /** - * Return the mixed at this location. This should throw an exception if - * the index does not exist. This method will not be called if - * {@link #isAssociative()} returns false. - * @param index +@typeof("ms.lang.ArrayAccess") +public interface ArrayAccess extends Booleanish { + + @SuppressWarnings("FieldNameHidesFieldInSuperclass") + public static final CClassType TYPE = CClassType.get(ArrayAccess.class); + + /** + * Return the mixed at this location. This should throw an exception if the index does not exist. This method will + * not be called if {@link #isAssociative()} returns false. + * + * @param index * @param t - * @return - */ - public Construct get(String index, Target t) throws ConfigRuntimeException; - + * @return + */ + public Mixed get(String index, Target t) throws ConfigRuntimeException; + /** - * Returns the mixed at this location. This should throw an exception if - * the index does not exist. This method will not be called if - * {@link #isAssociative()} returns true. + * Returns the mixed at this location. This should throw an exception if the index does not exist. This method will + * not be called if {@link #isAssociative()} returns true. + * * @param index * @param t * @return - * @throws ConfigRuntimeException + * @throws ConfigRuntimeException */ - public Construct get(int index, Target t) throws ConfigRuntimeException; - + public Mixed get(int index, Target t) throws ConfigRuntimeException; + /** - * Returns the mixed at this location. This should throw an exception if - * the index does not exist. This method may be called whether or not - * it isAssociative returns true. + * Returns the mixed at this location. This should throw an exception if the index does not exist. This method may + * be called whether or not it isAssociative returns true. + * * @param index * @param t * @return - * @throws ConfigRuntimeException + * @throws ConfigRuntimeException */ - public Construct get(Construct index, Target t) throws ConfigRuntimeException; - + public Mixed get(Mixed index, Target t) throws ConfigRuntimeException; + /** - * If {@link #isAssociative()} returns true, this should return a set of all - * keys. If {@link #isAssociative()} returns false, this method will not be - * called. - * @return + * If {@link #isAssociative()} returns true, this should return a set of all keys. If {@link #isAssociative()} + * returns false, this method will not be called. + * + * @return */ - public Set keySet(); - - /** - * Return the size of the array - * @return - */ - @Override - public long size(); - + public Set keySet(); + /** - * Unlike {@link #canBeAssociative()}, this is a runtime flag. If the underlying - * object is associative (that is, it is an unordered, non numeric key set), this should - * return true. If this is true, then {@link #get(java.lang.String, com.laytonsmith.core.constructs.Target)} - * will not be called, and {@link #get(int, com.laytonsmith.core.constructs.Target)} will be called - * instead. If this is false, the opposite will occur. - * @return + * Unlike {@link #canBeAssociative()}, this is a runtime flag. If the underlying object is associative (that is, it + * is an unordered, non numeric key set), this should return true. If this is true, then + * {@link #get(java.lang.String, com.laytonsmith.core.constructs.Target)} will not be called, and + * {@link #get(int, com.laytonsmith.core.constructs.Target)} will be called instead. If this is false, the opposite + * will occur. + * + * @return */ public boolean isAssociative(); - - /** - * Just because it is accessible as an array doesn't mean it will be associative. For optimiziation purposes, it - * may be possible to check at compile time if the code is attempting to send a non-integral index, - * in which case we can throw a compile error. This is a compile time flag, and is not used during - * the runtime, just during compilation. - * @return - */ - public boolean canBeAssociative(); - - /** - * Returns a slice at the specified location. Should throw an exception if an element in - * the range doesn't exist. - * @param begin - * @param end - * @param t - * @return - */ - public Construct slice(int begin, int end, Target t); - + /** - * This class contains iteration information for the ArrayAccess object - * as it is being iterated. This assumes that the object being iterated - * is not associative. Associative arrays have far simpler handling, - * and can therefore skip the handling needed for non-associative arrays. + * Just because it is accessible as an array doesn't mean it will be associative. For optimization purposes, it may + * be possible to check at compile time if the code is attempting to send a non-integral index, in which case we can + * throw a compile error. This is a compile time flag, and is not used during the runtime, just during compilation. + * + * @return + */ + public boolean canBeAssociative(); + + /** + * Returns a slice at the specified location. Should throw an exception if an element in the range doesn't exist. + * The range is inclusive. + * + * @implNote In general, MethodScript supports undefined begin and end slices, as well as reverse slices. For + * CArray type only, this is fully covered, but for other slice types, only negative slice numbers are converted, + * and subclasses must handle both forward and reverse slices if applicable. A good way to handle that is as such: + *

+	 * int start = Math.min(begin, end);
+	 * int stop = Math.max(begin, end);
+	 * int step = (begin <= end) ? 1 : -1;
+	 * for(int i = start; i != stop; i += step) { ... }
+	 * 
+ * + * @param begin + * @param end + * @param t + * @return */ - public static class ArrayAccessIterator { - private final ArrayAccess array; - private int current = 0; - private int[] blacklist = new int[]{-1}; - private int blacklistSize = 0; - - /** - * Creates a new ArrayAccessIterator. If the array is associative, - * a RuntimeException is thrown, since associative arrays have - * far simpler handling, and should not use this mechanism. - * @param array - */ - public ArrayAccessIterator(ArrayAccess array){ - if(array.isAssociative()){ - throw new RuntimeException(); - } - this.array = array; - } - - /** - * Returns the index of the currently iterated object. - * @return - */ - public int getCurrent(){ - return current; - } - - /** - * Decrements the current counter. This operation is used when the current - * item, or an item before the current item is removed. - */ - public void decrementCurrent(){ - --current; - } - - /** - * Increments the current counter. This operation is used when a new - * item is inserted before or at the current item. It is also used at the - * end of the loop. - */ - public void incrementCurrent(){ - ++current; - } - - /** - * Increments all the values in the blacklist. This is used when a new - * item is inserted into the array, after the current index. - * @param from The value to search from. Any values after this are incremented, - * and values before it are not. - */ - public void incrementBlacklistAfter(int from){ - for(int i = 0; i < blacklist.length; i++){ - if(blacklist[i] > from){ - blacklist[i]++; - } - } - } - - /** - * Adds a value to the blacklist. This is used when a value is added after - * the current index. This value will not be iterated in the future. - * @param index The index to add to the blacklist. - */ - public void addToBlacklist(int index){ - if(blacklistSize == blacklist.length){ - int[] bl = new int[blacklist.length * 2]; - Arrays.fill(bl, -1); - System.arraycopy(blacklist, 0, bl, 0, blacklist.length); - blacklist = bl; - } - blacklist[blacklistSize] = index; - blacklistSize++; - } - - /** - * Checks through the blacklist, and returns true if this index - * is blacklisted. If so, this value should be skipped in the iteration. - * @param index The index to check. - * @return True if this value should be skipped. - */ - public boolean isBlacklisted(int index){ - for(int v : blacklist){ - if(v == index){ - return true; - } - } - return false; - } - - /** - * Gets the underlying ArrayAccess object. - * @return - */ - public ArrayAccess underlyingArray(){ - return array; - } - - } + public Mixed slice(int begin, int end, Target t); + + + @Override + public String docs(); + + @Override + public Version since(); + } diff --git a/src/main/java/com/laytonsmith/core/natives/interfaces/ArrayAccessRunner.java b/src/main/java/com/laytonsmith/core/natives/interfaces/ArrayAccessRunner.java new file mode 100644 index 0000000000..c9c09373dd --- /dev/null +++ b/src/main/java/com/laytonsmith/core/natives/interfaces/ArrayAccessRunner.java @@ -0,0 +1,35 @@ +package com.laytonsmith.core.natives.interfaces; + +import com.laytonsmith.PureUtilities.Common.Annotations.InterfaceRunnerFor; +import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.core.MSVersion; +import com.laytonsmith.core.constructs.CClassType; + +/** + * + * @author cailin + */ +@InterfaceRunnerFor(ArrayAccess.class) +public class ArrayAccessRunner extends AbstractMixedInterfaceRunner { + + @Override + public String docs() { + return "Provides access to an object using the square bracket notation."; + } + + @Override + public Version since() { + return MSVersion.V3_3_1; + } + + @Override + public CClassType[] getSuperclasses() { + return new CClassType[]{Mixed.TYPE}; + } + + @Override + public CClassType[] getInterfaces() { + return CClassType.EMPTY_CLASS_ARRAY; + } + +} diff --git a/src/main/java/com/laytonsmith/core/natives/interfaces/ArrayAccessSet.java b/src/main/java/com/laytonsmith/core/natives/interfaces/ArrayAccessSet.java new file mode 100644 index 0000000000..1fa79ea07b --- /dev/null +++ b/src/main/java/com/laytonsmith/core/natives/interfaces/ArrayAccessSet.java @@ -0,0 +1,28 @@ +package com.laytonsmith.core.natives.interfaces; + +import com.laytonsmith.annotations.typeof; +import com.laytonsmith.core.constructs.CClassType; +import com.laytonsmith.core.constructs.Target; + +/** + * Things that implement this can set the value via square bracket notation. The generic type provided is the index + * type. IMPORTANT NOTE: If CNull is passed in, that will be converted to a Java null before calling the set method, + * since CNull won't extend whatever type T you pass in here, but is a valid input parameter (in general). + * + * @author Cailin + */ +@typeof("ms.lang.ArrayAccessSet") +public interface ArrayAccessSet extends Mixed { + + @SuppressWarnings("FieldNameHidesFieldInSuperclass") + public static final CClassType TYPE = CClassType.get(ArrayAccessSet.class); + + /** + * Sets the value at the specified index in the object. + * + * @param index The zero-based index. + * @param value The value to set. + * @param t The code target. + */ + public void set(Mixed index, Mixed value, Target t); +} diff --git a/src/main/java/com/laytonsmith/core/natives/interfaces/ArrayAccessSetRunner.java b/src/main/java/com/laytonsmith/core/natives/interfaces/ArrayAccessSetRunner.java new file mode 100644 index 0000000000..211c0150a9 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/natives/interfaces/ArrayAccessSetRunner.java @@ -0,0 +1,34 @@ +package com.laytonsmith.core.natives.interfaces; + +import com.laytonsmith.PureUtilities.Common.Annotations.InterfaceRunnerFor; +import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.core.MSVersion; +import com.laytonsmith.core.constructs.CClassType; + +/** + * + * @author Cailin + */ +@InterfaceRunnerFor(ArrayAccessSet.class) +public class ArrayAccessSetRunner extends AbstractMixedInterfaceRunner { + + @Override + public String docs() { + return "Provides write access to an object using the square bracket notation."; + } + + @Override + public Version since() { + return MSVersion.V3_3_5; + } + + @Override + public CClassType[] getSuperclasses() { + return new CClassType[]{Mixed.TYPE}; + } + + @Override + public CClassType[] getInterfaces() { + return CClassType.EMPTY_CLASS_ARRAY; + } +} diff --git a/src/main/java/com/laytonsmith/core/natives/interfaces/Booleanish.java b/src/main/java/com/laytonsmith/core/natives/interfaces/Booleanish.java new file mode 100644 index 0000000000..8074cdd2ab --- /dev/null +++ b/src/main/java/com/laytonsmith/core/natives/interfaces/Booleanish.java @@ -0,0 +1,33 @@ +package com.laytonsmith.core.natives.interfaces; + +import com.laytonsmith.annotations.typeof; +import com.laytonsmith.core.ArgumentValidation; +import com.laytonsmith.core.constructs.CClassType; +import com.laytonsmith.core.constructs.Target; + + +/** + * A value that is Booleanish is a non-boolean value, that can be converted to Boolean. + */ +@typeof("ms.lang.Booleanish") +public interface Booleanish extends Mixed { + + @SuppressWarnings("FieldNameHidesFieldInSuperclass") + public static final CClassType TYPE = CClassType.get(Booleanish.class); + + /** + * Returns true if this value is a trueish value. Each implementation is free to define this as they wish. In + * general, code that supports Booleanish values should not use this method directly, use + * {@link ArgumentValidation#getBoolean(com.laytonsmith.core.natives.interfaces.Mixed, + * com.laytonsmith.core.constructs.Target)}, which ensures that the error message, if this is not an actual + * Booleanish value, is standardized. In general, methods should not accept a Booleanish value (with some critical + * exceptions, such as if(), for(), etc) as in the future, this will prevent functions from being fully strongly + * typed in strict mode. In non-strict mode (or strict mode with the auto keyword) Booleanish types will be cross + * cast to a boolean first anyways, so there is no point in accepting Booleanish values. + * + * @param t The code target, in case there are errors that are thrown, the correct target can be provided in the + * error. + * @return True if the value is trueish, false if it is falseish. + */ + boolean getBooleanValue(Target t); +} diff --git a/src/main/java/com/laytonsmith/core/natives/interfaces/BooleanishRunner.java b/src/main/java/com/laytonsmith/core/natives/interfaces/BooleanishRunner.java new file mode 100644 index 0000000000..0d81268be2 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/natives/interfaces/BooleanishRunner.java @@ -0,0 +1,43 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.laytonsmith.core.natives.interfaces; + +import com.laytonsmith.core.objects.ObjectType; +import com.laytonsmith.PureUtilities.Common.Annotations.InterfaceRunnerFor; +import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.core.MSVersion; +import com.laytonsmith.core.constructs.CClassType; + +/** + * + */ +@InterfaceRunnerFor(Booleanish.class) +public class BooleanishRunner extends AbstractMixedInterfaceRunner { + @Override + public String docs() { + return "A value that is Booleanish is a non-boolean value, that can be converted to boolean."; + } + + @Override + public Version since() { + return MSVersion.V3_3_4; + } + + @Override + public CClassType[] getSuperclasses() { + return new CClassType[]{Mixed.TYPE}; + } + + @Override + public CClassType[] getInterfaces() { + return CClassType.EMPTY_CLASS_ARRAY; + } + + @Override + public ObjectType getObjectType() { + return ObjectType.INTERFACE; + } +} diff --git a/src/main/java/com/laytonsmith/core/natives/interfaces/Callable.java b/src/main/java/com/laytonsmith/core/natives/interfaces/Callable.java new file mode 100644 index 0000000000..392d87e075 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/natives/interfaces/Callable.java @@ -0,0 +1,44 @@ +package com.laytonsmith.core.natives.interfaces; + +import com.laytonsmith.annotations.typeof; +import com.laytonsmith.core.constructs.CClassType; +import com.laytonsmith.core.constructs.Target; +import com.laytonsmith.core.environments.Environment; +import com.laytonsmith.core.exceptions.CancelCommandException; +import com.laytonsmith.core.exceptions.ConfigRuntimeException; +import com.laytonsmith.core.exceptions.ProgramFlowManipulationException; + +/** + * A Callable represents something that is executable. + */ +@typeof("ms.lang.Callable") +public interface Callable extends Mixed { + + @SuppressWarnings("FieldNameHidesFieldInSuperclass") + public static final CClassType TYPE = CClassType.get(Callable.class); + + /** + * Executes the callable, giving it the supplied arguments. {@code values} may be null, which means that no + * arguments are being sent. + * + * LoopManipulationExceptions will never bubble up past this point, because they are never allowed, so they are + * handled automatically, but other ProgramFlowManipulationExceptions will, . ConfigRuntimeExceptions will also + * bubble up past this, since an execution mechanism may need to do custom handling. + * + * @param environment + * @param values The values to be passed to the callable + * @param t + * @return The return value of the callable, or VOID if nothing was returned + * @throws ConfigRuntimeException If any call inside the callable causes a CRE + * @throws ProgramFlowManipulationException If any ProgramFlowManipulationException is thrown (other than a + * LoopManipulationException) within the callable + */ + Mixed executeCallable(Environment environment, Target t, Mixed... values) + throws ConfigRuntimeException, ProgramFlowManipulationException, CancelCommandException; + + /** + * Returns the environment associated with this callable. + * @return + */ + Environment getEnv(); +} diff --git a/src/main/java/com/laytonsmith/core/natives/interfaces/CallableRunner.java b/src/main/java/com/laytonsmith/core/natives/interfaces/CallableRunner.java new file mode 100644 index 0000000000..35d48fe12d --- /dev/null +++ b/src/main/java/com/laytonsmith/core/natives/interfaces/CallableRunner.java @@ -0,0 +1,33 @@ +package com.laytonsmith.core.natives.interfaces; + +import com.laytonsmith.PureUtilities.Common.Annotations.InterfaceRunnerFor; +import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.core.MSVersion; +import com.laytonsmith.core.constructs.CClassType; + +/** + * + */ +@InterfaceRunnerFor(Callable.class) +public class CallableRunner extends AbstractMixedInterfaceRunner { + @Override + public String docs() { + return "A common interface for anything that can be executed via execute() or generally with parenthesis," + + " such as @a()."; + } + + @Override + public Version since() { + return MSVersion.V3_3_5; + } + + @Override + public CClassType[] getSuperclasses() { + return new CClassType[]{Mixed.TYPE}; + } + + @Override + public CClassType[] getInterfaces() { + return CClassType.EMPTY_CLASS_ARRAY; + } +} diff --git a/src/main/java/com/laytonsmith/core/natives/interfaces/Commentable.java b/src/main/java/com/laytonsmith/core/natives/interfaces/Commentable.java new file mode 100644 index 0000000000..71a74de3d7 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/natives/interfaces/Commentable.java @@ -0,0 +1,15 @@ +package com.laytonsmith.core.natives.interfaces; + +import com.laytonsmith.PureUtilities.SmartComment; + +/** + * If an element is Commentable, it must be able to be associated with a {@link SmartComment} block. + */ +public interface Commentable { + + /** + * Returns the comment on this element. + * @return + */ + SmartComment getElementComment(); +} diff --git a/src/main/java/com/laytonsmith/core/natives/interfaces/Iterable.java b/src/main/java/com/laytonsmith/core/natives/interfaces/Iterable.java new file mode 100644 index 0000000000..87a739b8c7 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/natives/interfaces/Iterable.java @@ -0,0 +1,20 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.laytonsmith.core.natives.interfaces; + +import com.laytonsmith.annotations.typeof; +import com.laytonsmith.core.constructs.CClassType; + +/** + * An object that is iterable is one that can be iterated. It must implement both ArrayAccess and Sizeable. + */ +@typeof("ms.lang.Iterable") +public interface Iterable extends ArrayAccess, Sizeable { + + @SuppressWarnings("FieldNameHidesFieldInSuperclass") + public static final CClassType TYPE = CClassType.get(Iterable.class); + +} diff --git a/src/main/java/com/laytonsmith/core/natives/interfaces/IterableRunner.java b/src/main/java/com/laytonsmith/core/natives/interfaces/IterableRunner.java new file mode 100644 index 0000000000..291b4ece1c --- /dev/null +++ b/src/main/java/com/laytonsmith/core/natives/interfaces/IterableRunner.java @@ -0,0 +1,46 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.laytonsmith.core.natives.interfaces; + +import com.laytonsmith.core.objects.ObjectType; +import com.laytonsmith.PureUtilities.Common.Annotations.InterfaceRunnerFor; +import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.core.MSVersion; +import com.laytonsmith.core.constructs.CClassType; + +/** + * + * @author caismith + */ +@InterfaceRunnerFor(Iterable.class) +public class IterableRunner extends AbstractMixedInterfaceRunner { + + @Override + public String docs() { + return "Provides access to an object using the square bracket notation."; + } + + @Override + public Version since() { + return MSVersion.V3_3_1; + } + + @Override + public CClassType[] getSuperclasses() { + return new CClassType[]{ArrayAccess.TYPE, Sizeable.TYPE}; + } + + @Override + public CClassType[] getInterfaces() { + return CClassType.EMPTY_CLASS_ARRAY; + } + + @Override + public ObjectType getObjectType() { + return ObjectType.INTERFACE; + } + +} diff --git a/src/main/java/com/laytonsmith/core/natives/interfaces/Iterator.java b/src/main/java/com/laytonsmith/core/natives/interfaces/Iterator.java new file mode 100644 index 0000000000..1e9d387dd4 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/natives/interfaces/Iterator.java @@ -0,0 +1,116 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.laytonsmith.core.natives.interfaces; + +import java.util.Arrays; + +/** + * This class contains iteration information for the Iterator object as it is being iterated. This assumes that + * the object being iterated is not associative. Associative arrays have far simpler handling, and can therefore + * skip the handling needed for non-associative arrays. + */ +public class Iterator { + + private final Iterable array; + private int current = 0; + private int[] blacklist = new int[]{-1}; + private int blacklistSize = 0; + + /** + * Creates a new ArrayAccessIterator. If the array is associative, a RuntimeException is thrown, since + * associative arrays have far simpler handling, and should not use this mechanism. + * + * @param array + */ + public Iterator(Iterable array) { + if(array.isAssociative()) { + throw new RuntimeException(); + } + this.array = array; + } + + /** + * Returns the index of the currently iterated object. + * + * @return + */ + public int getCurrent() { + return current; + } + + /** + * Decrements the current counter. This operation is used when the current item, or an item before the current + * item is removed. + */ + public void decrementCurrent() { + --current; + } + + /** + * Increments the current counter. This operation is used when a new item is inserted before or at the current + * item. It is also used at the end of the loop. + */ + public void incrementCurrent() { + ++current; + } + + /** + * Increments all the values in the blacklist. This is used when a new item is inserted into the array, after + * the current index. + * + * @param from The value to search from. Any values after this are incremented, and values before it are not. + */ + public void incrementBlacklistAfter(int from) { + for(int i = 0; i < blacklist.length; i++) { + if(blacklist[i] > from) { + blacklist[i]++; + } + } + } + + /** + * Adds a value to the blacklist. This is used when a value is added after the current index. This value will + * not be iterated in the future. + * + * @param index The index to add to the blacklist. + */ + public void addToBlacklist(int index) { + if(blacklistSize == blacklist.length) { + int[] bl = new int[blacklist.length * 2]; + Arrays.fill(bl, -1); + System.arraycopy(blacklist, 0, bl, 0, blacklist.length); + blacklist = bl; + } + blacklist[blacklistSize] = index; + blacklistSize++; + } + + /** + * Checks through the blacklist, and returns true if this index is blacklisted. If so, this value should be + * skipped in the iteration. + * + * @param index The index to check. + * @return True if this value should be skipped. + */ + public boolean isBlacklisted(int index) { + for(int v : blacklist) { + if(v == index) { + return true; + } + } + return false; + } + + /** + * Gets the underlying Iterable object. + * + * @return + */ + public Iterable underlyingArray() { + return array; + } + +} diff --git a/src/main/java/com/laytonsmith/core/natives/interfaces/MAnnotation.java b/src/main/java/com/laytonsmith/core/natives/interfaces/MAnnotation.java new file mode 100644 index 0000000000..3594887177 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/natives/interfaces/MAnnotation.java @@ -0,0 +1,13 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.laytonsmith.core.natives.interfaces; + +/** + * Represents a MethodScript annotation. + */ +public class MAnnotation { + // TODO +} diff --git a/src/main/java/com/laytonsmith/core/natives/interfaces/MEnumType.java b/src/main/java/com/laytonsmith/core/natives/interfaces/MEnumType.java new file mode 100644 index 0000000000..2aa4e17be3 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/natives/interfaces/MEnumType.java @@ -0,0 +1,534 @@ +package com.laytonsmith.core.natives.interfaces; + +import com.laytonsmith.core.objects.ObjectType; +import com.laytonsmith.core.objects.ObjectModifier; +import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery; +import com.laytonsmith.PureUtilities.SimpleVersion; +import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.annotations.MDynamicEnum; +import com.laytonsmith.annotations.MEnum; +import com.laytonsmith.annotations.typeof; +import com.laytonsmith.core.MSVersion; +import com.laytonsmith.core.Documentation; +import com.laytonsmith.core.FullyQualifiedClassName; +import com.laytonsmith.core.SimpleDocumentation; +import com.laytonsmith.core.constructs.CClassType; +import com.laytonsmith.core.constructs.Construct; +import com.laytonsmith.core.constructs.Target; +import com.laytonsmith.core.exceptions.CRE.CREIllegalArgumentException; +import com.laytonsmith.core.exceptions.CRE.CREIndexOverflowException; +import com.laytonsmith.core.exceptions.CRE.CREUnsupportedOperationException; +import com.laytonsmith.core.exceptions.ConfigRuntimeException; +import com.laytonsmith.core.objects.AccessModifier; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; +import java.util.AbstractList; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * This is the base class for all enums in MethodScript. Enums themselves can be automatically generated based on a + * real enum, or dynamically generated based on user code. Enums marked with {@link MEnum} or {@link MDynamicEnum} are + * automatically added to the ecosystem, however. + */ +@typeof("ms.lang.enum") +public abstract class MEnumType implements Mixed, com.laytonsmith.core.natives.interfaces.Iterable { + + + @SuppressWarnings("FieldNameHidesFieldInSuperclass") + public static final CClassType TYPE = CClassType.get(MEnumType.class); + + /** + * Generates a new MEnumType subclass. + * @param fqcn The fully qualified class name. Generally, this should be gathered from the typeof of the MEnum, if + * applicable, but if this is an external enum, or dynamically generated, this may come from other sources. + * @param enumClass The underlying java enum class + * @param docs This may be null if the enum implements {@code public static String enumDocs()}, otherwise this + * should be the docs for the enum class as a whole. + * @param since This may be null if the enum implements {@code public static Version enumSince()}, otherwise this + * should be the since tag for the enum class as a whole. + * @return A subclass of MEnumType. This does not register it in the ecosystem. + */ + public static MEnumType FromEnum(FullyQualifiedClassName fqcn, final Class> enumClass, + String docs, Version since) { + return FromPartialEnum(fqcn, enumClass, enumClass.getEnumConstants(), docs, since); + } + + /** + * Generates a new MEnumType subclass. + * @param fqcn The fully qualified class name. Generally, this should be gathered from the typeof of the MEnum, if + * applicable, but if this is an external enum, or dynamically generated, this may come from other sources. + * @param enumClass The underlying java enum class + * @param values The list of enum constants. This does not have to be the full list of Enum values in the type, or + * indeed, even the enum values from the enumClass. It does have to be an Enum type, however, as we need a + * customizable type for documentation purposes. + * @param docs This may be null if the enum implements {@code public static String enumDocs()}, otherwise this + * should be the docs for the enum class as a whole. + * @param since This may be null if the enum implements {@code public static Version enumSince()}, otherwise this + * should be the since tag for the enum class as a whole. + * @return A subclass of MEnumType. This does not register it in the ecosystem. + */ + public static MEnumType FromPartialEnum(FullyQualifiedClassName fqcn, final Class enumClass, + Enum[] values, String docs, Version since) { + final Enum[] constants = values; + return new MEnumType() { + @Override + public String docs() { + if(docs != null) { + return docs; + } + Method enumDocs; + try { + enumDocs = enumClass.getDeclaredMethod("enumDocs"); + } catch (NoSuchMethodException | SecurityException ex) { + return "This enum does not have documentation. Either pass in the docs, or implement public static" + + " String enumDocs() in the enum."; + } + Object d; + try { + d = enumDocs.invoke(null); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + d = null; + } + if(d instanceof String) { + return (String) d; + } + return "The return type of the enumDocs method is wrong. It must return a String"; + } + + @Override + public Version since() { + if(since != null) { + return since; + } + Method enumSince; + try { + enumSince = enumClass.getDeclaredMethod("enumSince"); + } catch (NoSuchMethodException | SecurityException ex) { + return new SimpleVersion(0, 0, 0); + } + Object d; + try { + d = enumSince.invoke(null); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + d = null; + } + if(d instanceof Version) { + return (Version) d; + } + return new SimpleVersion(0, 0, 0); + } + + @Override + public CClassType[] getSuperclasses() { + return new CClassType[]{MEnumType.TYPE}; + } + + @Override + public String getName() { + return fqcn.getFQCN(); + } + + @Override + public URL getSourceJar() { + return ClassDiscovery.GetClassContainer(enumClass); + } + + @Override + public boolean isInstanceOf(CClassType type) { + return Mixed.TYPE.equals(type) || MEnumType.TYPE.equals(type) || this.typeof().equals(type); + } + + @Override + public boolean isInstanceOf(Class type) { + return type.isAssignableFrom(this.getClass()); + } + + @Override + public CClassType typeof() { + try { + return CClassType.get(fqcn); + } catch (ClassNotFoundException ex) { + throw new Error(ex); + } + } + + @Override + public String val() { + return getName(); + } + + @Override + public List getValues() { + return new AbstractList() { + @Override + public MEnumTypeValue get(int index) { + final Enum v = constants[index]; + return new MEnumTypeValue() { + @Override + public int ordinal() { + return index; + } + + @Override + public String name() { + return v.name(); + } + + @Override + public URL getSourceJar() { + return ClassDiscovery.GetClassContainer(enumClass); + } + + @Override + public Class[] seeAlso() { + if(SimpleDocumentation.class.isAssignableFrom(v.getDeclaringClass())) { + try { + return (Class[]) v.getDeclaringClass().getDeclaredMethod("seeAlso").invoke(v); + } catch (NoSuchMethodException | SecurityException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException ex) { + throw new RuntimeException(ex); + } + } else { + return new Class[0]; + } + } + + @Override + public String getName() { + return v.name(); + } + + @Override + public String docs() { + if(SimpleDocumentation.class.isAssignableFrom(v.getDeclaringClass())) { + try { + return (String) v.getDeclaringClass().getDeclaredMethod("docs").invoke(v); + } catch (NoSuchMethodException | SecurityException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException ex) { + throw new RuntimeException(ex); + } + } else { + return ""; + } + } + + @Override + public Version since() { + if(SimpleDocumentation.class.isAssignableFrom(v.getDeclaringClass())) { + try { + return (Version) v.getDeclaringClass().getDeclaredMethod("since").invoke(v); + } catch (NoSuchMethodException | SecurityException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException ex) { + throw new RuntimeException(ex); + } + } else { + return MSVersion.V0_0_0; + } + } + + @Override + public int hashCode() { + return v.hashCode(); + } + + @Override + @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") + public boolean equals(Object obj) { + return v.equals(obj); + } + + @Override + public String toString() { + return v.toString(); + } + + @Override + public CClassType typeof() { + try { + return CClassType.get(fqcn); + } catch (ClassNotFoundException ex) { + throw new Error(ex); + } + } + + @Override + public boolean isInstanceOf(CClassType type) { + return Construct.isInstanceof(this, type); + } + + @Override + public boolean isInstanceOf(Class type) { + return Construct.isInstanceof(this, type); + } + + @Override + public CClassType getContainingClass() { + return null; + } + + @Override + public AccessModifier getAccessModifier() { + return AccessModifier.PUBLIC; + } + + @Override + public Set getObjectModifiers() { + return EnumSet.of(ObjectModifier.FINAL, ObjectModifier.STATIC, + ObjectModifier.ABSTRACT); + } + + @Override + public ObjectType getObjectType() { + return ObjectType.ENUM; + } + + @Override + public CClassType[] getInterfaces() { + return new CClassType[0]; + } + + @Override + public CClassType[] getSuperclasses() { + return new CClassType[]{MEnumType.TYPE}; + } + + @Override + public Mixed clone() throws CloneNotSupportedException { + return this; + } + + private Target t = Target.UNKNOWN; + + @Override + public Target getTarget() { + return t; + } + + @Override + public void setTarget(Target target) { + t = target; + } + + @Override + public String val() { + return getName(); + } + }; + } + + @Override + public int size() { + return constants.length; + } + }; + } + + @Override + public boolean getBooleanValue(Target t) { + return true; + } + + }; + } + + private static final MEnumType ROOT_TYPE = new MEnumType() { + @Override + protected List getValues() { + throw new UnsupportedOperationException("The root MEnumType is a meta object, and cannot be used normally." + + " There are no values in the class."); + } + + }; + + /** + * Returns the meta object representing the ms.lang.enum type. While this is a valid type, it is not an enum per se + * and cannot be used like an enum. It can be used as a class type or a documentation getter. + * @return + */ + public static MEnumType getRootEnumType() { + return ROOT_TYPE; + } + + + + private Target target; + + public MEnumType() { + + } + + @Override + public String getName() { + return TYPE.getName(); + } + + @Override + public String val() { + return TYPE.getName(); + } + + @Override + public void setTarget(Target target) { + this.target = target; + } + + @Override + public Target getTarget() { + return this.target; + } + + @Override + @SuppressWarnings("CloneDoesntCallSuperClone") + public MEnumType clone() throws CloneNotSupportedException { + return this; + } + + @Override + public String docs() { + return "This is the base type for all enums in MethodScript."; + } + + @Override + public Version since() { + return MSVersion.V3_3_4; + } + + @Override + public CClassType[] getSuperclasses() { + return new CClassType[]{Mixed.TYPE}; + } + + @Override + public CClassType[] getInterfaces() { + return new CClassType[]{Iterable.TYPE}; + } + + @Override + public ObjectType getObjectType() { + // The individual values in the enum are ENUM, but the container is a class + return ObjectType.CLASS; + } + + @Override + public Set getObjectModifiers() { + return EnumSet.of(ObjectModifier.FINAL, ObjectModifier.ABSTRACT); + } + + @Override + public AccessModifier getAccessModifier() { + return AccessModifier.PUBLIC; + } + + @Override + public CClassType getContainingClass() { + return null; + } + + @Override + public boolean isInstanceOf(CClassType type) { + return TYPE.equals(type); + } + + @Override + public boolean isInstanceOf(Class type) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public CClassType typeof() { + return TYPE; + } + + @Override + public URL getSourceJar() { + return ClassDiscovery.GetClassContainer(MEnumType.class); + } + + @Override + public Class[] seeAlso() { + return new Class[0]; + } + + private volatile List values = null; + private final Object lock = new Object(); + /** + * Unlike + * @return + */ + public List values() { + @SuppressWarnings("LocalVariableHidesMemberVariable") + List values = this.values; + if(values == null) { + synchronized(lock) { + values = this.values; + if(values == null) { + this.values = values = getValues(); + } + } + } + return values; + } + @Override + public Mixed get(String index, Target t) throws ConfigRuntimeException { + for(MEnumTypeValue v : values()) { + if(v.name().equals(index)) { + return v; + } + } + throw new CREIllegalArgumentException(index + " cannot be found in " + typeof(), t); + } + + @Override + public Mixed get(int index, Target t) throws ConfigRuntimeException { + if(index >= values().size()) { + throw new CREIndexOverflowException("The index " + index + " is out of bounds", t); + } + return values().get(index); + } + + @Override + public Mixed get(Mixed index, Target t) throws ConfigRuntimeException { + return get(index.val(), t); + } + + @Override + public Set keySet() { + return values().stream().collect(Collectors.toSet()); + } + + @Override + public long size() { + return values().size(); + } + + @Override + public boolean isAssociative() { + return true; + } + + @Override + public boolean canBeAssociative() { + return true; + } + + @Override + public Mixed slice(int begin, int end, Target t) { + throw new CREUnsupportedOperationException("Cannot slice an enum", t); + } + + /** + * Returns a list of the underlying enum values. This is roughly equivalent to a list of the Enum java class. + * @return + */ + protected abstract List getValues(); + + @Override + public boolean getBooleanValue(Target t) { + return true; + } + +} diff --git a/src/main/java/com/laytonsmith/core/natives/interfaces/MEnumTypeValue.java b/src/main/java/com/laytonsmith/core/natives/interfaces/MEnumTypeValue.java new file mode 100644 index 0000000000..84f20e41c2 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/natives/interfaces/MEnumTypeValue.java @@ -0,0 +1,24 @@ +package com.laytonsmith.core.natives.interfaces; + +import com.laytonsmith.core.Documentation; + +/** + * + * @author caismith + */ +public interface MEnumTypeValue extends Documentation, Mixed { + + /** + * Returns the ordinal value for this entry. The ordinal value goes from 0 to size-1, and is defined by the + * order the values were defined. + * @return + */ + int ordinal(); + + /** + * The name of this value + * @return + */ + String name(); + +} diff --git a/src/main/java/com/laytonsmith/core/natives/interfaces/MList.java b/src/main/java/com/laytonsmith/core/natives/interfaces/MList.java index 9e20f93861..1039b5fb42 100644 --- a/src/main/java/com/laytonsmith/core/natives/interfaces/MList.java +++ b/src/main/java/com/laytonsmith/core/natives/interfaces/MList.java @@ -3,10 +3,9 @@ import java.util.ArrayList; /** - * A list that extends ArrayList>Object>, to ensure - * compliance when using MObject. - * + * A list that extends ArrayList>Object>, to ensure compliance when using MObject. + * */ public class MList extends ArrayList { - + } diff --git a/src/main/java/com/laytonsmith/core/natives/interfaces/MMap.java b/src/main/java/com/laytonsmith/core/natives/interfaces/MMap.java index 2642681e36..841dbe97d0 100644 --- a/src/main/java/com/laytonsmith/core/natives/interfaces/MMap.java +++ b/src/main/java/com/laytonsmith/core/natives/interfaces/MMap.java @@ -3,10 +3,9 @@ import java.util.HashMap; /** - * An extension of HashMap<String, Object>, which is meant - * to ensure generic type compliance when using MObjects. - * + * An extension of HashMap<String, Object>, which is meant to ensure generic type compliance when using MObjects. + * */ public class MMap extends HashMap { - + } diff --git a/src/main/java/com/laytonsmith/core/natives/interfaces/MObject.java b/src/main/java/com/laytonsmith/core/natives/interfaces/MObject.java index 8c97aa92f1..53b57488b6 100644 --- a/src/main/java/com/laytonsmith/core/natives/interfaces/MObject.java +++ b/src/main/java/com/laytonsmith/core/natives/interfaces/MObject.java @@ -1,22 +1,21 @@ package com.laytonsmith.core.natives.interfaces; import com.laytonsmith.annotations.nofield; -import com.laytonsmith.core.Static; +import com.laytonsmith.core.ArgumentValidation; import com.laytonsmith.core.constructs.CArray; import com.laytonsmith.core.constructs.CNull; import com.laytonsmith.core.constructs.Construct; import com.laytonsmith.core.constructs.Target; -import com.laytonsmith.core.exceptions.ConfigRuntimeException; -import com.laytonsmith.core.functions.Exceptions; +import com.laytonsmith.core.exceptions.CRE.CRECastException; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; /** - * An MObject is a definition of an array with a certain configuration. While an MObject can be constructed - * directly, this is generally discouraged, since the reusability is lower this way. Instead, subclass it, - * add direct accessors for various properties, and construct that instead. The static methods - * provided allow for creation of an instance, given a CArray. + * An MObject is a definition of an array with a certain configuration. While an MObject can be constructed directly, + * this is generally discouraged, since the reusability is lower this way. Instead, subclass it, add direct accessors + * for various properties, and construct that instead. The static methods provided allow for creation of an instance, + * given a CArray. * * Subclasses should note that a typical implementation will look like this: *
@@ -28,26 +27,22 @@
  * ...
  * 
* - * Additionally, the alias function can be overriden to provide for aliased - * values, which will be accepted as non-dynamic parameters that point to - * the real fields. They will not be included during serialization, however, - * reads and writes to these fields will work correctly. + * Additionally, the alias function can be overridden to provide for aliased values, which will be accepted as + * non-dynamic parameters that point to the real fields. They will not be included during serialization, however, reads + * and writes to these fields will work correctly. * - * If an object contains fields that should not be handled as instance fields, - * they must be set to private, and they will be - * ignored. All other fields should be public. If a field MUST be public, but - * not accessible, you may annotate it with the {@link nofield} annotation, though - * this is highly discouraged, since it breaks future compatibility. + * If an object contains fields that should not be handled as instance fields, they must be set to private, and they + * will be ignored. All other fields should be public. If a field MUST be public, but not accessible, you may annotate + * it with the {@link nofield} annotation, though this is highly discouraged, since it breaks future compatibility. * - * Note that in the example, an Integer is used. This is appropriate, though Construct - * types may be used as well. The supported POJO types are only the following, - * and conversions are automatic: All 8 primitives' object wrappers, String, MList, - * MObject (and subclasses) and MMap. + * Note that in the example, an Integer is used. This is appropriate, though Construct types may be used as well. The + * supported POJO types are only the following, and conversions are automatic: All 8 primitives' object wrappers, + * String, MList, MObject (and subclasses) and MMap. * */ public class MObject { - public static T Construct(Class type, CArray data){ + public static T Construct(Class type, CArray data) { T instance; try { instance = type.newInstance(); @@ -60,99 +55,97 @@ public static T Construct(Class type, CArray data){ } /** - * Constructs a fully dynamic MObject based on the given array. This is discouraged - * from direct use. + * Constructs a fully dynamic MObject based on the given array. This is discouraged from direct use. + * * @param data * @return */ - public static MObject Construct(CArray data){ + public static MObject Construct(CArray data) { return Construct(MObject.class, data); } - private Map fields = new HashMap(); + private final Map fields = new HashMap(); /** - * If a field can have an alias, this should return the proper - * name given this alias. If this is not an alias, return null, and - * the parameter will be added as a dynamic parameter, or if it actually - * represents a field, that is set instead. If both the value and the - * alias are set, the actual value takes priority, should the two values + * If a field can have an alias, this should return the proper name given this alias. If this is not an alias, + * return null, and the parameter will be added as a dynamic parameter, or if it actually represents a field, that + * is set instead. If both the value and the alias are set, the actual value takes priority, should the two values * be different. + * * @param field * @return */ - protected String alias(String field){ + protected String alias(String field) { return null; } /** - * Sets the field to the given parameter. If the field is a non-dynamic - * property, it is actually set in the object (and converted properly), - * otherwise it is simply added to the dynamic field list. + * Sets the field to the given parameter. If the field is a non-dynamic property, it is actually set in the object + * (and converted properly), otherwise it is simply added to the dynamic field list. + * * @param field * @param value * @param t */ - public final void set(String field, Construct value, Target t){ + public final void set(String field, Construct value, Target t) { String alias = alias(field); - if(alias != null){ + if(alias != null) { field = alias; } - for(Field f : this.getClass().getFields()){ - if(f.isAnnotationPresent(nofield.class)){ + for(Field f : this.getClass().getFields()) { + if(f.isAnnotationPresent(nofield.class)) { //Skip this one continue; } - if(f.getName().equals(field)){ + if(f.getName().equals(field)) { //This is it, so let's set it, (converting if necessary) then break Object val; Class fType = f.getType(); - if(value instanceof CNull){ //TODO + if(value instanceof CNull) { //TODO //Easy case val = null; } else { - if(fType == byte.class){ - val = Static.getInt8(value, t); - } else if(fType == short.class){ - val = Static.getInt16(value, t); - } else if(fType == int.class){ - val = Static.getInt32(value, t); - } else if(fType == long.class){ - val = Static.getInt(value, t); - } else if(fType == char.class){ - if(value.val().length() == 0){ + if(fType == byte.class) { + val = ArgumentValidation.getInt8(value, t); + } else if(fType == short.class) { + val = ArgumentValidation.getInt16(value, t); + } else if(fType == int.class) { + val = ArgumentValidation.getInt32(value, t); + } else if(fType == long.class) { + val = ArgumentValidation.getInt(value, t); + } else if(fType == char.class) { + if(value.val().length() == 0) { val = null; } else { val = value.val().charAt(0); } - } else if(fType == boolean.class){ - val = Static.getBoolean(value); - } else if(fType == float.class){ - val = Static.getDouble32(value, t); - } else if(fType == double.class){ - val = Static.getDouble(value, t); - } else if(fType == MMap.class){ - CArray ca = Static.getArray(value, t); + } else if(fType == boolean.class) { + val = ArgumentValidation.getBoolean(value, t); + } else if(fType == float.class) { + val = ArgumentValidation.getDouble32(value, t); + } else if(fType == double.class) { + val = ArgumentValidation.getDouble(value, t); + } else if(fType == MMap.class) { + CArray ca = ArgumentValidation.getArray(value, t); MMap m = new MMap(); - for(String key : ca.stringKeySet()){ + for(String key : ca.stringKeySet()) { m.put(key, ca.get(key, t)); } val = m; - } else if(fType == MList.class){ - CArray ca = Static.getArray(value, t); + } else if(fType == MList.class) { + CArray ca = ArgumentValidation.getArray(value, t); MList m = new MList(); - if(ca.inAssociativeMode()){ - throw new ConfigRuntimeException("Expected non-associative array, but an associative array was found instead.", - Exceptions.ExceptionType.CastException, t); + if(ca.inAssociativeMode()) { + throw new CRECastException("Expected non-associative array, but an associative array was found instead.", t); } - for(int i = 0; i < ca.size(); i++){ + for(int i = 0; i < ca.size(); i++) { m.add(ca.get(i, t)); } val = m; - } else if(Construct.class.isAssignableFrom(fType)){ + } else if(Construct.class.isAssignableFrom(fType)) { val = value; - } else if(MObject.class.isAssignableFrom(fType)){ - CArray ca = Static.getArray(value, t); + } else if(MObject.class.isAssignableFrom(fType)) { + CArray ca = ArgumentValidation.getArray(value, t); val = MObject.Construct(fType, ca); } else { //Programming error. @@ -164,9 +157,7 @@ public final void set(String field, Construct value, Target t){ //val is now set correctly, guaranteed. f.set(this, val); //These exceptions cannot happen. - } catch (IllegalArgumentException ex) { - throw new Error(ex); - } catch (IllegalAccessException ex) { + } catch (IllegalArgumentException | IllegalAccessException ex) { throw new Error(ex); } } @@ -177,10 +168,11 @@ public final void set(String field, Construct value, Target t){ /** * Retrieves a Construct from the + * * @param field * @return */ - public Construct get(String field){ + public Construct get(String field) { return null; //TODO } } diff --git a/src/main/java/com/laytonsmith/core/natives/interfaces/Matrix.java b/src/main/java/com/laytonsmith/core/natives/interfaces/Matrix.java new file mode 100644 index 0000000000..beb7c17c0d --- /dev/null +++ b/src/main/java/com/laytonsmith/core/natives/interfaces/Matrix.java @@ -0,0 +1,77 @@ +package com.laytonsmith.core.natives.interfaces; + +import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.annotations.typeof; +import com.laytonsmith.core.constructs.CClassType; +import com.laytonsmith.core.constructs.Target; + +/** + * + * @param The underlying type in the matrix. + */ +@typeof("ms.lang.Matrix") +public interface Matrix extends Mixed { + + @SuppressWarnings("FieldNameHidesFieldInSuperclass") + public static final CClassType TYPE = CClassType.get(Matrix.class); + + @Override + public String docs(); + + @Override + public Version since(); + + /** + * Returns true if the matrix is isotropic, that is, all dimensions are equal. + * For higher dimension matrices, this terminology is slightly + * inaccurate, it would rather be isCubic, isTesseractic, is5Cubic, etc, but for simplicity sake we simplify + * the name as if every matrix were 2d. + * @return + */ + public boolean isSquare(); + + /** + * Returns the size of the given dimension. This is the same as {@code getDimensions()[dimension]}. Subclasses + * may provide named dimensions. + * @param dimension + * @param t + * @return + */ + public int getDimensionSize(int dimension, Target t); + + /** + * Returns true if the matrix has a dimension of the given value. That is, if this is a 2d matrix, then + * {@code hasDimension(2)} would return true, but {@code hasDimension(3)} would return false. If the input + * is 0 or negative, this will always return false, but will not otherwise error. + * @param dimension + * @return + */ + public boolean hasDimension(int dimension); + + /** + * Returns the dimension sizes. + * @return + */ + public int[] getDimensions(); + + /** + * Returns true if the given matrix can be added to this matrix. This also implies that it could be subtracted + * instead. + * @param other + * @return + */ + public boolean canAdd(Matrix other); + + /** + * Returns true if the given matrix can be multiplied with this matrix. + * @param other + * @return + */ + public boolean canMultiply(Matrix other); + + /** + * Returns the underlying data type of this matrix. + * @return + */ + public Class getDataType(); +} diff --git a/src/main/java/com/laytonsmith/core/natives/interfaces/MatrixRunner.java b/src/main/java/com/laytonsmith/core/natives/interfaces/MatrixRunner.java new file mode 100644 index 0000000000..b6531ac269 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/natives/interfaces/MatrixRunner.java @@ -0,0 +1,37 @@ +package com.laytonsmith.core.natives.interfaces; + +import com.laytonsmith.PureUtilities.Common.Annotations.InterfaceRunnerFor; +import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.core.MSVersion; +import com.laytonsmith.core.constructs.CClassType; + +/** + * + */ +@InterfaceRunnerFor(Matrix.class) +public class MatrixRunner extends AbstractMixedInterfaceRunner { + @Override + public String docs() { + return "A matrix is a multidimensional collection of values, similar to a multidimensional array." + + " Unlike a multidimensional array, a matrix requires the values in each dimension to have" + + " a fixed size. For instance, in a 2 x 5 2d matrix, each row must have exactly 5 elements, and there" + + " must be exactly 2 rows. Various specialized subclasses of matrices exist, such as a square" + + " matrix, the identity matrix, and more. This interface defines basic operations that are common" + + " to all matrices."; + } + + @Override + public Version since() { + return MSVersion.V3_3_5; + } + + @Override + public CClassType[] getSuperclasses() { + return new CClassType[]{Mixed.TYPE}; + } + + @Override + public CClassType[] getInterfaces() { + return CClassType.EMPTY_CLASS_ARRAY; + } +} diff --git a/src/main/java/com/laytonsmith/core/natives/interfaces/Mixed.java b/src/main/java/com/laytonsmith/core/natives/interfaces/Mixed.java index 29efaec56d..3333761733 100644 --- a/src/main/java/com/laytonsmith/core/natives/interfaces/Mixed.java +++ b/src/main/java/com/laytonsmith/core/natives/interfaces/Mixed.java @@ -1,11 +1,169 @@ - - package com.laytonsmith.core.natives.interfaces; +import com.laytonsmith.core.objects.ObjectType; +import com.laytonsmith.core.objects.ObjectModifier; +import com.laytonsmith.PureUtilities.Common.Annotations.ForceImplementation; +import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.annotations.typeof; +import com.laytonsmith.core.Documentation; +import com.laytonsmith.core.SimpleDocumentation; +import com.laytonsmith.core.constructs.CClassType; +import com.laytonsmith.core.constructs.Construct; +import com.laytonsmith.core.constructs.Target; +import com.laytonsmith.core.objects.AccessModifier; +import java.util.Set; + /** - * - * + * Mixed is the root type of all MethodScript objects and primitives. */ -public interface Mixed { - public String val(); +@typeof("ms.lang.mixed") +public interface Mixed extends Cloneable, Documentation { + + @SuppressWarnings("FieldNameHidesFieldInSuperclass") + public static final CClassType TYPE = CClassType.get(Mixed.class); + + public String val(); + + public void setTarget(Target target); + + public Target getTarget(); + + public Mixed clone() throws CloneNotSupportedException; + + /** + * Overridden from {@link SimpleDocumentation}. This should just return the value of the typeof annotation, + * unconditionally. + * + * @return + */ + @Override + public String getName(); + + @Override + @ForceImplementation + public String docs(); + + @Override + @ForceImplementation + public Version since(); + + /** + * Returns a list of the classes that this *directly* extends. This is not always equivalent to the classes that the + * underlying java class extends. All classes must override this method, but if the class is a phantom class (that + * is, it implements Mixed, but does not have a typeof annotation) then it can simply throw an + * UnsupportedOperationException. + * + * For true interfaces (that is, classes that return {@link ObjectType#INTERFACE}, this means the values that this + * interface also extends. + * + * @return + */ + @ForceImplementation + public CClassType[] getSuperclasses(); + + /** + * Returns a list of the interfaces that this *directly* implements. This is not always equivalent to the interfaces + * that the underlying java class extends. All classes must override this method, but if the class is a phantom + * class (that is, it implements Mixed, but does not have a typeof annotation) then it can simply throw an + * UnsupportedOperationException. + * + * If this is an interface, this should return an empty array always. + * + * It's also important to note that for performance reasons, if an empty array is returned, the code should prefer + * to use {@link CClassType#EMPTY_CLASS_ARRAY}, and for core code, this is required by a unit test. + * + * @return + */ + @ForceImplementation + public CClassType[] getInterfaces(); + + /** + * Returns information about this class, whether it is a class, whether it is final, etc. + */ + public ObjectType getObjectType(); + + /** + * Returns modification information about this class, i.e. if it is final + * + * @return + */ + public Set getObjectModifiers(); + + /** + * Gets the access level for this object, i.e. public, private... + * @return + */ + public AccessModifier getAccessModifier(); + + /** + * Returns the containing class for this object. If null is returned, that means this is a top level class. + * + * @return + */ + public CClassType getContainingClass(); + + /** + * Generally speaking, we cannot use Java's instanceof keyword to determine if something is an instanceof, because + * user classes do not extend the hierarchy of objects in MethodScript. Essentially, we need to extend Java's + * instanceof keyword, so in order to do that, we must compare objects with a custom method, rather than rely on + * Java's keyword. + * + * This method works with CClassTypes. + * + * Implementation note: The implementation of this should just be + * {@link Construct#isInstanceof(com.laytonsmith.core.natives.interfaces.Mixed, + * com.laytonsmith.core.constructs.CClassType)} which supports Mixed values. + * + * @param type + * @return + */ + public boolean isInstanceOf(CClassType type); + + /** + * Generally speaking, we cannot use Java's instanceof keyword to determine if something is an instanceof, because + * user classes do not extend the hierarchy of objects in MethodScript. Essentially, we need to extend Java's + * instanceof keyword, so in order to do that, we must compare objects with a custom method, rather than rely on + * Java's keyword. + * + * This method works with class type directly. + * + * Implementation note: The implementation of this should just be + * {@link Construct#isInstanceof(com.laytonsmith.core.natives.interfaces.Mixed, java.lang.Class)} which supports + * Mixed values. + * + * @param type + * @return + */ + public boolean isInstanceOf(Class type); + + /** + * Returns the typeof this value, as a CClassType object. Not all constructs are annotated with the @typeof + * annotation, in which case this is considered a "private" object, which can't be directly accessed via + * MethodScript. In this case, an IllegalArgumentException is thrown. + * + * @return + * @throws IllegalArgumentException If the class isn't public facing. + */ + public CClassType typeof(); + + /** + * Casts the class to the specified type. This only works with Java types, and so for dynamic elements, this + * may throw a RuntimeException. For dynamic types, use the other castTo. + * + *

For classes that are instanceof this class, (or vice versa) no logic is done, it is just cast, though if + * it can't be cast, it will throw a ClassCastException. For classes that are cross castable, they will be first + * cross casted. + * + * An important note, while it seems like you can just cast from the value using standard java cast notation, + * the object model is not 1:1, and so dynamic classes may logically extend the java class, but due to restrictions + * in java, that can't actually happen in java's object model, so we cannot rely on java's casting functionality + * either. Add to that that user types don't exist in the java anyways. + * @param + * @param clazz + * @return + * @throws ClassCastException if the class can't be cast + */ + // The whole argument validation/Static.get* methods needs to be moved to this mechanism. + //public T castTo(Class clazz) throws ClassCastException; + } diff --git a/src/main/java/com/laytonsmith/core/natives/interfaces/MixedInterfaceRunner.java b/src/main/java/com/laytonsmith/core/natives/interfaces/MixedInterfaceRunner.java new file mode 100644 index 0000000000..6d310c5f56 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/natives/interfaces/MixedInterfaceRunner.java @@ -0,0 +1,93 @@ +package com.laytonsmith.core.natives.interfaces; + +import com.laytonsmith.core.objects.ObjectType; +import com.laytonsmith.core.objects.ObjectModifier; +import com.laytonsmith.PureUtilities.Common.Annotations.ForceImplementation; +import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.core.constructs.CClassType; +import java.net.URL; +import java.util.Set; + +/** + * Classes that use InterfaceRunnerFor and represent an object that implements Mixed should implement this interface. + * See {@link TypeofRunnerFor} for more details. + */ +public interface MixedInterfaceRunner extends Mixed { + + /** + * See {@link Mixed#docs()} + * + * @return + */ + @ForceImplementation + @Override + String docs(); + + /** + * See {@link Mixed#since()} + * + * @return + */ + @ForceImplementation + @Override + Version since(); + + /** + * See {@link Mixed#getSuperclasses()} + * + * @return + */ + @ForceImplementation + @Override + CClassType[] getSuperclasses(); + + /** + * See {@link Mixed#getInterfaces()} + * + * @return + */ + @ForceImplementation + @Override + CClassType[] getInterfaces(); + + /** + * See {@link Mixed#getSourceJar()} + * + * @return + */ + @Override + URL getSourceJar(); + + /** + * See {@link Mixed#getName()} + * + * @return + */ + @Override + String getName(); + + /** + * See {@link Mixed#getObjectType()} + * + * @return + */ + @Override + ObjectType getObjectType(); + + /** + * See {@link Mixed#getObjectModifiers()} + * + * @return + */ + @Override + Set getObjectModifiers(); + + /** + * See {@link Mixed#getContainingClass()} + * + * @return + */ + @Override + CClassType getContainingClass(); + +} diff --git a/src/main/java/com/laytonsmith/core/natives/interfaces/MixedRunner.java b/src/main/java/com/laytonsmith/core/natives/interfaces/MixedRunner.java new file mode 100644 index 0000000000..2d4eb1815a --- /dev/null +++ b/src/main/java/com/laytonsmith/core/natives/interfaces/MixedRunner.java @@ -0,0 +1,48 @@ +package com.laytonsmith.core.natives.interfaces; + +import com.laytonsmith.core.objects.ObjectType; +import com.laytonsmith.PureUtilities.Common.Annotations.InterfaceRunnerFor; +import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.core.MSVersion; +import com.laytonsmith.core.constructs.CClassType; +import com.laytonsmith.core.objects.AccessModifier; + +/** + * + */ +@InterfaceRunnerFor(Mixed.class) +public class MixedRunner extends AbstractMixedInterfaceRunner { + + @Override + public String docs() { + return "The root datatype. All datatypes extend mixed."; + } + + @Override + public Version since() { + return MSVersion.V3_0_1; + } + + // Interestingly, this is the only class that will return empty arrays for both + // of these methods. + @Override + public CClassType[] getSuperclasses() { + return CClassType.EMPTY_CLASS_ARRAY; + } + + @Override + public CClassType[] getInterfaces() { + return CClassType.EMPTY_CLASS_ARRAY; + } + + @Override + public ObjectType getObjectType() { + return ObjectType.CLASS; + } + + @Override + public AccessModifier getAccessModifier() { + return AccessModifier.PUBLIC; + } + +} diff --git a/src/main/java/com/laytonsmith/core/natives/interfaces/Sizable.java b/src/main/java/com/laytonsmith/core/natives/interfaces/Sizable.java deleted file mode 100644 index ac500f450d..0000000000 --- a/src/main/java/com/laytonsmith/core/natives/interfaces/Sizable.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.laytonsmith.core.natives.interfaces; - -/** - * Any object that can report a size should implement this. - * - */ -public interface Sizable { - - /** - * Returns the size of this object. - * @return - */ - long size(); -} diff --git a/src/main/java/com/laytonsmith/core/natives/interfaces/Sizeable.java b/src/main/java/com/laytonsmith/core/natives/interfaces/Sizeable.java new file mode 100644 index 0000000000..33cee2f9cb --- /dev/null +++ b/src/main/java/com/laytonsmith/core/natives/interfaces/Sizeable.java @@ -0,0 +1,21 @@ +package com.laytonsmith.core.natives.interfaces; + +import com.laytonsmith.annotations.typeof; +import com.laytonsmith.core.constructs.CClassType; + +/** + * Any object that can report a size should implement this. + */ +@typeof("ms.lang.Sizeable") +public interface Sizeable extends Mixed { + + @SuppressWarnings("FieldNameHidesFieldInSuperclass") + public static final CClassType TYPE = CClassType.get(Sizeable.class); + + /** + * Returns the size of this object. + * + * @return + */ + long size(); +} diff --git a/src/main/java/com/laytonsmith/core/natives/interfaces/SizeableRunner.java b/src/main/java/com/laytonsmith/core/natives/interfaces/SizeableRunner.java new file mode 100644 index 0000000000..a76c70ad17 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/natives/interfaces/SizeableRunner.java @@ -0,0 +1,34 @@ +package com.laytonsmith.core.natives.interfaces; + +import com.laytonsmith.PureUtilities.Common.Annotations.InterfaceRunnerFor; +import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.core.MSVersion; +import com.laytonsmith.core.constructs.CClassType; + +/** + * + */ +@InterfaceRunnerFor(Sizeable.class) +public class SizeableRunner extends AbstractMixedInterfaceRunner { + + @Override + public String docs() { + return "Any object that can report a size should implement this."; + } + + @Override + public Version since() { + return MSVersion.V3_3_1; + } + + @Override + public CClassType[] getSuperclasses() { + return new CClassType[]{Mixed.TYPE}; + } + + @Override + public CClassType[] getInterfaces() { + return CClassType.EMPTY_CLASS_ARRAY; + } + +} diff --git a/src/main/java/com/laytonsmith/core/natives/interfaces/ValueType.java b/src/main/java/com/laytonsmith/core/natives/interfaces/ValueType.java new file mode 100644 index 0000000000..52017d232f --- /dev/null +++ b/src/main/java/com/laytonsmith/core/natives/interfaces/ValueType.java @@ -0,0 +1,37 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.laytonsmith.core.natives.interfaces; + +import com.laytonsmith.PureUtilities.Common.Annotations.ForceImplementation; +import com.laytonsmith.annotations.typeof; +import com.laytonsmith.core.constructs.CClassType; + +/** + * Represents a class that supports passing by value. Value types must meet a few assumptions, they are immutable, + * it may be faster to pass by value than by reference, and if value a and b are equal, then they could be also the same + * reference with no ill effects to any programs. The compiler may choose to pass by value or by reference, and it + * should be the same either way. + * + * Primitives are a good example of this, (and in fact, the primitive class implements this interface) but more + * complex object types may benefit from this as well. + */ +@typeof("ms.lang.ValueType") +public interface ValueType extends Mixed { + + @SuppressWarnings("FieldNameHidesFieldInSuperclass") + public static final CClassType TYPE = CClassType.get(ValueType.class); + + /** + * Returns a duplicated value of this object. The duplicate MUST be equals(), and it MUST NOT be ref_equals(). The + * compiler may choose to call this method or not, if not, then the values would be ref_equals. + * @return The interface defines the return type as {@code ValueType}, but the actual type returned MUST be of the + * type defined in this class. The method is @ForceImplementation'd, to remind the programmer that this must be + * overridden in every class, to return the appropriate type. + */ + @ForceImplementation + ValueType duplicate(); + +} diff --git a/src/main/java/com/laytonsmith/core/natives/interfaces/ValueTypeInterfaceRunner.java b/src/main/java/com/laytonsmith/core/natives/interfaces/ValueTypeInterfaceRunner.java new file mode 100644 index 0000000000..6e6a977605 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/natives/interfaces/ValueTypeInterfaceRunner.java @@ -0,0 +1,41 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.laytonsmith.core.natives.interfaces; + +import com.laytonsmith.PureUtilities.Common.Annotations.InterfaceRunnerFor; +import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.core.MSVersion; +import com.laytonsmith.core.constructs.CClassType; + +/** + * + */ +@InterfaceRunnerFor(ValueType.class) +public class ValueTypeInterfaceRunner extends AbstractMixedInterfaceRunner { + @Override + public String docs() { + return "Any object that supports pass-by-value semantics should implement this interface. Value types must" + + " meet a few assumptions, they are immutable, it may be faster to pass by value than by reference," + + " and if value a and b are equal, then they could be also the same reference with no ill effects" + + " to any programs. The compiler may choose to pass by value or by reference, and it should be" + + " the same either way."; + } + + @Override + public Version since() { + return MSVersion.V3_3_1; + } + + @Override + public CClassType[] getSuperclasses() { + return new CClassType[]{Mixed.TYPE}; + } + + @Override + public CClassType[] getInterfaces() { + return CClassType.EMPTY_CLASS_ARRAY; + } +} diff --git a/src/main/java/com/laytonsmith/core/objects/AccessModifier.java b/src/main/java/com/laytonsmith/core/objects/AccessModifier.java new file mode 100644 index 0000000000..c98d8e6e8f --- /dev/null +++ b/src/main/java/com/laytonsmith/core/objects/AccessModifier.java @@ -0,0 +1,52 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.laytonsmith.core.objects; + +import com.laytonsmith.annotations.MEnum; + +/** + * An AccessModifier describes the state of visibility of an element or class. + */ +@MEnum("ms.lang.AccessModifier") +public enum AccessModifier { + /** + * A public class is one that can be accessed from any other class. Public methods, fields, and classes are used + * to determine the project's version number, and are considered to be the public API of a project, and should be + * treated with care, as well as properly documented. + */ + PUBLIC, + /** + * An internal class is one that can be accessed from within just this project. + */ + INTERNAL, + /** + * A package level class is one that can be accessed from other classes that are within the same package + */ + PACKAGE, + /** + * Protected level item is one that can be accessed only from subclasses. + */ + PROTECTED, + /** + * A private class is one that can only be accessed from other classes within the same containing class. This is not + * usable in top level classes, as it wouldn't make sense otherwise. + */ + PRIVATE, + /** + * A default modifier implies that the modifier was left off. This causes the behavior + * of one of the other modifiers, but the behavior depends on the context in which + * this is used. + * + * If the method + * is defined in a parent class or interface, then the method will adopt the overridden + * method's access modifier. For classes, it will be whatever the parent specifically set + * it as, and for methods defined in an interface, they will be public. (They are necessarily + * public anyways, because all methods in an interface are public.) If the defined method + * is not overriding a method in a parent class, then the method is considered internal. + * Fields that have the default visibility default to internal. + */ + DEFAULT; +} diff --git a/src/main/java/com/laytonsmith/core/objects/DuplicateObjectDefinitionException.java b/src/main/java/com/laytonsmith/core/objects/DuplicateObjectDefinitionException.java new file mode 100644 index 0000000000..d9b213c82c --- /dev/null +++ b/src/main/java/com/laytonsmith/core/objects/DuplicateObjectDefinitionException.java @@ -0,0 +1,29 @@ +package com.laytonsmith.core.objects; + +import com.laytonsmith.core.constructs.Target; +import com.laytonsmith.core.exceptions.ConfigCompileException; + +/** + * Thrown if an ObjectDefintion is attempted to be redefined. An ObjectDefinition is uniquely identified by its fully + * qualified class name, as a String. + */ +public class DuplicateObjectDefinitionException extends ConfigCompileException { + private final boolean wasCopy; + public DuplicateObjectDefinitionException(Target t, boolean isCopy) { + this(null, t, isCopy); + } + + public DuplicateObjectDefinitionException(String message, Target t, boolean isCopy) { + super(message, t); + this.wasCopy = isCopy; + } + + /** + * If the duplicate was an exact copy, then this may assist in diagnosing the problem. It is still not allowed + * to create duplicate classes, even if they are identical, but this flag indicates if that actually is the case. + * @return + */ + public boolean wasCopy() { + return this.wasCopy; + } +} diff --git a/src/main/java/com/laytonsmith/core/objects/ElementDefinition.java b/src/main/java/com/laytonsmith/core/objects/ElementDefinition.java new file mode 100644 index 0000000000..0298ffb807 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/objects/ElementDefinition.java @@ -0,0 +1,198 @@ +package com.laytonsmith.core.objects; + +import com.laytonsmith.core.ParseTree; +import com.laytonsmith.core.constructs.CClassType; +import com.laytonsmith.core.constructs.CNull; +import com.laytonsmith.core.natives.interfaces.Mixed; +import java.lang.reflect.Field; +import java.util.Objects; +import java.util.Set; + +/** + * An ElementDefinition is the definition of elements within an object. These can be either properties or methods, as + * they are handled the same in most ways, this superclass can safely represent both. For methods, the defaultValue + * should always be provided, a Callable. There is special support for native types, in which a Method/Field is + * provided. + */ +public class ElementDefinition { + private final AccessModifier accessModifier; + private final Set elementModifiers; + private final CClassType definedIn; + private final CClassType type; + private final String name; + + private final ParseTree defaultValue; + private java.lang.reflect.Method nativeMethod = null; + private Field nativeField = null; + private final com.laytonsmith.core.Method method; + + /** + * Constructs a new element definition. If this is a native method or field, + * you must also call {@link #setNativeField(java.lang.reflect.Field)} or + * {@link #setNativeMethod(java.lang.reflect.Method)} immediately after construction. + * @param accessModifier The access modifier of the element + * @param elementModifiers The modifiers of the element + * @param definedIn The class that this element is defined in + * @param type The type of the element (variable type for fields, return type + * for methods, java null for constructors) + * @param name The name of the element (should start with @ if this is a + * variable declaration). + * @param defaultValue The default value, if this is a field, and null if this + * is a method. If the default value is MethodScript null, or is not set, you MUST + * send either {@link CNull#NULL} or {@link CNull#UNDEFINED}, rather than java + * null. + * @param method The method, if this is a method. + * @throws NullPointerException If one of the required fields is null + * @throws IllegalArgumentException If both defaultValue and method are non-null. + */ + public ElementDefinition( + AccessModifier accessModifier, + Set elementModifiers, + CClassType definedIn, + CClassType type, + String name, + ParseTree defaultValue, + com.laytonsmith.core.Method method + ) { + Objects.requireNonNull(accessModifier); + Objects.requireNonNull(elementModifiers); + Objects.requireNonNull(name); + if(defaultValue == null && method == null) { + throw new NullPointerException("Either defaultValue must be" + + " set, or method must be set."); + } + if(defaultValue != null && method != null) { + throw new IllegalArgumentException("Both default value and" + + " method cannot be set, one must be null."); + } + this.accessModifier = accessModifier; + this.elementModifiers = elementModifiers; + this.definedIn = definedIn; + this.type = type; + this.name = name; + this.defaultValue = defaultValue; + this.method = method; + } + + /** + * If the underlying class is a native class, the actual Method can be provided here. No + * checks are done at this point, but it MUST be true that the method return type is castable to {@link Mixed}, as + * well as all arguments. + * @param m + */ + public void setNativeMethod(java.lang.reflect.Method m) { + this.nativeMethod = m; + } + + /** + * If the underlying class is a native class, the actual Field can be provided here. No + * checks are done at this point, but it MUST be true that the field type is castable to {@link Mixed}. + * @param f + */ + public void setNativeField(Field f) { + this.nativeField = f; + } + + /** + * Returns true if the underlying item is a reference to a native class. + * @return + */ + public boolean isNative() { + return this.isNativeMethod() || this.isNativeField(); + } + + /** + * Returns true if the underlying item is a reference to a native class method. + * @return + */ + public boolean isNativeMethod() { + return this.nativeMethod != null; + } + + /** + * Returns true if the underlying item is a reference to a native class field. + * @return + */ + public boolean isNativeField() { + return this.nativeField != null; + } + + /** + * The access modifier of the element. + * @return + */ + public AccessModifier getAccessModifier() { + return accessModifier; + } + + /** + * The element modifiers. + * @return + */ + public Set getElementModifiers() { + return elementModifiers; + } + + /** + * The type of the element. For methods, this is the return type. + * @return + */ + public CClassType getType() { + return type; + } + + /** + * The name of the element. + * @return + */ + public String getName() { + return name; + } + + /** + * The default value of the element. This will be {@link CNull#UNDEFINED} if this was a property of the class with + * no assignment at all, and {@link CNull#NULL} if it was defined as null. For methods, this will be java null. + *

+ * Because this is the prototype of the element, we can't simply define this as a Mixed, we need + * to evaluate the prototypical value when we instantiate the object. Therefore, the ParseTree is stored here. For + * atomic values, it's ok to just pull them out and use them, but for others, you must invoke the ParseTree to get + * the value. + *

+ * Iff this is a native type, this will this return (java) null, though that fact should not be relied on, + * use {@link #isNative()} to determine that for sure. + * @return + */ + public ParseTree getDefaultValue() { + return defaultValue; + } + + /** + * If this is a native class, and it represents a method, this should return a reference to the Java Method. + * It will return null if this is not a native class, but that + * fact should not be relied on, use {@link #isNative()} to determine that for sure. + * @return + */ + public java.lang.reflect.Method getNativeMethod() { + return nativeMethod; + } + + /** + * If this is a native class, and it represents a field, this should return a reference to the Java Field. + * It will return null if this is not a native class, but + * that fact should not be relied on, use {@link #isNative()} to determine that for sure. + * @return + */ + public Field getNativeField() { + return nativeField; + } + + /** + * Returns the MethodScript Method reference. If this is a field, this + * will be null. + * @return + */ + public com.laytonsmith.core.Method getMethod() { + return method; + } + +} diff --git a/src/main/java/com/laytonsmith/core/objects/ElementModifier.java b/src/main/java/com/laytonsmith/core/objects/ElementModifier.java new file mode 100644 index 0000000000..d8fbb0ad0e --- /dev/null +++ b/src/main/java/com/laytonsmith/core/objects/ElementModifier.java @@ -0,0 +1,37 @@ +package com.laytonsmith.core.objects; + +import com.laytonsmith.annotations.MEnum; + +/** + * These are the list of modifers that are valid on an Element (i.e. property or method). + */ +@MEnum("ms.lang.ElementModifier") +public enum ElementModifier { + /** + * A final method is one that cannot be overridden in subclasses. + */ + FINAL, + /** + * A static element is one that is not tied to the containing class's instance scope, but is tied to the static + * scope. It is available by dereferencing the ClassType with the :: operator. + */ + STATIC, + /** + * An abstract method is one that is not defined in the class. Only abstract classes may contain these + * methods. This is not allowed on fields. + */ + ABSTRACT, + /** + * A native element is one whose value is drawn from the native code. This is only valid in a class that is itself + * marked as native. + */ + NATIVE, + /** + * An immutable object is one in which none of the fields may be set, other than within the constructor. + * When used on a variable definition (which creates a type overload, {@code int} is not the same type as + * {@code immutable int}), this prevents use of any methods which could directly or + * indirectly set a field (or setting of fields directly). + * Mutable fields may be cast to immutable fields, but not vice versa. + */ + IMMUTABLE; +} diff --git a/src/main/java/com/laytonsmith/core/objects/ObjectDefinition.java b/src/main/java/com/laytonsmith/core/objects/ObjectDefinition.java new file mode 100644 index 0000000000..fdfc168988 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/objects/ObjectDefinition.java @@ -0,0 +1,298 @@ +package com.laytonsmith.core.objects; + +import com.laytonsmith.PureUtilities.ObjectHelpers; +import com.laytonsmith.PureUtilities.ObjectHelpers.Equals; +import com.laytonsmith.PureUtilities.ObjectHelpers.HashCode; +import com.laytonsmith.PureUtilities.ObjectHelpers.ToString; +import com.laytonsmith.PureUtilities.SmartComment; +import com.laytonsmith.core.FullyQualifiedClassName; +import com.laytonsmith.core.UnqualifiedClassName; +import com.laytonsmith.core.constructs.CClassType; +import com.laytonsmith.core.constructs.NativeTypeList; +import com.laytonsmith.core.constructs.Target; +import com.laytonsmith.core.environments.Environment; +import com.laytonsmith.core.exceptions.ConfigCompileException; +import com.laytonsmith.core.exceptions.ConfigCompileGroupException; +import com.laytonsmith.core.natives.interfaces.Commentable; +import com.laytonsmith.core.natives.interfaces.MAnnotation; +import com.laytonsmith.core.natives.interfaces.Mixed; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * Contains the definition of an object. Within certain limits, this information is available to the runtime, and in + * limited cases, can even be modified at runtime. For the large part, however, the data in an ObjectDefinition is + * read only. + * + * Everything is an object at the core, but there are subtypes of Object that have special handling, such as enums or + * annotations, but those are none-the-less represented in this class. + */ +@HashCode +@Equals +public class ObjectDefinition implements Commentable { + @ToString + private final List annotations; + @ToString + private final AccessModifier accessModifier; + @ToString + private final Set objectModifiers; + @ToString + private final ObjectType objectType; + @ToString + private final CClassType type; + @ToString + private final Set superclasses; + @ToString + private final Set interfaces; + private final CClassType containingClass; + private final Target definitionTarget; + private final Map> properties; + private final SmartComment classComment; + private final List genericParameters; + private final Class nativeClass; + + private Set qualifiedSuperclasses; + private Set qualifiedInterfaces; + + public ObjectDefinition(AccessModifier accessModifier, Set objectModifiers, ObjectType objectType, + CClassType type, + Set superclasses, Set interfaces, + CClassType containingClass, Target t, + Map> properties, List annotations, + SmartComment classComment, List genericParameters, Class nativeClass) { + this.accessModifier = accessModifier; + this.objectModifiers = objectModifiers; + this.objectType = objectType; + this.type = type; + this.superclasses = superclasses; + this.interfaces = interfaces; + this.containingClass = containingClass; + this.definitionTarget = t; + this.properties = properties; + this.annotations = annotations; + this.classComment = classComment; + this.genericParameters = genericParameters; + this.nativeClass = nativeClass; + } + + /** + * Returns the class name + * @return + */ + public String getClassName() { + return getName(); + } + + /** + * Returns the FQCN of this object. + * @return + */ + public FullyQualifiedClassName getFQCN() { + return this.type.getFQCN(); + } + + @Override + public boolean equals(Object obj) { + if(this == obj) { + return true; + } + if(obj == null) { + return false; + } + if(getClass() != obj.getClass()) { + return false; + } + final ObjectDefinition other = (ObjectDefinition) obj; + if(!Objects.equals(this.type, other.type)) { + return false; + } + return true; + } + + public boolean exactlyEquals(Object obj) { + return ObjectHelpers.DoEquals(this, obj); + } + + @Override + public int hashCode() { + return ObjectHelpers.DoHashCode(this); + } + + @Override + public String toString() { + return ObjectHelpers.DoToString(this); + } + + /** + * Gets the access modifier associated with this class + * @return + */ + public AccessModifier getAccessModifier() { + return accessModifier; + } + + /** + * Gets the object modifiers associated with this class + * @return + */ + public Set getObjectModifiers() { + if(objectModifiers.isEmpty()) { + return EnumSet.noneOf(ObjectModifier.class); + } + return EnumSet.copyOf(objectModifiers); + } + + /** + * Gets the type of this object, i.e. if it is a class or an enum, etc. + * @return + */ + public ObjectType getObjectType() { + return objectType; + } + + /** + * Returns the type of the class + * @return + */ + public CClassType getType() { + return type; + } + + /** + * Returns the name of the class. This is exactly equivalent to {@code getType().getFQCN().getFQCN()}. + * @return + */ + public String getName() { + return type.getFQCN().getFQCN(); + } + + private volatile boolean classesQualified = false; + /** + * Qualifies the unqualified class names used internally, and allows {@link #getSuperclasses()} and + * {@link #getInterfaces()} to be used. If this method is not called first, those methods will throw an Error. + * Calling this method more than once does nothing, but is not an error. This normally should be done as part of + * the compilation process. + * @param env + * @throws ConfigCompileGroupException If one or more of the classes couldn't be found. + */ + public void qualifyClasses(Environment env) throws ConfigCompileGroupException { + if(classesQualified) { + return; + } + synchronized(this) { + if(classesQualified) { + return; + } + Set uhohs = new HashSet<>(); + @SuppressWarnings("LocalVariableHidesMemberVariable") + Set superclasses = new HashSet<>(); + @SuppressWarnings("LocalVariableHidesMemberVariable") + Set interfaces = new HashSet<>(); + for(UnqualifiedClassName ucn : this.superclasses) { + try { + superclasses.add(CClassType.get(ucn.getFQCN(env))); + } catch (ClassNotFoundException ex) { + uhohs.add(new ConfigCompileException("Could not find " + ucn.getUnqualifiedClassName(), + ucn.getTarget(), ex)); + } + } + for(UnqualifiedClassName ucn : this.interfaces) { + try { + interfaces.add(CClassType.get(ucn.getFQCN(env))); + } catch (ClassNotFoundException ex) { + uhohs.add(new ConfigCompileException("Could not find " + ucn.getUnqualifiedClassName(), + ucn.getTarget(), ex)); + } + } + if(!uhohs.isEmpty()) { + throw new ConfigCompileGroupException(uhohs); + } + this.qualifiedSuperclasses = superclasses; + this.qualifiedInterfaces = interfaces; + classesQualified = true; + } + } + + /** + * Returns a List of superclasses. + * @return + */ + public Set getSuperclasses() { + if(qualifiedSuperclasses == null) { + throw new Error("qualifyClasses() must be called before getSuperclasses can be used (" + getType() + ")"); + } + return new HashSet<>(qualifiedSuperclasses); + } + + /** + * Returns a list of implementing interfaces. + * @return + */ + public Set getInterfaces() { + if(qualifiedSuperclasses == null) { + throw new Error("qualifyClasses() must be called before getInterfaces can be used (" + getType() + ")"); + } + return new HashSet<>(qualifiedInterfaces); + } + + /** + * Returns the ClassType that contains this class. If this is not an inner class, this value will be null. + * @return + */ + public CClassType getContainingClass() { + return containingClass; + } + + /** + * Returns the code target where the class was originally defined. Native classes have this value set, but it is a + * synthesized value, with line/column set to 0, and the file set to a fake file name. For instance + * "/Natives:/ms/lang/string.ms" + * @return + */ + public Target getDefinitionTarget() { + return definitionTarget; + } + + public Map> getElements() { + return properties; + } + + public List getAnnotations() { + return annotations; + } + + @Override + public SmartComment getElementComment() { + return classComment; + } + + public List getGenericParameters() { + return genericParameters; + } + + /** + * Checks if this is a native class, and can be properly cast to an actual native java class. If this + * returns true, then calling one of the methods in {@link NativeTypeList} will most certainly succeed. + * @return True if this is a native class, false otherwise. + */ + public boolean isNative() { + if(!getObjectModifiers().contains(ObjectModifier.NATIVE)) { + // If it doesn't claim to be native, it certainly isn't. + return false; + } + try { + // We want to ensure that if we attempt to instantiate this + // through the native type list, it will succeed, so we actually + // do that test here. + NativeTypeList.getNativeClassOrInterfaceRunner(type.getFQCN()); + return true; + } catch (ClassNotFoundException ex) { + return false; + } + } + +} diff --git a/src/main/java/com/laytonsmith/core/objects/ObjectDefinitionNotFoundException.java b/src/main/java/com/laytonsmith/core/objects/ObjectDefinitionNotFoundException.java new file mode 100644 index 0000000000..1de7ffd132 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/objects/ObjectDefinitionNotFoundException.java @@ -0,0 +1,20 @@ +package com.laytonsmith.core.objects; + +/** + * This exception is thrown if the ObjectDefinition could not be found. + */ +public class ObjectDefinitionNotFoundException extends Exception { + + public ObjectDefinitionNotFoundException() { + super(); + } + + public ObjectDefinitionNotFoundException(String message) { + super(message); + } + + public ObjectDefinitionNotFoundException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/com/laytonsmith/core/objects/ObjectDefinitionTable.java b/src/main/java/com/laytonsmith/core/objects/ObjectDefinitionTable.java new file mode 100644 index 0000000000..5bfe1d0fbf --- /dev/null +++ b/src/main/java/com/laytonsmith/core/objects/ObjectDefinitionTable.java @@ -0,0 +1,230 @@ +package com.laytonsmith.core.objects; + +import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery; +import com.laytonsmith.PureUtilities.Common.FileUtil; +import com.laytonsmith.PureUtilities.Common.StackTraceUtils; +import com.laytonsmith.PureUtilities.Common.StringUtils; +import com.laytonsmith.PureUtilities.ZipReader; +import com.laytonsmith.annotations.typeof; +import com.laytonsmith.core.FullyQualifiedClassName; +import com.laytonsmith.core.MethodScriptCompiler; +import com.laytonsmith.core.compiler.TokenStream; +import com.laytonsmith.core.constructs.NativeTypeList; +import com.laytonsmith.core.constructs.Target; +import com.laytonsmith.core.environments.Environment; +import com.laytonsmith.core.exceptions.ConfigCompileException; +import com.laytonsmith.core.exceptions.ConfigCompileGroupException; +import com.laytonsmith.core.natives.interfaces.Mixed; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.Spliterator; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; +/** + * Contains the underlying definitions of all classes in the ecosystem. Both user created classes and native classes are + * added to this list, with the exception that native classes are parsed and optimized at Java compile time, to save + * startup time. The native classes must be defined within the src/methodscript folder. Eventually, all references to + * the native classes using the {@link NativeTypeList} will go away, and be replaced by this object, which will contain + * all known classes, including "native" classes written purely in MethodScript, as well as user classes (at runtime.) + */ +public final class ObjectDefinitionTable implements Iterable { + + /** + * The String is the fully qualified class name. There are convenience methods for accepting a FQCN to get the + * value, but the underlying item is a String. + */ + private final Map classList; + private final AtomicBoolean nativeTypesAdded = new AtomicBoolean(false); + + private ObjectDefinitionTable() { + // Go ahead and set the size to that of the native class list, whether or not it actually gets added + classList = new java.util.concurrent.ConcurrentHashMap<>(NativeTypeList.getNativeTypeList().size()); + } + + /** + * Returns a new instance of the ObjectDefinitionTable with the native classes pre-populated. This is the usual + * use case for creating a new instance. + * @param env The environment. It MUST include a CompilerEnvironment. The GlobalEnv may be a mock environment. + * @param envs The target runtime environments + * @return + */ + public static ObjectDefinitionTable GetNewInstance(Environment env, + Set> envs) { + ObjectDefinitionTable table = new ObjectDefinitionTable(); + table.addNativeTypes(env, envs); + return table; + } + + /** + * Unless you have a special use case, use {@link #GetNewInstance()} instead of this method. + *

+ * In normal use cases, the native types should all be added. However, for testing and other meta use cases, it + * may be desirable to have a totally blank table. This method returns such an instance. + * @return A new, empty table. + */ + public static ObjectDefinitionTable GetBlankInstance() { + return new ObjectDefinitionTable(); + } + + /** + * Adds the native types to the instance. If this has already been called, it is not an error to call it again, + * but no changes will be made. + * @param env The environment. It MUST include a CompilerEnvironment. The GlobalEnv may be a mock environment. + * @param envs The target runtime environments + */ + public void addNativeTypes(Environment env, Set> envs) { + if(nativeTypesAdded.compareAndSet(false, true)) { + // We have two types of onboarding, normal onboarding, and fallback onboarding. + // Normal onboarding is when the native code was properly compiled in, and we + // hydrate from a pre-compiled version of the native code. This should be the + // normal operation, but if for whatever reason that file isn't present, we + // still want to properly manage. Fallback onboarding is when we scan the + // native source files and compile them ourselves. This is actually the normal + // code path when compiling the java code, so we do need to support this anyhow. + List oops = new ArrayList<>(); + if(ObjectDefinitionTable.class.getResource("/nativeSource.ser") != null) { + // Normal onboarding + throw new UnsupportedOperationException(); + } else { + // Fallback onboarding + Queue msFiles = new LinkedList<>(); + try { + Queue q = new LinkedList<>(); + File root = new File(ObjectDefinitionTable.class.getResource("/nativeSource").toExternalForm()); + ZipReader reader = new ZipReader(root); + q.addAll(Arrays.asList(reader.listFiles())); + // Flatten the structure, so we get a full list of files (no directories) + while(q.peek() != null) { + ZipReader r = new ZipReader(q.peek()); + if(r.isDirectory()) { + q.addAll(Arrays.asList(r.listFiles())); + q.poll(); + } else { + msFiles.add(q.poll()); + } + } + } catch (IOException ex) { + oops.add(ex); + } + msFiles.stream() + .parallel() + .forEach((file) -> { + try { + String script = FileUtil.read(file); + TokenStream ts = MethodScriptCompiler.lex(script, env, file, true); + MethodScriptCompiler.compile(ts, env, envs); + } catch (ConfigCompileGroupException g) { + oops.addAll(g.getList()); + } catch (IOException | ConfigCompileException ex) { + oops.add(ex); + } + }); + if(!oops.isEmpty()) { + List b = new ArrayList<>(); + for(Exception e : oops) { + if(e instanceof ConfigCompileException) { + ConfigCompileException cce = (ConfigCompileException) e; + b.add(cce.getMessage() + " " + cce.getFile() + ":" + + cce.getLineNum() + "." + cce.getColumn()); + } else { + b.add(e.getMessage() + "\n" + StackTraceUtils.GetStacktrace(e)); + } + } + throw new Error("One or more exceptions occured while trying to compile the native files!\n" + + StringUtils.Join(b, "\n")); + } + } + } + } + + @Override + public Iterator iterator() { + return classList.values().iterator(); + } + + @Override + public void forEach(Consumer action) { + classList.values().forEach(action); + } + + @Override + public Spliterator spliterator() { + return classList.values().spliterator(); + } + + private ObjectDefinition get(String fullyQualifiedClassName) throws ObjectDefinitionNotFoundException { + if(classList.containsKey(fullyQualifiedClassName)) { + return classList.get(fullyQualifiedClassName); + } else { + throw new ObjectDefinitionNotFoundException(fullyQualifiedClassName + " could not be found."); + } + } + + /** + * Returns the ObjectDefinition for the given type. If the type cannot be found, an exception is thrown. + * @param fullyQualifiedClassName The FQCN to find + * @return The ObjectDefinition + * @throws ObjectDefinitionNotFoundException If the type cannot be found, because it has not yet been registered. + */ + public ObjectDefinition get(FullyQualifiedClassName fullyQualifiedClassName) + throws ObjectDefinitionNotFoundException { + return get(fullyQualifiedClassName.getFQCN()); + } + + /** + * Returns the ObjectDefinition for a native class. It is generally impossible for this to be missing an + * ObjectDefintion, since that is generated automatically, so the underlying + * {@link ObjectDefinitionNotFoundException} that would normally be thrown is re-wrapped as an Error. + * @param clazz The Java Class to get an ObjectDefinition for + * @return The underlying ObjectDefinition + */ + public ObjectDefinition get(Class clazz) { + try { + return get(ClassDiscovery.GetClassAnnotation(clazz, typeof.class).value()); + } catch (ObjectDefinitionNotFoundException ex) { + throw new Error("Missing ObjectDefinition for native class: " + clazz.getName(), ex); + } + } + + /** + * Adds a new ObjectDefinition to the ObjectTable. + * @param object The ObjectDefinition to add. + * @param t + * @throws DuplicateObjectDefinitionException If an object with this name already exists. + */ + public void add(ObjectDefinition object, Target t) throws DuplicateObjectDefinitionException { + if(this.classList.containsKey(object.getClassName())) { + String msg = "The class " + object.getClassName() + " was attempted to be redefined."; + boolean isCopy = false; + if(this.classList.get(object.getClassName()).exactlyEquals(object)) { + msg += " The object appears to be an identical definition, is code running twice that shouldn't?"; + isCopy = true; + } + throw new DuplicateObjectDefinitionException(msg, t, isCopy); + } + this.classList.put(object.getClassName(), object); + } + + /** + * Considered a "meta" operation, this should never be called in the course of normal operation, as it is an + * expensive operation. It should only be called if the user code specifically uses reflection mechanisms. + *

+ * While a copy is returned, it is intentionally shallow. It is not expected that new classes are added or any + * removed, but the internal references may be changed, within the allowed limits of ObjectDefinition. + * @return A copy of the ObjectDefinitionTable + */ + public Set getObjectDefinitionSet() { + return new HashSet<>(classList.values()); + } + + +} diff --git a/src/main/java/com/laytonsmith/core/objects/ObjectModifier.java b/src/main/java/com/laytonsmith/core/objects/ObjectModifier.java new file mode 100644 index 0000000000..6e17fa24ed --- /dev/null +++ b/src/main/java/com/laytonsmith/core/objects/ObjectModifier.java @@ -0,0 +1,39 @@ +package com.laytonsmith.core.objects; + +import com.laytonsmith.annotations.ExposedElement; +import com.laytonsmith.annotations.MEnum; + +/** + * + * @author cailin + */ +@MEnum("ms.lang.ObjectModifier") +public enum ObjectModifier { + /** + * A final class is one that cannot be extended by subclasses. Only a non-abstract class can use this keyword, as + * interfaces and abstract classes must be extended/implemented for its existence to make sense. + */ + FINAL, + /** + * A static class is one that is not tied to the containing class's instance scope, but is tied to the static scope. + * This in not usable in top level classes, as it wouldn't make sense otherwise. + */ + STATIC, + /** + * An abstract class is one that cannot be instantiated directly, only non-abstract subclasses can be. It is an + * error for a class to be both final and abstract in user code, though this restriction is not enforced for system + * level code (which is taken advantage of by enums, so that ms.lang.enum cannot be subclassed or instantiated). + */ + ABSTRACT, + /** + * A native class maps to a class defined in the java code. This modifier is required on the class if any of the + * methods or fields are defined as native. And should generally be used for performance enhancement reasons if + * there does exist a native class. Additionally, the signatures of the methods and fields that are defined + * in this class must match the elements tagged with the {@link ExposedElement} annotations, or it will be an error. + */ + NATIVE, + /** + * An immutable object is one in which none of the fields may be set, other than within the constructor. + */ + IMMUTABLE; +} diff --git a/src/main/java/com/laytonsmith/core/objects/ObjectType.java b/src/main/java/com/laytonsmith/core/objects/ObjectType.java new file mode 100644 index 0000000000..ce41775d22 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/objects/ObjectType.java @@ -0,0 +1,92 @@ +package com.laytonsmith.core.objects; + +import com.laytonsmith.annotations.MEnum; + +/** + * + * @author cailin + */ +@MEnum("ms.lang.ObjectType") +public enum ObjectType { + // Maybe consider making this an extendable object type so extensions can add more types of objects? + // Probably not, that's fairly fundamental, but it may be worth thinking about. + /** + * A class is defined with the {@code class} keyword and represents a class that is instantiatable (in general) and + * can (in general) be extended, with the subclass inheriting this class's methods. + */ + CLASS(true, true, true, true), + /** + * An abstract class is defined with the {@code abstract class} keywords, and represents a class that can be + * extended, with the subclass inheriting this class's methods. It cannot be directly instantiated, however + */ + ABSTRACT(false, true, true, true), + /** + * An interface is defined with the {@code interface} keyword, and represents an interface. Subclasses can implement + * this interface, which makes them able to be grouped together with other interfaces of the same type. No methods + * may be implemented in the class, it merely defines the methods that other classes should implement. + */ + INTERFACE(false, false, false, true), + /** + * An annotation is a supplementary tag that can be tagged onto various other elements, to provide meta information + * about that class. + */ + ANNOTATION(false, false, false, true), + /** + * An enum class is one that is a list of values. The values themselves are instances of this enum, which can + * otherwise function like a final class, and provide custom methods within the class. + */ + ENUM(false, true, true, true), + /** + * A mask is a class that is similar to an enum, but is required to have an ordinal attached to it. It is limited to + * 64 elements in the mask, and each element is required to return a value that is 2*n where n is the ordinal place. + * This way, a single mask value can contain multiple enum values within. + */ + MASK(false, true, true, true); + + private final boolean isInstantiatable; + private final boolean extendsMixed; + private final boolean canUseExtends; + private final boolean canUseImplements; + + private ObjectType(boolean isInstantiatable, boolean extendsMixed, boolean canUseExtends, + boolean canUseImplements) { + this.isInstantiatable = isInstantiatable; + this.extendsMixed = extendsMixed; + this.canUseExtends = canUseExtends; + this.canUseImplements = canUseImplements; + } + + + /** + * Returns true if this type can be instantiated. That is, if it is compatible with the {@code new} keyword. + * @return + */ + public boolean isInstantiatable() { + return this.isInstantiatable; + } + + /** + * Returns true if this object type inherently extends mixed. For instance, interfaces do not, but classes do. + * @return + */ + public boolean extendsMixed() { + return this.extendsMixed; + } + + /** + * Returns true if this object type can use the extends keyword. + * @return + */ + public boolean canUseExtends() { + return this.canUseExtends; + } + + /** + * Returns true if this object type can use the implements keyword. + * @return + */ + public boolean canUseImplements() { + return this.canUseImplements; + } + +} diff --git a/src/main/java/com/laytonsmith/core/objects/UserObject.java b/src/main/java/com/laytonsmith/core/objects/UserObject.java new file mode 100644 index 0000000000..f370fcd56e --- /dev/null +++ b/src/main/java/com/laytonsmith/core/objects/UserObject.java @@ -0,0 +1,181 @@ +package com.laytonsmith.core.objects; + +import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.core.Documentation; +import com.laytonsmith.core.MSVersion; +import com.laytonsmith.core.Method; +import com.laytonsmith.core.Script; +import com.laytonsmith.core.constructs.CClassType; +import com.laytonsmith.core.constructs.CNull; +import com.laytonsmith.core.constructs.Construct; +import com.laytonsmith.core.constructs.Target; +import com.laytonsmith.core.environments.Environment; +import com.laytonsmith.core.natives.interfaces.Mixed; +import java.net.URL; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * A UserObject represents an instance of an object that was defined in MethodScript, i.e. we have no native + * class reference here. + */ +public class UserObject implements Mixed { + + private static int objectIdCounter = 0; + + private final Environment env; + private final Target t; + private final ObjectDefinition objectDefinition; + + private final Map fieldTable; + + private final Mixed nativeObject; + + private final int objectId; + + /** + * Creates a new UserObject. + * @param t The code target where this element is being created + * @param parent The parent script. This may be null, but ONLY if this is intended on being used as + * an invalid object. The default values will not be properly populated if this is null. + * @param env The environment + * @param objectDefinition The object definition this object is based on. + * @param nativeObject If there is an underlying native object, this should be sent along with this object. However, + * if this is not a native object, this should be null. + */ + public UserObject(Target t, Script parent, Environment env, ObjectDefinition objectDefinition, Mixed nativeObject) { + this.t = t; + this.env = env; + this.objectDefinition = objectDefinition; + this.nativeObject = nativeObject; + this.objectId = objectIdCounter++; + this.fieldTable = new HashMap<>(); + for(Map.Entry> e : objectDefinition.getElements().entrySet()) { + // Fields can only have one element definition, so if the list contains more than one, it is + // certainly a method. + // TODO + if(e.getValue().size() > 1) { + continue; + } + ElementDefinition ed = e.getValue().get(0); + if(ed.getMethod() != null) { + continue; + } + if(parent == null) { + fieldTable.put(e.getKey(), CNull.UNDEFINED); + } else { + Mixed value = parent.eval(ed.getDefaultValue(), env); + fieldTable.put(e.getKey(), value); + } + } + } + + @Override + public String val() { + List toStrings = this.objectDefinition.getElements().get("toString"); + if(toStrings != null) { + for(ElementDefinition ed : toStrings) { + Method m = ed.getMethod(); + if(m != null) { + if(m.getParameters().length == 0) { + return m.executeCallable(env, t).val(); + } + } + } + } + // Use the default toString + return objectDefinition.getClassName() + "@" + String.format("0x%X", objectId); + } + + @Override + public void setTarget(Target target) { + // + } + + @Override + public Target getTarget() { + return t; + } + + @Override + public Mixed clone() throws CloneNotSupportedException { + throw new UnsupportedOperationException("UserObject clone"); + } + + @Override + public String getName() { + return objectDefinition.getName(); + } + + @Override + public String docs() { + return "TODO"; + } + + @Override + public Version since() { + return MSVersion.V0_0_0; + } + + @Override + public CClassType[] getSuperclasses() { + return objectDefinition.getSuperclasses().toArray(new CClassType[objectDefinition.getSuperclasses().size()]); + } + + @Override + public CClassType[] getInterfaces() { + return objectDefinition.getInterfaces().toArray(new CClassType[objectDefinition.getInterfaces().size()]); + } + + @Override + public ObjectType getObjectType() { + return objectDefinition.getObjectType(); + } + + @Override + public Set getObjectModifiers() { + return objectDefinition.getObjectModifiers(); + } + + @Override + public AccessModifier getAccessModifier() { + return objectDefinition.getAccessModifier(); + } + + @Override + public CClassType getContainingClass() { + return objectDefinition.getContainingClass(); + } + + @Override + public boolean isInstanceOf(CClassType type) { + return Construct.isInstanceof(this, type); + } + + @Override + public boolean isInstanceOf(Class type) { + return Construct.isInstanceof(this, type); + } + + @Override + public CClassType typeof() { + return objectDefinition.getType(); + } + + @Override + public URL getSourceJar() { + return null; + } + + @Override + public Class[] seeAlso() { + return new Class[0]; + } + + public T getNativeObject(Class type) { + return (T) nativeObject; + } + +} diff --git a/src/main/java/com/laytonsmith/core/packetjumper/PacketInfo.java b/src/main/java/com/laytonsmith/core/packetjumper/PacketInfo.java deleted file mode 100644 index 50668ded3e..0000000000 --- a/src/main/java/com/laytonsmith/core/packetjumper/PacketInfo.java +++ /dev/null @@ -1,77 +0,0 @@ - -package com.laytonsmith.core.packetjumper; - -import com.laytonsmith.PureUtilities.Common.StringUtils; -import java.lang.reflect.Constructor; -import java.util.ArrayList; -import java.util.List; - -/** - * - */ -public class PacketInfo implements Comparable { - private final int packetID; - private final String packetName; - private final Class packetClass; - private final Constructor[] constructors; - private final Class[][] arguments; - private final String docs; - - public PacketInfo(Class packetClass) { - this.packetClass = packetClass; - constructors = (Constructor[])packetClass.getConstructors(); - arguments = new Class[constructors.length][]; - for(int i = 0; i < constructors.length; i++){ - Constructor constructor = constructors[i]; - arguments[i] = constructor.getParameterTypes(); - } - packetID = Integer.parseInt(packetClass.getSimpleName().replaceAll(".*?(\\d+).*", "$1")); - packetName = packetClass.getSimpleName().replaceAll(".*\\d+(.*)", "$1"); - //TODO: Parse docs - docs = "Docs coming soon!"; - } - - public int getPacketID() { - return packetID; - } - - public String getPacketName() { - return packetName; - } - - public Class getPacketClass() { - return packetClass; - } - - public Constructor[] getConstructors() { - return constructors; - } - - public Class[] getArguments() { - Class[] a = new Class[arguments.length]; - System.arraycopy(arguments, 0, a, 0, arguments.length); - return a; - } - - public String getDocs() { - return docs; - } - - @Override - public String toString() { - List l = new ArrayList<>(); - for(Class[] args : arguments){ - l.add(StringUtils.Join(args, ", ")); - } - return packetClass.getName() + "(" + StringUtils.Join(l, " | ") + ")"; - } - - @Override - public int compareTo(PacketInfo o) { - if(this.packetID == o.packetID){ - return 0; - } - return this.packetID < o.packetID?-1:1; - } - - } diff --git a/src/main/java/com/laytonsmith/core/packetjumper/PacketInstance.java b/src/main/java/com/laytonsmith/core/packetjumper/PacketInstance.java deleted file mode 100644 index 9e256cb8b1..0000000000 --- a/src/main/java/com/laytonsmith/core/packetjumper/PacketInstance.java +++ /dev/null @@ -1,25 +0,0 @@ - -package com.laytonsmith.core.packetjumper; - -import java.util.Set; - -/** - * - */ -public class PacketInstance { - private final Set data; - private final PacketInfo packetInfo; - - public PacketInstance(PacketInfo packetInfo, Set data){ - this.packetInfo = packetInfo; - this.data = data; - } - - public Set getData(){ - return this.data; - } - - public PacketInfo getPacketInfo(){ - return this.packetInfo; - } -} diff --git a/src/main/java/com/laytonsmith/core/packetjumper/PacketJumper.java b/src/main/java/com/laytonsmith/core/packetjumper/PacketJumper.java deleted file mode 100644 index 3dd23d099f..0000000000 --- a/src/main/java/com/laytonsmith/core/packetjumper/PacketJumper.java +++ /dev/null @@ -1,147 +0,0 @@ - -package com.laytonsmith.core.packetjumper; - -import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery; -import com.laytonsmith.PureUtilities.Web.WebUtility; -import com.laytonsmith.abstraction.MCPlayer; -import com.laytonsmith.core.constructs.Construct; -import java.io.IOException; -import java.util.Set; -import java.util.SortedSet; -import java.util.TreeSet; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * - * - */ -public class PacketJumper { - - private static boolean started = false; - private static SortedSet packetInfo; - private static Thread initializingThread = null; - private static String protocolDocs = ""; - public static void startup(){ - if(true) return; //TODO: - if(started){ - return; - } - packetInfo = new TreeSet<>(); - initializingThread = new Thread(new Runnable() { - - @Override - public void run() { -// try { -// protocolDocs = WebUtility.GetPageContents("http://mc.kev009.com/Protocol"); -// } catch (IOException ex) { -// Logger.getLogger(PacketJumper.class.getName()).log(Level.SEVERE, null, ex); -// } - - Class PacketClass = ClassDiscovery.getDefaultInstance().forFuzzyName("net\\.minecraft\\.server.*", "Packet").loadClass(); - - Set packets = ClassDiscovery.getDefaultInstance().loadClassesThatExtend(PacketClass); - for(Class packet : packets){ - packetInfo.add(new PacketInfo(packet)); - } - started = true; - } - }, "PacketJumperInitializer"); - initializingThread.start(); - for(PacketInfo p : getPacketInfo()){ - System.out.println(p); - } - } - - public static boolean started(){ - return started; - } - - private static void waitForInitialization() throws InterruptedException{ - if(initializingThread == null){ - startup(); - } - if(initializingThread.isAlive()){ - //Wait for the startup thread, if it's running - initializingThread.join(); - } - } - - public static Set getPacketInfo(){ - try { - waitForInitialization(); - } catch (InterruptedException ex) { - Logger.getLogger(PacketJumper.class.getName()).log(Level.SEVERE, null, ex); - } - return new TreeSet<>(packetInfo); - } - - public static void fakePacketToPlayer(MCPlayer player, PacketInstance packet){ - try { - waitForInitialization(); - } catch (InterruptedException ex) { - Logger.getLogger(PacketJumper.class.getName()).log(Level.SEVERE, null, ex); - } - //TODO: - throw new UnsupportedOperationException("Not supported yet."); - } - - public static void fakePacketFromPlayer(MCPlayer player, PacketInstance packet){ - try { - waitForInitialization(); - } catch (InterruptedException ex) { - Logger.getLogger(PacketJumper.class.getName()).log(Level.SEVERE, null, ex); - } - //TODO: - throw new UnsupportedOperationException("Not supported yet."); - } - - //TODO: Add interceptor listeners, and make this support more than one. It should - //probably support binds even. - - public static void setPacketRecievedInterceptor(int id, PacketHandler handler){ - try { - waitForInitialization(); - } catch (InterruptedException ex) { - Logger.getLogger(PacketJumper.class.getName()).log(Level.SEVERE, null, ex); - } - //TODO: - throw new UnsupportedOperationException("Not supported yet."); - } - - public static void setPacketSentInterceptor(int id, PacketHandler handler){ - try { - waitForInitialization(); - } catch (InterruptedException ex) { - Logger.getLogger(PacketJumper.class.getName()).log(Level.SEVERE, null, ex); - } - //TODO: - throw new UnsupportedOperationException("Not supported yet."); - } - - public static PacketInstance getPacket(int id, Construct ... args){ - try { - waitForInitialization(); - } catch (InterruptedException ex) { - Logger.getLogger(PacketJumper.class.getName()).log(Level.SEVERE, null, ex); - } - //TODO: - throw new UnsupportedOperationException("Not supported yet."); - } - - /** - * Used for packet interceptors, this allows an opportunity for a class to - * manipulate a packet before it is processed/sent - */ - public static interface PacketHandler{ - /** - * The packet to be processed/sent is passed to this method, and it is expected - * that this method returns a packet (which is actually going to be sent) or - * null, which cancels the packet send entirely. - * @param player The player sending/recieving the packet - * @param packet The packet in question - * @return - */ - PacketInstance Handle(MCPlayer player, PacketInstance packet); - } -} diff --git a/src/main/java/com/laytonsmith/core/profiler/GarbageCollectionDetector.java b/src/main/java/com/laytonsmith/core/profiler/GarbageCollectionDetector.java index 9197d30fc1..891e9453cd 100644 --- a/src/main/java/com/laytonsmith/core/profiler/GarbageCollectionDetector.java +++ b/src/main/java/com/laytonsmith/core/profiler/GarbageCollectionDetector.java @@ -1,25 +1,25 @@ - package com.laytonsmith.core.profiler; /** * - * + * */ final class GarbageCollectionDetector { - private Profiler profiler; + private final Profiler profiler; + public GarbageCollectionDetector(Profiler profiler) { this.profiler = profiler; } - + @Override - protected void finalize() throws Throwable { - if (profiler.queuedProfilePoints > 0) { - for (ProfilePoint p : profiler.operations.keySet()) { + protected void finalize() throws Throwable { + if(profiler.queuedProfilePoints > 0) { + for(ProfilePoint p : profiler.operations.keySet()) { p.garbageCollectorRun(); } } new GarbageCollectionDetector(profiler); } - + } diff --git a/src/main/java/com/laytonsmith/core/profiler/ProfilePoint.java b/src/main/java/com/laytonsmith/core/profiler/ProfilePoint.java index 8a356c1e17..7ff83c8889 100644 --- a/src/main/java/com/laytonsmith/core/profiler/ProfilePoint.java +++ b/src/main/java/com/laytonsmith/core/profiler/ProfilePoint.java @@ -4,18 +4,19 @@ /** * - * + * */ public class ProfilePoint implements Comparable { - private String name; + + private final String name; private String message; - boolean GCRun; - private Profiler parent; + boolean gcRun; + private final Profiler parent; private LogLevel granularity; public ProfilePoint(String name, Profiler parent) { this.name = name; - GCRun = false; + gcRun = false; this.parent = parent; this.message = null; } @@ -26,25 +27,25 @@ public String toString() { } void garbageCollectorRun() { - GCRun = true; + gcRun = true; } boolean wasGCd() { - return GCRun; + return gcRun; } - - public void stop(){ + + public void stop() { parent.stop(this); } public String getMessage() { return message; } - + public void setMessage(String newMessage) { message = newMessage; } - + /** * This is an arbitrary comparison, for the sake of fast tree searches. * @@ -58,18 +59,20 @@ public int compareTo(ProfilePoint o) { /** * Package private. - * @param granularity + * + * @param granularity */ void setGranularity(LogLevel granularity) { this.granularity = granularity; } - + /** * Returns the log level at which this profile point was registered. - * @return + * + * @return */ - public LogLevel getGranularity(){ + public LogLevel getGranularity() { return this.granularity; } - + } diff --git a/src/main/java/com/laytonsmith/core/profiler/Profiler.java b/src/main/java/com/laytonsmith/core/profiler/Profiler.java index d1675590d0..2488c6f7d9 100644 --- a/src/main/java/com/laytonsmith/core/profiler/Profiler.java +++ b/src/main/java/com/laytonsmith/core/profiler/Profiler.java @@ -2,11 +2,12 @@ import com.laytonsmith.PureUtilities.Common.DateUtils; import com.laytonsmith.PureUtilities.Common.FileUtil; +import com.laytonsmith.PureUtilities.Common.StreamUtils; import com.laytonsmith.PureUtilities.ExecutionQueue; +import com.laytonsmith.PureUtilities.ExecutionQueueImpl; import com.laytonsmith.PureUtilities.Preferences; import com.laytonsmith.PureUtilities.Preferences.Preference; import com.laytonsmith.core.LogLevel; -import com.laytonsmith.core.MethodScriptExecutionQueue; import com.laytonsmith.core.Static; import java.io.File; import java.io.IOException; @@ -18,22 +19,22 @@ import java.util.TreeMap; /** - * TODO: The following points need profile hooks: - * 1 - Execution Queue task run times - * 3 - Procedure execution run times (with parameters) + * TODO: The following points need profile hooks: 1 - Execution Queue task run times 3 - Procedure execution run times + * (with parameters) */ /** * - * + * */ public final class Profiler { - + /** - * Useful for test cases and other places where a profiler is needed, but not desired, - * this can be called to generate a fake Profiler. - * @return + * Useful for test cases and other places where a profiler is needed, but not desired, this can be called to + * generate a fake Profiler. + * + * @return */ - public static Profiler FakeProfiler(){ + public static Profiler FakeProfiler() { Profiler p = new Profiler(); p.profilerOn = false; return p; @@ -45,18 +46,17 @@ public static void Install(File initFile) throws IOException { } private static Preferences GetPrefs(File initFile) throws IOException { - List defaults = new ArrayList(Arrays.asList(new Preference[]{ - new Preference("profiler-on", "false", Preferences.Type.BOOLEAN, "Turns the profiler on or off. The profiler can cause a slight amount of lag, so generally speaking" - + " you don't leave it on during normal operation."), - new Preference("profiler-granularity", "1", Preferences.Type.INT, "Sets the granularity of the profiler. 1 logs some things, while 5 logs everything possible."), - new Preference("profiler-log", "logs/profiling/internal/%Y-%M-%D-profiler.log", Preferences.Type.STRING, "The location of the profiler output log. The following macros are supported" - + " and will expand to the specified values: %Y - Year, %M - Month, %D - Day, %h - Hour, %m - Minute, %s - Second"), - new Preference("write-to-file", "true", Preferences.Type.BOOLEAN, "If true, will write results out to file."), - new Preference("write-to-screen", "false", Preferences.Type.BOOLEAN, "If true, will write results out to screen."), - new Preference("profile-log-threshold", "0.005", Preferences.Type.DOUBLE, "If a profile point took less than this amount of time (in ms) to run, it won't be logged. This is good for reducing data blindness" - + " caused by too much data being displayed. Normally you only care about things that took longer than a certain amount, not things that took less than a certain amount. Setting this to 0" - + " will trigger everything."), - })); + List defaults = new ArrayList<>(Arrays.asList(new Preference[]{ + new Preference("profiler-on", "false", Preferences.Type.BOOLEAN, "Turns the profiler on or off. The profiler can cause a slight amount of lag, so generally speaking" + + " you don't leave it on during normal operation."), + new Preference("profiler-granularity", "1", Preferences.Type.INT, "Sets the granularity of the profiler. 1 logs some things, while 5 logs everything possible."), + new Preference("profiler-log", "logs/profiling/internal/%Y-%M-%D-profiler.log", Preferences.Type.STRING, "The location of the profiler output log. The following macros are supported" + + " and will expand to the specified values: %Y - Year, %M - Month, %D - Day, %h - Hour, %m - Minute, %s - Second"), + new Preference("write-to-file", "true", Preferences.Type.BOOLEAN, "If true, will write results out to file."), + new Preference("write-to-screen", "false", Preferences.Type.BOOLEAN, "If true, will write results out to screen."), + new Preference("profile-log-threshold", "0.005", Preferences.Type.DOUBLE, "If a profile point took less than this amount of time (in ms) to run, it won't be logged. This is good for reducing data blindness" + + " caused by too much data being displayed. Normally you only care about things that took longer than a certain amount, not things that took less than a certain amount. Setting this to 0" + + " will trigger everything.")})); Preferences prefs = new Preferences("CommandHelper", Static.getLogger(), defaults, "These settings control the integrated profiler"); prefs.init(initFile); return prefs; @@ -64,7 +64,7 @@ private static Preferences GetPrefs(File initFile) throws IOException { //Needs to be package protected Map operations; long queuedProfilePoints = 0; - + private LogLevel configGranularity; private boolean profilerOn; private String logFile; @@ -75,31 +75,33 @@ private static Preferences GetPrefs(File initFile) throws IOException { private double logThreshold; //To prevent file fights across threads, we only want one outputQueue. private static ExecutionQueue outputQueue; + @SuppressWarnings("checkstyle:membername") // Allow custom name since it more clearly describes the object. private final ProfilePoint NULL_OP = new ProfilePoint("NULL_OP", this); - - private Profiler(){ + + private Profiler() { //Private constructor for FakeProfiler - if (outputQueue == null) { - outputQueue = new MethodScriptExecutionQueue("CommandHelper-Profiler", "default"); + if(outputQueue == null) { + outputQueue = new ExecutionQueueImpl("CommandHelper-Profiler", "default"); } } - + + @SuppressWarnings("ResultOfObjectAllocationIgnored") public Profiler(File initFile) throws IOException { this(); prefs = GetPrefs(initFile); //We want speed here, not memory usage, so lets put an excessively large capacity, and excessively low load factor - operations = new HashMap(1024, 0.25f); + operations = new HashMap<>(1024, 0.25f); this.initFile = initFile; - configGranularity = LogLevel.getEnum((Integer) prefs.getPreference("profiler-granularity")); - if (configGranularity == null) { + configGranularity = LogLevel.getEnum(prefs.getIntegerPreference("profiler-granularity")); + if(configGranularity == null) { configGranularity = LogLevel.ERROR; } - profilerOn = (Boolean) prefs.getPreference("profiler-on"); - logFile = (String) prefs.getPreference("profiler-log"); - writeToFile = (Boolean) prefs.getPreference("write-to-file"); - writeToScreen = (Boolean) prefs.getPreference("write-to-screen"); - logThreshold = (Double) prefs.getPreference("profile-log-threshold"); + profilerOn = prefs.getBooleanPreference("profiler-on"); + logFile = prefs.getStringPreference("profiler-log"); + writeToFile = prefs.getBooleanPreference("write-to-file"); + writeToScreen = prefs.getBooleanPreference("write-to-screen"); + logThreshold = prefs.getDoublePreference("profile-log-threshold"); new GarbageCollectionDetector(this); //As a form of calibration, we want to "warm up" a point. //For whatever reason, this levels out the profile points pretty well. @@ -108,18 +110,16 @@ public Profiler(File initFile) throws IOException { } /** - * Starts a timer, and returns a profile point object, which should be used - * to stop this timer later. A special ProfilePoint is returned if this - * profile point shouldn't be logged based on the granularity settings, - * which short circuits the entire profiling process, for non-trigger - * points, which should speed operation considerably. + * Starts a timer, and returns a profile point object, which should be used to stop this timer later. A special + * ProfilePoint is returned if this profile point shouldn't be logged based on the granularity settings, which short + * circuits the entire profiling process, for non-trigger points, which should speed operation considerably. * * @param name The name to be used during logging * @param granularity * @return */ public ProfilePoint start(String name, LogLevel granularity) { - if (!isLoggable(granularity)) { + if(!isLoggable(granularity)) { return NULL_OP; } ProfilePoint p = new ProfilePoint(name, this); @@ -128,110 +128,108 @@ public ProfilePoint start(String name, LogLevel granularity) { } /** - * "Starts" an operation. Note that for each start, you must use EXACTLY one - * stop, with exactly the same object for operationName. Multiple profile - * points can share the same name, and they will be stacked and lined up + * "Starts" an operation. Note that for each start, you must use EXACTLY one stop, with exactly the same object for + * operationName. Multiple profile points can share the same name, and they will be stacked and lined up * accordingly. * - * @param operationName The name of the operation. A corresponding call to - * DoStop must be called with this exact same object. + * @param operationName The name of the operation. A corresponding call to DoStop must be called with this exact + * same object. * @param granularity The granularity at which to log. */ private void start0(ProfilePoint operationName, LogLevel granularity) { - if (operations.containsKey(operationName)) { + if(operations.containsKey(operationName)) { //Nope. Can't queue up multiple versions of the same //id throw new RuntimeException("Cannot queue the same profile point multiple times!"); } queuedProfilePoints++; operationName.setGranularity(granularity); - + //This line should ALWAYS be last in the function operations.put(operationName, System.nanoTime()); } - private final static Map indents = new TreeMap(); - static{ + private static final Map INDENTS = new TreeMap(); + + static { //Let's just warm it up some - for(int i = 0; i < 10; i++){ + for(int i = 0; i < 10; i++) { getIndent(i); } } - private static String getIndent(long count){ - if(!indents.containsKey(count)){ + + private static String getIndent(long count) { + if(!INDENTS.containsKey(count)) { StringBuilder b = new StringBuilder(); - for(int i = 0; i < count; i++){ + for(int i = 0; i < count; i++) { b.append(" "); } - indents.put(count, b.toString()); + INDENTS.put(count, b.toString()); } - return indents.get(count); + return INDENTS.get(count); } - private final static String gcString = " (however, the garbage collector was run during this profile point)"; + private static final String GC_STRING = " (however, the garbage collector was run during this profile point)"; + public void stop(ProfilePoint operationName) { //This line should ALWAYS be first in the function long stop = System.nanoTime(); - if (operationName == NULL_OP) { + if(operationName == NULL_OP) { return; } - if (!operations.containsKey(operationName)) { + if(!operations.containsKey(operationName)) { return; } long total = stop - operations.get(operationName); //1 million nano seconds in 1 ms. We want x.xxx ms shown, so divide by 1000, round (well, integer truncate, since it's faster), then divide by 1000 again. //voila, significant figure to the 3rd degree. double time = (total / 1000) / 1000.0; - if(time >= logThreshold){ + if(time >= logThreshold) { String stringTime = Double.toString(time); - if(stringTime.length() < 6 && stringTime.contains(".")){ - while(stringTime.length() < 6){ + if(stringTime.length() < 6 && stringTime.contains(".")) { + while(stringTime.length() < 6) { stringTime += "0"; } } stringTime += "ms"; - if(time > 1000){ + if(time > 1000) { //Let's change this to seconds, actually. - stringTime = Double.toString(((long)time) / 1000.0) + "sec"; + stringTime = Double.toString(((long) time) / 1000.0) + "sec"; } - String operationMessage = operationName.getMessage()!=null?" Message: "+operationName.getMessage():""; + String operationMessage = operationName.getMessage() != null ? " Message: " + operationName.getMessage() : ""; doLog("[" + stringTime + "][Lvl:" + (operationName.getGranularity().getLevel()) + "]:" + getIndent(queuedProfilePoints) - + operationName.toString() + operationMessage + (operationName.wasGCd()?gcString:"")); + + operationName.toString() + operationMessage + (operationName.wasGCd() ? GC_STRING : "")); } queuedProfilePoints--; } public boolean isLoggable(LogLevel granularity) { - if (!profilerOn || granularity == null) { + if(!profilerOn || granularity == null) { return false; } return granularity.getLevel() <= configGranularity.getLevel(); } /** - * Pushes a log to either the screen or the log file, depending on config - * settings. Arbitrary messages can be logged using this method. + * Pushes a log to either the screen or the log file, depending on config settings. Arbitrary messages can be logged + * using this method. * * @param message */ public void doLog(final String message) { - outputQueue.push(null, null, new Runnable() { - @Override - public void run() { - if (writeToScreen) { - System.out.println(message); - } - if (writeToFile) { - File file = new File(initFile.getParentFile(), DateUtils.ParseCalendarNotation(logFile)); - try { - FileUtil.write(DateUtils.ParseCalendarNotation("%Y-%M-%D %h:%m.%s") + ": " + message + Static.LF(), //Message to log - file, //File to output to - FileUtil.APPEND, //We want to append - true); //Create it for us if it doesn't exist - } catch (IOException ex) { - System.err.println("While trying to write to the profiler log file (" + file.getAbsolutePath() + "), recieved an IOException: " + ex.getMessage()); - } + outputQueue.push(null, null, () -> { + if(writeToScreen) { + StreamUtils.GetSystemOut().println(message); + } + if(writeToFile) { + File file = new File(initFile.getParentFile(), DateUtils.ParseCalendarNotation(logFile)); + try { + FileUtil.write(DateUtils.ParseCalendarNotation("%Y-%M-%D %h:%m.%s") + ": " + message + Static.LF(), //Message to log + file, //File to output to + FileUtil.APPEND, //We want to append + true); //Create it for us if it doesn't exist + } catch (IOException ex) { + StreamUtils.GetSystemErr().println("While trying to write to the profiler log file (" + file.getAbsolutePath() + "), received an IOException: " + ex.getMessage()); } - } }); } diff --git a/src/main/java/com/laytonsmith/core/snapins/CorePermissions.java b/src/main/java/com/laytonsmith/core/snapins/CorePermissions.java index 916e7ea1ad..bb9391c2d6 100644 --- a/src/main/java/com/laytonsmith/core/snapins/CorePermissions.java +++ b/src/main/java/com/laytonsmith/core/snapins/CorePermissions.java @@ -1,25 +1,24 @@ package com.laytonsmith.core.snapins; import com.laytonsmith.PureUtilities.Common.StringUtils; -import com.laytonsmith.core.CHVersion; +import com.laytonsmith.core.MSVersion; /** * - * + * */ public enum CorePermissions implements PackagePermission { FILE_READ("CORE.FILE.READ", ThreatLevel.CAUTIOUS, "This permission grants read access to the filesystem, via the VFS only," - + " therefore making it relatively safe compared to raw filesystem access.", CHVersion.V3_3_1), + + " therefore making it relatively safe compared to raw filesystem access.", MSVersion.V3_3_1), FILE_WRITE("CORE.FILE.WRITE", ThreatLevel.CAUTIOUS, "This permission grants write access to the filesystem, via the VFS only," - + " therefore making it relatively safe compared to raw filesystem access.", CHVersion.V3_3_1), + + " therefore making it relatively safe compared to raw filesystem access.", MSVersion.V3_3_1), FILE_RAW("CORE.FILE.RAW", ThreatLevel.DANGEROUS, "This permission grants raw read/write access to the filesystem, outside of the" - + " VFS, which on most systems could allow for full control of the system", CHVersion.V3_3_1, new PackagePermission[]{FILE_READ, FILE_WRITE}), + + " VFS, which on most systems could allow for full control of the system", MSVersion.V3_3_1, new PackagePermission[]{FILE_READ, FILE_WRITE}), NETWORK("CORE.NETWORK", ThreatLevel.CAUTIOUS, "This permission grants raw network access. If filesystem access is not allowed," - + " this is a relatively safe permission to allow, but can be misused by malicious code to transmit sensitive data.", CHVersion.V3_3_1), + + " this is a relatively safe permission to allow, but can be misused by malicious code to transmit sensitive data.", MSVersion.V3_3_1), GLOBAL_PERSISTENCE("CORE.GLOBAL_PERSISTENCE", ThreatLevel.TRIVIAL, "This permission grants access to the global persistence storage." - + " This generally cannot be abused, other than to delete other package's data.", CHVersion.V3_3_1), - ; - + + " This generally cannot be abused, other than to delete other package's data.", MSVersion.V3_3_1),; + /** * This is the namespace for the permission */ @@ -29,30 +28,28 @@ public enum CorePermissions implements PackagePermission { */ private final ThreatLevel threatLevel; private final String docs; - private final CHVersion since; + private final MSVersion since; /** - * If a permission is implied, then it will automatically be granted - * as well, if the two permissions are in the same requirement category. - * For instance, giving CORE.FILE.RAW implies CORE.FILE.READ and CORE.FILE.WRITE, - * so those two won't be separately requested. - * TODO: Provide access to this data + * If a permission is implied, then it will automatically be granted as well, if the two permissions are in the same + * requirement category. For instance, giving CORE.FILE.RAW implies CORE.FILE.READ and CORE.FILE.WRITE, so those two + * won't be separately requested. TODO: Provide access to this data */ private final PackagePermission[] implied; - - private CorePermissions(String permission, ThreatLevel threatLevel, String docs, CHVersion since) { - this(permission, threatLevel, docs, since, (PackagePermission[])null); + + private CorePermissions(String permission, ThreatLevel threatLevel, String docs, MSVersion since) { + this(permission, threatLevel, docs, since, (PackagePermission[]) null); } - - private CorePermissions(String permission, ThreatLevel threatLevel, String docs, CHVersion since, PackagePermission implied){ + + private CorePermissions(String permission, ThreatLevel threatLevel, String docs, MSVersion since, PackagePermission implied) { this(permission, threatLevel, docs, since, new PackagePermission[]{implied}); } - - private CorePermissions(String permission, ThreatLevel threatLevel, String docs, CHVersion since, PackagePermission [] implied){ + + private CorePermissions(String permission, ThreatLevel threatLevel, String docs, MSVersion since, PackagePermission[] implied) { this.namespace = permission.split("\\."); this.threatLevel = threatLevel; this.docs = docs; this.since = since; - if(implied == null){ + if(implied == null) { this.implied = new PackagePermission[]{}; } else { this.implied = implied; @@ -82,8 +79,8 @@ public String docs() { } @Override - public CHVersion since() { + public MSVersion since() { return since; } - + } diff --git a/src/main/java/com/laytonsmith/core/snapins/PackagePermission.java b/src/main/java/com/laytonsmith/core/snapins/PackagePermission.java index cca854cc13..0e83741259 100644 --- a/src/main/java/com/laytonsmith/core/snapins/PackagePermission.java +++ b/src/main/java/com/laytonsmith/core/snapins/PackagePermission.java @@ -1,21 +1,19 @@ package com.laytonsmith.core.snapins; -import com.laytonsmith.core.CHVersion; +import com.laytonsmith.core.MSVersion; import com.laytonsmith.core.SimpleDocumentation; /** - * This should be implemented by permission enums. This provides a standard way - * to get permission information, without tying them down to a single enum class, - * so extensions can later provide different permissions, while - * + * This should be implemented by permission enums. This provides a standard way to get permission information, without + * tying them down to a single enum class, so extensions can later provide different permissions, while + * */ public interface PackagePermission extends SimpleDocumentation { - + /** - * This is a specially defined PackagePermission that should be used by - * code that has no security implications, if a permission is required. - * During permission checks, if this permission is returned, then no - * permissions are requested from the installation. + * This is a specially defined PackagePermission that should be used by code that has no security implications, if a + * permission is required. During permission checks, if this permission is returned, then no permissions are + * requested from the installation. */ public static final PackagePermission NO_PERMISSIONS_NEEDED = new PackagePermission() { @@ -40,62 +38,66 @@ public String docs() { } @Override - public CHVersion since() { + public MSVersion since() { return null; } - + }; - + /** - * Returns the name of the permission. Namespaces are supported, which - * are may be displayed in a tree hierarchy in the UI, to allow for - * quicker selections. - * @return + * Returns the name of the permission. Namespaces are supported, which are may be displayed in a tree hierarchy in + * the UI, to allow for quicker selections. + * + * @return */ - String [] getNamespace(); - + String[] getNamespace(); + /** * The threat level of this permission. - * @return + * + * @return */ ThreatLevel getThreatLevel(); - + /** * This should return getNamespace()[getNamespace().length - 1]. - * @return + * + * @return */ @Override String getName(); - + /** - * PackagePermission can be defined by a package to have a certain requirement level. - * The strings in the manifest should match one of the values in this enum. + * PackagePermission can be defined by a package to have a certain requirement level. The strings in the manifest + * should match one of the values in this enum. */ public static enum Requirement implements SimpleDocumentation { /** * If this permission is not granted, the package will not be installed */ - CRITICAL("If this permission is not granted, the package will not be installed", - CHVersion.V3_3_1), + CRITICAL("If this permission is not granted, the package will not be installed", + MSVersion.V3_3_1), /** - * If this permission is not granted, large portions of the code will not work, however, the main purpose - * of the package will still work fine. + * If this permission is not granted, large portions of the code will not work, however, the main purpose of the + * package will still work fine. */ IMPORTANT("If this permission is not granted, large portions of the code will not work, however, the main purpose" - + "of the package will still work fine.", CHVersion.V3_3_1), + + "of the package will still work fine.", MSVersion.V3_3_1), /** - * If this permission is not granted, some features of the package may not work, but all of the main features will work. - * User experience may be impacted though. + * If this permission is not granted, some features of the package may not work, but all of the main features + * will work. User experience may be impacted though. */ OPTIONAL("If this permission is not granted, some features of the package may not work, but all of the main features will work." - + " User experience may be impacted though.", CHVersion.V3_3_1); + + " User experience may be impacted though.", MSVersion.V3_3_1); + + private final String docs; + private final MSVersion since; - private String docs; - private CHVersion since; - private Requirement(String docs, CHVersion since){ + private Requirement(String docs, MSVersion since) { this.docs = docs; this.since = since; } + @Override public String getName() { return name(); @@ -107,43 +109,42 @@ public String docs() { } @Override - public CHVersion since() { + public MSVersion since() { return since; } - + } - + /** - * Each permission must define its own threat level. This is used by the UI - * to display various information about the + * Each permission must define its own threat level. This is used by the UI to display various information about the */ public static enum ThreatLevel implements SimpleDocumentation { /** - * No security risks could be introduced by granting this permission + * No security risks could be introduced by granting this permission */ - TRIVIAL("No security risks could be introduced by granting this permission ", CHVersion.V3_3_1), - + TRIVIAL("No security risks could be introduced by granting this permission ", MSVersion.V3_3_1), /** - * A malicious package may be able to cause issues for the administrator, - * but no permanent damage could happen to the system + * A malicious package may be able to cause issues for the administrator, but no permanent damage could happen + * to the system */ CAUTIOUS("A malicious package may be able to cause issues for the administrator," - + " but no permanent damage could happen to the system ", CHVersion.V3_3_1), + + " but no permanent damage could happen to the system ", MSVersion.V3_3_1), /** - * Malicious packages could cause serious damage to a system if this - * permission is granted. You should be sure you trust this package - * before granting this permission. + * Malicious packages could cause serious damage to a system if this permission is granted. You should be sure + * you trust this package before granting this permission. */ DANGEROUS("Malicious packages could cause serious damage to a system" + " if this permission is granted. You should be sure you" - + " trust this package before granting this permission. ", CHVersion.V3_3_1); + + " trust this package before granting this permission. ", MSVersion.V3_3_1); - private String docs; - private CHVersion since; - private ThreatLevel(String docs, CHVersion since){ + private final String docs; + private final MSVersion since; + + private ThreatLevel(String docs, MSVersion since) { this.docs = docs; this.since = since; } + @Override public String getName() { return name(); @@ -155,9 +156,9 @@ public String docs() { } @Override - public CHVersion since() { + public MSVersion since() { return since; } - + } } diff --git a/src/main/java/com/laytonsmith/core/taskmanager/CoreTaskType.java b/src/main/java/com/laytonsmith/core/taskmanager/CoreTaskType.java index e9450f8a83..49a84b7131 100644 --- a/src/main/java/com/laytonsmith/core/taskmanager/CoreTaskType.java +++ b/src/main/java/com/laytonsmith/core/taskmanager/CoreTaskType.java @@ -2,7 +2,7 @@ import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery; import com.laytonsmith.PureUtilities.Version; -import com.laytonsmith.core.CHVersion; +import com.laytonsmith.core.MSVersion; import com.laytonsmith.core.Documentation; import java.net.URL; @@ -10,24 +10,23 @@ * */ public enum CoreTaskType implements TaskType { - TIMEOUT("Timeout", "A timeout is a closure that runs at some scheduled time in the future, only once.", CHVersion.V3_3_1), - INTERVAL("Interval", "An interval is a closure that is run repeatedly, with a given delay between runs.", CHVersion.V3_3_1), + TIMEOUT("Timeout", "A timeout is a closure that runs at some scheduled time in the future, only once.", MSVersion.V3_3_1), + INTERVAL("Interval", "An interval is a closure that is run repeatedly, with a given delay between runs.", MSVersion.V3_3_1), CRON("Cron Job", "A cron job is like an interval, except it runs at some interval based on wall time, not an interval" - + " between runs.", CHVersion.V3_3_1), + + " between runs.", MSVersion.V3_3_1), EXECUTION("Execution Task", "An execution task is a way for tasks to be scheduled to be run as soon as possible, though at" - + " some deferred time.", CHVersion.V3_3_1), - THREAD("Thread", "A thread is a separate, user defined task that runs asyncronously from the main thread. The user controls all" - + " aspects about this task.", CHVersion.V3_3_1), + + " some deferred time.", MSVersion.V3_3_1), + THREAD("Thread", "A thread is a separate, user defined task that runs asynchronously from the main thread. The user controls all" + + " aspects about this task.", MSVersion.V3_3_1), ASYNC_TASK("Async Task", "An async task is some task that various individual functions use to do some processing off of the main" + " thread. Usually, once the processing is complete, the task will return control to the main thread, and execute" - + " some user defined callback.", CHVersion.V3_3_1), - ; + + " some user defined callback.", MSVersion.V3_3_1),; private final String displayName; private final String docs; private final Version version; - private CoreTaskType(String displayName, String docs, Version version){ + private CoreTaskType(String displayName, String docs, Version version) { this.displayName = displayName; this.docs = docs; this.version = version; diff --git a/src/main/java/com/laytonsmith/core/taskmanager/TaskHandler.java b/src/main/java/com/laytonsmith/core/taskmanager/TaskHandler.java index f7f68f1f59..9b5983cbde 100644 --- a/src/main/java/com/laytonsmith/core/taskmanager/TaskHandler.java +++ b/src/main/java/com/laytonsmith/core/taskmanager/TaskHandler.java @@ -9,10 +9,9 @@ import java.util.Set; /** - * A TaskHandler is an object that actually manages the various task - * types. It knows several things, including how to get display information - * about a particular task, as well as how to manipulate the task (and if the - * task can be manipulated at all). + * A TaskHandler is an object that actually manages the various task types. It knows several things, including how to + * get display information about a particular task, as well as how to manipulate the task (and if the task can be + * manipulated at all). */ public abstract class TaskHandler { @@ -23,9 +22,9 @@ public abstract class TaskHandler { private int id; private Target target; - protected TaskHandler(TaskType type, int id, Target target){ + protected TaskHandler(TaskType type, int id, Target target) { this.annotation = this.getClass().getAnnotation(taskhandler.class); - if(this.annotation == null){ + if(this.annotation == null) { throw new RuntimeException("All instances of TaskHandler must be tagged with the @taskhandler"); } this.type = type; @@ -34,80 +33,87 @@ protected TaskHandler(TaskType type, int id, Target target){ } /** - * Adds a state change listener to the task manager. When a task's state changes, it - * will be sent to all listeners. + * Adds a state change listener to the task manager. When a task's state changes, it will be sent to all listeners. + * * @param listener */ - public void addStateChangeListener(TaskStateChangeListener listener){ + public void addStateChangeListener(TaskStateChangeListener listener) { stateChangeListeners.add(listener); } /** * Removes a state change listener. + * * @param listener */ - public void removeStateChangeListener(TaskStateChangeListener listener){ + public void removeStateChangeListener(TaskStateChangeListener listener) { stateChangeListeners.remove(listener); } - public synchronized void changeState(TaskState changeTo){ + public synchronized void changeState(TaskState changeTo) { TaskState old = this.getState(); this.state = changeTo; - for(TaskStateChangeListener listener : stateChangeListeners){ + for(TaskStateChangeListener listener : stateChangeListeners) { listener.taskStateChanged(old, this); } } /** * Returns the current state of the task. + * * @return */ - public final TaskState getState(){ + public final TaskState getState() { return this.state; } /** * Returns a list of properties + * * @return */ - public final String[] getProperties(){ + public final String[] getProperties() { return annotation.properties(); } /** * Returns a map of properties and their data. + * * @return */ - public final Map getPropertyData(){ + public final Map getPropertyData() { Map data = new HashMap<>(); - for(String prop : getProperties()){ + for(String prop : getProperties()) { data.put(prop, ReflectionUtils.get(this.getClass(), this, "get" + prop)); } return data; } /** - * Returns the id of the task. This is not necessarily unique across all tasks, but - * should be unique across each task type. Another id, based on the task + * Returns the id of the task. This is not necessarily unique across all tasks, but should be unique across each + * task type. Another id, based on the task + * * @return */ - public final int getID(){ + public final int getID() { return id; } /** * Gets the TaskType of this task. + * * @return */ - public final TaskType getType(){ + public final TaskType getType() { return type; } /** * Returns the code target for where this task was defined at. + * * @return */ - public final Target getDefinedAt(){ + public final Target getDefinedAt() { return target; } diff --git a/src/main/java/com/laytonsmith/core/taskmanager/TaskManager.java b/src/main/java/com/laytonsmith/core/taskmanager/TaskManager.java index 5f7edab6e9..a2075424a2 100644 --- a/src/main/java/com/laytonsmith/core/taskmanager/TaskManager.java +++ b/src/main/java/com/laytonsmith/core/taskmanager/TaskManager.java @@ -1,106 +1,60 @@ package com.laytonsmith.core.taskmanager; -import java.util.ArrayList; import java.util.List; /** - * A TaskManager is an object that is aware of, and can control - * various tasks in MethodScript. + * A TaskManager is an object that is aware of, and can control various tasks in MethodScript. */ -public class TaskManager { +public interface TaskManager { /** - * An internal list of the tasks. - */ - private List tasks; - - /** - * Creates a new TaskManager - */ - public TaskManager(){ - tasks = new ArrayList<>(); - } - - /** - * Returns a list of existing tasks - * @return + * Adds a task to the list. Once the task is finalized, it is automatically removed from this list. + * + * @param task */ - public synchronized List getTasks(){ - return new ArrayList<>(tasks); - } + void addTask(final TaskHandler task); /** * Gets a task, given the task type and id + * * @param type * @param id * @return */ - public synchronized TaskHandler getTask(TaskType type, int id){ - for(TaskHandler task : getTasks()){ - if(task.getType().equals(type) && task.getID() == id){ - return task; - } - } - return null; - } + TaskHandler getTask(TaskType type, int id); /** * Gets a task, given a string representation of the task type, and id. + * * @param type * @param id * @return */ - public synchronized TaskHandler getTask(String type, int id){ - for(TaskHandler task : getTasks()){ - if(task.getType().name().equals(type) && task.getID() == id){ - return task; - } - } - return null; - } + TaskHandler getTask(String type, int id); /** - * Attempts to kill the given task. If the task isn't registered, - * already dead, or in the process of dying, nothing happens. - * @param id - * @param type + * Returns a list of existing tasks + * + * @return */ - public void killTask(TaskType type, int id){ - TaskHandler task = getTask(type, id); - if(task != null){ - task.kill(); - } - } + List getTasks(); /** - * Attempts to kill the given task. If the task isn't registered, - * already dead, or in the process of dying, nothing happens. - * @param type A string representation of the task type + * Attempts to kill the given task. If the task isn't registered, already dead, or in the process of dying, nothing + * happens. + * * @param id + * @param type */ - public void killTask(String type, int id){ - TaskHandler task = getTask(type, id); - if(task != null){ - task.kill(); - } - } + void killTask(TaskType type, int id); /** - * Adds a task to the list. Once the task is finalized, it is - * automatically removed from this list. - * @param task + * Attempts to kill the given task. If the task isn't registered, already dead, or in the process of dying, nothing + * happens. + * + * @param type A string representation of the task type + * @param id */ - public synchronized void addTask(final TaskHandler task){ - task.addStateChangeListener(new TaskStateChangeListener() { - - @Override - public void taskStateChanged(TaskState from, TaskHandler task) { - if(task.getState().isFinalized()){ - tasks.remove(task); - } - } - }); - tasks.add(task); - } + void killTask(String type, int id); } diff --git a/src/main/java/com/laytonsmith/core/taskmanager/TaskManagerImpl.java b/src/main/java/com/laytonsmith/core/taskmanager/TaskManagerImpl.java new file mode 100644 index 0000000000..64b65c5a55 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/taskmanager/TaskManagerImpl.java @@ -0,0 +1,116 @@ +package com.laytonsmith.core.taskmanager; + +import java.util.ArrayList; +import java.util.List; + +/** + * A TaskManager is an object that is aware of, and can control various tasks in MethodScript. + */ +public class TaskManagerImpl implements TaskManager { + + /** + * An internal list of the tasks. + */ + private final List tasks; + + /** + * Creates a new TaskManager + */ + public TaskManagerImpl() { + tasks = new ArrayList<>(); + } + + /** + * Returns a list of existing tasks + * + * @return + */ + @Override + public synchronized List getTasks() { + return new ArrayList<>(tasks); + } + + /** + * Gets a task, given the task type and id + * + * @param type + * @param id + * @return + */ + @Override + public synchronized TaskHandler getTask(TaskType type, int id) { + for(TaskHandler task : getTasks()) { + if(task.getType().equals(type) && task.getID() == id) { + return task; + } + } + return null; + } + + /** + * Gets a task, given a string representation of the task type, and id. + * + * @param type + * @param id + * @return + */ + @Override + public synchronized TaskHandler getTask(String type, int id) { + for(TaskHandler task : getTasks()) { + if(task.getType().name().equals(type) && task.getID() == id) { + return task; + } + } + return null; + } + + /** + * Attempts to kill the given task. If the task isn't registered, already dead, or in the process of dying, nothing + * happens. + * + * @param id + * @param type + */ + @Override + public void killTask(TaskType type, int id) { + TaskHandler task = getTask(type, id); + if(task != null) { + task.kill(); + } + } + + /** + * Attempts to kill the given task. If the task isn't registered, already dead, or in the process of dying, nothing + * happens. + * + * @param type A string representation of the task type + * @param id + */ + @Override + public void killTask(String type, int id) { + TaskHandler task = getTask(type, id); + if(task != null) { + task.kill(); + } + } + + /** + * Adds a task to the list. Once the task is finalized, it is automatically removed from this list. + * + * @param task + */ + @Override + public synchronized void addTask(final TaskHandler task) { + task.addStateChangeListener(new TaskStateChangeListener() { + + @Override + public void taskStateChanged(TaskState from, TaskHandler task) { + if(task.getState().isFinalized()) { + tasks.remove(task); + } + } + }); + tasks.add(task); + } + +} diff --git a/src/main/java/com/laytonsmith/core/taskmanager/TaskState.java b/src/main/java/com/laytonsmith/core/taskmanager/TaskState.java index 672a396fa0..88a726f970 100644 --- a/src/main/java/com/laytonsmith/core/taskmanager/TaskState.java +++ b/src/main/java/com/laytonsmith/core/taskmanager/TaskState.java @@ -6,43 +6,39 @@ public enum TaskState { /** - * The task has been registered, but not yet determined to be either IDLE - * or RUNNING. This should immediately be followed by another state. + * The task has been registered, but not yet determined to be either IDLE or RUNNING. This should immediately be + * followed by another state. */ REGISTERED(false), - /** * The task is registered, but not currently active */ IDLE(false), - /** * The task is currently running */ RUNNING(false), - /** * The task completed normally, and will be automatically removed from the task list. */ FINISHED(true), - /** - * The task was killed by some abnormal means. This does not include exceptions, but rather - * say, being killed by the task manager. + * The task was killed by some abnormal means. This does not include exceptions, but rather say, being killed by the + * task manager. */ - KILLED(true), - ; + KILLED(true),; private final boolean finalized; - private TaskState(boolean finalized){ + + private TaskState(boolean finalized) { this.finalized = finalized; } /** - * Returns true if this state means the task is finalized, that is, it can - * probably be removed from lists of "active" tasks. + * Returns true if this state means the task is finalized, that is, it can probably be removed from lists of + * "active" tasks. */ - public boolean isFinalized(){ + public boolean isFinalized() { return this.finalized; } } diff --git a/src/main/java/com/laytonsmith/core/taskmanager/TaskStateChangeListener.java b/src/main/java/com/laytonsmith/core/taskmanager/TaskStateChangeListener.java index 7962af15f6..d0579bea62 100644 --- a/src/main/java/com/laytonsmith/core/taskmanager/TaskStateChangeListener.java +++ b/src/main/java/com/laytonsmith/core/taskmanager/TaskStateChangeListener.java @@ -7,9 +7,10 @@ public interface TaskStateChangeListener { /** * Fired when a task's state has changed. + * * @param from The old state * @param task The TaskHandler for this task. The new state can be determined from this object. */ void taskStateChanged(TaskState from, TaskHandler task); - + } diff --git a/src/main/java/com/laytonsmith/core/taskmanager/TaskType.java b/src/main/java/com/laytonsmith/core/taskmanager/TaskType.java index e1be98dddd..0513b0499a 100644 --- a/src/main/java/com/laytonsmith/core/taskmanager/TaskType.java +++ b/src/main/java/com/laytonsmith/core/taskmanager/TaskType.java @@ -9,12 +9,14 @@ public interface TaskType extends Documentation { /** * Returns the display name of the task type + * * @return */ String displayName(); /** * Returns the enum name of the task type. + * * @return */ String name(); diff --git a/src/main/java/com/laytonsmith/core/taskmanager/TimeoutTaskHandler.java b/src/main/java/com/laytonsmith/core/taskmanager/TimeoutTaskHandler.java index b1cf7d5d2e..4d274c4273 100644 --- a/src/main/java/com/laytonsmith/core/taskmanager/TimeoutTaskHandler.java +++ b/src/main/java/com/laytonsmith/core/taskmanager/TimeoutTaskHandler.java @@ -9,9 +9,9 @@ @taskhandler(properties = {}) public class TimeoutTaskHandler extends TaskHandler { - private Runnable killTaskRunnable; + private final Runnable killTaskRunnable; - public TimeoutTaskHandler(int id, Target t, Runnable killTaskRunnable){ + public TimeoutTaskHandler(int id, Target t, Runnable killTaskRunnable) { super(CoreTaskType.TIMEOUT, id, t); this.killTaskRunnable = killTaskRunnable; } diff --git a/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/Base.java b/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/Base.java new file mode 100644 index 0000000000..f629471567 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/Base.java @@ -0,0 +1,54 @@ +/* +* ApplicationInsights-Java +* Copyright (c) Microsoft Corporation +* All rights reserved. +* +* MIT License +* 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. +*/ +/* + * Generated from Base.bond (https://github.com/Microsoft/bond) + */ +package com.laytonsmith.core.telemetry.ApplicationInsights; + +/** + * Data contract class Base. + */ +public abstract class Base { + + /** + * Backing field for property BaseType. + */ + private String baseType; + + /** + * Gets the BaseType property. + * + * @return + */ + public String getBaseType() { + return this.baseType; + } + + /** + * Sets the BaseType property. + * + * @param value + */ + public void setBaseType(String value) { + this.baseType = value; + } + +} diff --git a/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/Data.java b/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/Data.java new file mode 100644 index 0000000000..62c3aebc87 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/Data.java @@ -0,0 +1,56 @@ +/* +* ApplicationInsights-Java +* Copyright (c) Microsoft Corporation +* All rights reserved. +* +* MIT License +* 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. +*/ +/* + * Generated from Data.bond (https://github.com/Microsoft/bond) + */ +package com.laytonsmith.core.telemetry.ApplicationInsights; + +/** + * Data contract class Data. + * + * @param + */ +public class Data extends Base { + + /** + * Backing field for property BaseData. + */ + private TDomain baseData; + + /** + * Gets the BaseData property. + * + * @return + */ + public TDomain getBaseData() { + return this.baseData; + } + + /** + * Sets the BaseData property. + * + * @param value + */ + public void setBaseData(TDomain value) { + this.baseData = value; + } + +} diff --git a/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/DataPoint.java b/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/DataPoint.java new file mode 100644 index 0000000000..4dc118e26b --- /dev/null +++ b/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/DataPoint.java @@ -0,0 +1,192 @@ +/* +* ApplicationInsights-Java +* Copyright (c) Microsoft Corporation +* All rights reserved. +* +* MIT License +* 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. +*/ +/* + * Generated from DataPoint.bond (https://github.com/Microsoft/bond) + */ +package com.laytonsmith.core.telemetry.ApplicationInsights; + +/** + * Data contract class DataPoint. + */ +public final class DataPoint { + + /** + * Backing field for property Name. + */ + private String name; + + /** + * Backing field for property Kind. + */ + private DataPointType kind = DataPointType.Measurement; + + /** + * Backing field for property Value. + */ + private double value; + + /** + * Backing field for property Count. + */ + private Integer count; + + /** + * Backing field for property Min. + */ + private Double min; + + /** + * Backing field for property Max. + */ + private Double max; + + /** + * Backing field for property StdDev. + */ + private Double stdDev; + + /** + * Gets the Name property. + * + * @return + */ + public String getName() { + return this.name; + } + + /** + * Sets the Name property. + * + * @param value + */ + public void setName(String value) { + this.name = value; + } + + /** + * Gets the Kind property. + * + * @return + */ + public DataPointType getKind() { + return this.kind; + } + + /** + * Sets the Kind property. + * + * @param value + */ + public void setKind(DataPointType value) { + this.kind = value; + } + + /** + * Gets the Value property. + * + * @return + */ + public double getValue() { + return this.value; + } + + /** + * Sets the Value property. + * + * @param value + */ + public void setValue(double value) { + this.value = value; + } + + /** + * Gets the Count property. + * + * @return + */ + public Integer getCount() { + return this.count; + } + + /** + * Sets the Count property. + * + * @param value + */ + public void setCount(Integer value) { + this.count = value; + } + + /** + * Gets the Min property. + * + * @return + */ + public Double getMin() { + return this.min; + } + + /** + * Sets the Min property. + * + * @param value + */ + public void setMin(Double value) { + this.min = value; + } + + /** + * Gets the Max property. + * + * @return + */ + public Double getMax() { + return this.max; + } + + /** + * Sets the Max property. + * + * @param value + */ + public void setMax(Double value) { + this.max = value; + } + + /** + * Gets the StdDev property. + * + * @return + */ + public Double getStdDev() { + return this.stdDev; + } + + /** + * Sets the StdDev property. + * + * @param value + */ + public void setStdDev(Double value) { + this.stdDev = value; + } + +} diff --git a/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/DataPointType.java b/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/DataPointType.java new file mode 100644 index 0000000000..305abe2f64 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/DataPointType.java @@ -0,0 +1,43 @@ +/* +* ApplicationInsights-Java +* Copyright (c) Microsoft Corporation +* All rights reserved. +* +* MIT License +* 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. +*/ +/* + * Generated from DataPointType.bond (https://github.com/Microsoft/bond) + */ +package com.laytonsmith.core.telemetry.ApplicationInsights; + +/** + * Enum DataPointType. + */ +public enum DataPointType { + Measurement(0), + Aggregation(1); + + private final int id; + + public int getValue() { + return id; + } + + DataPointType(int id) { + this.id = id; + } + +} diff --git a/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/Domain.java b/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/Domain.java new file mode 100644 index 0000000000..51d506610a --- /dev/null +++ b/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/Domain.java @@ -0,0 +1,31 @@ +/* +* ApplicationInsights-Java +* Copyright (c) Microsoft Corporation +* All rights reserved. +* +* MIT License +* 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. +*/ +/* + * Generated from Domain.bond (https://github.com/Microsoft/bond) + */ +package com.laytonsmith.core.telemetry.ApplicationInsights; + +/** + * Data contract class Domain. + */ +public abstract class Domain { + +} diff --git a/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/Envelope.java b/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/Envelope.java new file mode 100644 index 0000000000..77e3978fb1 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/Envelope.java @@ -0,0 +1,221 @@ +/* +* ApplicationInsights-Java +* Copyright (c) Microsoft Corporation +* All rights reserved. +* +* MIT License +* 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. +*/ +/* + * This has been ripped from the Application Insights SDK. + */ +package com.laytonsmith.core.telemetry.ApplicationInsights; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Data contract class Envelope. + */ +public final class Envelope { + + /** + * Backing field for property Ver. + */ + private int ver = 1; + + /** + * Backing field for property Name. + */ + private String name; + + /** + * Backing field for property Time. + */ + private String time; + + /** + * Backing field for property SampleRate. + */ + private double sampleRate = 100.0; + + /** + * Backing field for property Seq. + */ + private String seq; + + /** + * Backing field for property IKey. + */ + private String iKey; + + /** + * Backing field for property Tags. + */ + private Map tags; + + /** + * Backing field for property Data. + */ + private Base data; + + /** + * Gets the Ver property. + * + * @return + */ + public int getVer() { + return this.ver; + } + + /** + * Sets the Ver property. + * + * @param value + */ + public void setVer(int value) { + this.ver = value; + } + + /** + * Gets the Name property. + * + * @return + */ + public String getName() { + return this.name; + } + + /** + * Sets the Name property. + * + * @param value + */ + public void setName(String value) { + this.name = value; + } + + /** + * Gets the Time property. + * + * @return + */ + public String getTime() { + return this.time; + } + + /** + * Sets the Time property. + * + * @param value + */ + public void setTime(String value) { + this.time = value; + } + + /** + * Gets the SampleRate property. + * + * @return + */ + public double getSampleRate() { + return this.sampleRate; + } + + /** + * Sets the SampleRate property. + * + * @param value + */ + public void setSampleRate(double value) { + this.sampleRate = value; + } + + /** + * Gets the Seq property. + * + * @return + */ + public String getSeq() { + return this.seq; + } + + /** + * Sets the Seq property. + * + * @param value + */ + public void setSeq(String value) { + this.seq = value; + } + + /** + * Gets the IKey property. + * + * @return + */ + public String getIKey() { + return this.iKey; + } + + /** + * Sets the IKey property. + * + * @param value + */ + public void setIKey(String value) { + this.iKey = value; + } + + /** + * Gets the Tags property. + * + * @return + */ + public Map getTags() { + if(this.tags == null) { + this.tags = new ConcurrentHashMap<>(); + } + return this.tags; + } + + /** + * Sets the Tags property. See available options here: + * https://github.com/microsoft/ApplicationInsights-Home/blob/master/EndpointSpecs/Schemas/Bond/ContextTagKeys.bond + * + * @param value + */ + public void setTags(Map value) { + this.tags = value; + } + + /** + * Gets the Data property. + * + * @return + */ + public Base getData() { + return this.data; + } + + /** + * Sets the Data property. + * + * @param value + */ + public void setData(Base value) { + this.data = value; + } +} diff --git a/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/EventData.java b/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/EventData.java new file mode 100644 index 0000000000..20cf55cea7 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/EventData.java @@ -0,0 +1,135 @@ +/* +* ApplicationInsights-Java +* Copyright (c) Microsoft Corporation +* All rights reserved. +* +* MIT License +* 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. +*/ +/* + * Generated from EventData.bond (https://github.com/Microsoft/bond) + */ +package com.laytonsmith.core.telemetry.ApplicationInsights; + +import com.laytonsmith.PureUtilities.JSONUtil; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Data contract class EventData. + */ +public final class EventData extends Domain { + + /** + * Backing field for property Ver. + */ + private int ver = 2; + + /** + * Backing field for property Name. + */ + private String name; + + /** + * Backing field for property Properties. + */ +// @JSONUtil.MapType(String.class) + private Map properties; + + /** + * Backing field for property Measurements. + */ + @JSONUtil.MapType(Double.class) + private Map measurements; + + /** + * Gets the Ver property. + * + * @return + */ + public int getVer() { + return this.ver; + } + + /** + * Sets the Ver property. + * + * @param value + */ + public void setVer(int value) { + this.ver = value; + } + + /** + * Gets the Name property. + * + * @return + */ + public String getName() { + return this.name; + } + + /** + * Sets the Name property. + * + * @param value + */ + public void setName(String value) { + this.name = value; + } + + /** + * Gets the Properties property. + * + * @return + */ + public Map getProperties() { + if(this.properties == null) { + this.properties = new ConcurrentHashMap<>(); + } + return this.properties; + } + + /** + * Sets the Properties property. + * + * @param value + */ + public void setProperties(Map value) { + this.properties = value; + } + + /** + * Gets the Measurements property. + * + * @return + */ + public Map getMeasurements() { + if(this.measurements == null) { + this.measurements = new ConcurrentHashMap<>(); + } + return this.measurements; + } + + /** + * Sets the Measurements property. + * + * @param value + */ + public void setMeasurements(Map value) { + this.measurements = value; + } + +} diff --git a/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/ExceptionData.java b/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/ExceptionData.java new file mode 100644 index 0000000000..fbdcf48ffb --- /dev/null +++ b/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/ExceptionData.java @@ -0,0 +1,182 @@ +/* +* ApplicationInsights-Java +* Copyright (c) Microsoft Corporation +* All rights reserved. +* +* MIT License +* 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. +*/ +/* + * Generated from ExceptionData.bond (https://github.com/Microsoft/bond) + */ +package com.laytonsmith.core.telemetry.ApplicationInsights; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Data contract class ExceptionData. + */ +public final class ExceptionData extends Domain { + + /** + * Backing field for property Ver. + */ + private int ver = 2; + + /** + * Backing field for property Exceptions. + */ + private ExceptionDetails[] exceptions; + + /** + * Backing field for property SeverityLevel. + */ + private SeverityLevel severityLevel; + + /** + * Backing field for property ProblemId. + */ + private String problemId; + + /** + * Backing field for property Properties. + */ + private Map properties; + + /** + * Backing field for property Measurements. + */ + private Map measurements; + + /** + * Gets the Ver property. + * + * @return + */ + public int getVer() { + return this.ver; + } + + /** + * Sets the Ver property. + * + * @param value + */ + public void setVer(int value) { + this.ver = value; + } + + /** + * Gets the Exceptions property. + * + * @return + */ + public ExceptionDetails[] getExceptions() { + if(this.exceptions == null) { + this.exceptions = new ExceptionDetails[0]; + } + return this.exceptions; + } + + /** + * Sets the Exceptions property. + * + * @param value + */ + public void setExceptions(ExceptionDetails[] value) { + this.exceptions = value; + } + + /** + * Gets the SeverityLevel property. + * + * @return + */ + public SeverityLevel getSeverityLevel() { + return this.severityLevel; + } + + /** + * Sets the SeverityLevel property. + * + * @param value + */ + public void setSeverityLevel(SeverityLevel value) { + this.severityLevel = value; + } + + /** + * Gets the ProblemId property. + * + * @return + */ + public String getProblemId() { + return this.problemId; + } + + /** + * Sets the ProblemId property. + * + * @param value + */ + public void setProblemId(String value) { + this.problemId = value; + } + + /** + * Gets the Properties property. + * + * @return + */ + public Map getProperties() { + if(this.properties == null) { + this.properties = new ConcurrentHashMap<>(); + } + return this.properties; + } + + /** + * Sets the Properties property. + * + * @param value + */ + public void setProperties(ConcurrentMap value) { + this.properties = value; + } + + /** + * Gets the Measurements property. + * + * @return + */ + public Map getMeasurements() { + if(this.measurements == null) { + this.measurements = new ConcurrentHashMap<>(); + } + return this.measurements; + } + + /** + * Sets the Measurements property. + * + * @param value + */ + public void setMeasurements(ConcurrentMap value) { + this.measurements = value; + } + +} diff --git a/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/ExceptionDetails.java b/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/ExceptionDetails.java new file mode 100644 index 0000000000..310057c8ee --- /dev/null +++ b/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/ExceptionDetails.java @@ -0,0 +1,198 @@ +/* +* ApplicationInsights-Java +* Copyright (c) Microsoft Corporation +* All rights reserved. +* +* MIT License +* 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. +*/ +/* + * Generated from ExceptionDetails.bond (https://github.com/Microsoft/bond) + */ +package com.laytonsmith.core.telemetry.ApplicationInsights; + +import java.util.ArrayList; +import java.util.List; + +/** + * Data contract class ExceptionDetails. + */ +public class ExceptionDetails { + + /** + * Backing field for property Id. + */ + private int id; + + /** + * Backing field for property OuterId. + */ + private int outerId; + + /** + * Backing field for property TypeName. + */ + private String typeName; + + /** + * Backing field for property Message. + */ + private String message; + + /** + * Backing field for property HasFullStack. + */ + private boolean hasFullStack = true; + + /** + * Backing field for property Stack. + */ + private String stack; + + /** + * Backing field for property ParsedStack. + */ + private List parsedStack; + + /** + * Gets the Id property. + * + * @return + */ + public int getId() { + return this.id; + } + + /** + * Sets the Id property. + * + * @param value + */ + public void setId(int value) { + this.id = value; + } + + /** + * Gets the OuterId property. + * + * @return + */ + public int getOuterId() { + return this.outerId; + } + + /** + * Sets the OuterId property. + * + * @param value + */ + public void setOuterId(int value) { + this.outerId = value; + } + + /** + * Gets the TypeName property. + * + * @return + */ + public String getTypeName() { + return this.typeName; + } + + /** + * Sets the TypeName property. + * + * @param value + */ + public void setTypeName(String value) { + this.typeName = value; + } + + /** + * Gets the Message property. + * + * @return + */ + public String getMessage() { + return this.message; + } + + /** + * Sets the Message property. + * + * @param value + */ + public void setMessage(String value) { + this.message = value; + } + + /** + * Gets the HasFullStack property. + * + * @return + */ + public boolean getHasFullStack() { + return this.hasFullStack; + } + + /** + * Sets the HasFullStack property. + * + * @param value + */ + public void setHasFullStack(boolean value) { + this.hasFullStack = value; + } + + /** + * Gets the Stack property. + * + * @return + */ + public String getStack() { + return this.stack; + } + + /** + * Sets the Stack property. + * + * @param value + */ + public void setStack(String value) { + this.stack = value; + } + + /** + * Gets the ParsedStack property. + * + * @return + */ + public List getParsedStack() { + if(this.parsedStack == null) { + this.parsedStack = new ArrayList<>(); + } + return this.parsedStack; + } + + /** + * Sets the ParsedStack property. + * + * @param value + */ + public void setParsedStack(List value) { + this.parsedStack = value; + } + +} diff --git a/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/MetricData.java b/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/MetricData.java new file mode 100644 index 0000000000..6af2dc9ec5 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/MetricData.java @@ -0,0 +1,111 @@ +/* +* ApplicationInsights-Java +* Copyright (c) Microsoft Corporation +* All rights reserved. +* +* MIT License +* 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. +*/ +/* + * Generated from MetricData.bond (https://github.com/Microsoft/bond) + */ +package com.laytonsmith.core.telemetry.ApplicationInsights; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Data contract class MetricData. + */ +public final class MetricData extends Domain { + + /** + * Backing field for property Ver. + */ + private int ver = 2; + + /** + * Backing field for property Metrics. + */ + private List metrics; + + /** + * Backing field for property Properties. + */ + private ConcurrentMap properties; + + /** + * Gets the Ver property. + * + * @return + */ + public int getVer() { + return this.ver; + } + + /** + * Sets the Ver property. + * + * @param value + */ + public void setVer(int value) { + this.ver = value; + } + + /** + * Gets the Metrics property. + * + * @return + */ + public List getMetrics() { + if(this.metrics == null) { + this.metrics = new ArrayList<>(); + } + return this.metrics; + } + + /** + * Sets the Metrics property. + * + * @param value + */ + public void setMetrics(List value) { + this.metrics = value; + } + + /** + * Gets the Properties property. + * + * @return + */ + public ConcurrentMap getProperties() { + if(this.properties == null) { + this.properties = new ConcurrentHashMap<>(); + } + return this.properties; + } + + /** + * Sets the Properties property. + * + * @param value + */ + public void setProperties(ConcurrentMap value) { + this.properties = value; + } + +} diff --git a/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/SeverityLevel.java b/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/SeverityLevel.java new file mode 100644 index 0000000000..b094d8214d --- /dev/null +++ b/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/SeverityLevel.java @@ -0,0 +1,59 @@ +/* +* ApplicationInsights-Java +* Copyright (c) Microsoft Corporation +* All rights reserved. +* +* MIT License +* 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. +*/ +/* + * Generated from SeverityLevel.bond (https://github.com/Microsoft/bond) + */ +package com.laytonsmith.core.telemetry.ApplicationInsights; + +import com.laytonsmith.PureUtilities.JSONUtil; + +/** + * Enum SeverityLevel. + */ +public enum SeverityLevel implements JSONUtil.CustomLongEnum { + Verbose(0), + Information(1), + Warning(2), + Error(3), + Critical(4); + + private final long id; + + @Override + public Long getValue() { + return id; + } + + SeverityLevel(int id) { + this.id = id; + } + + @Override + public SeverityLevel getFromValue(Long value) { + for(SeverityLevel l : values()) { + if(l.getValue() == value) { + return l; + } + } + return null; + } + +} diff --git a/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/StackFrame.java b/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/StackFrame.java new file mode 100644 index 0000000000..c8d275664d --- /dev/null +++ b/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/StackFrame.java @@ -0,0 +1,146 @@ +/* +* ApplicationInsights-Java +* Copyright (c) Microsoft Corporation +* All rights reserved. +* +* MIT License +* 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. +*/ +/* + * Generated from StackFrame.bond (https://github.com/Microsoft/bond) + */ +package com.laytonsmith.core.telemetry.ApplicationInsights; + +/** + * Data contract class StackFrame. + */ +public final class StackFrame { + + /** + * Backing field for property Level. + */ + private int level; + + /** + * Backing field for property Method. + */ + private String method; + + /** + * Backing field for property Assembly. + */ + private String assembly; + + /** + * Backing field for property FileName. + */ + private String fileName; + + /** + * Backing field for property Line. + */ + private int line; + + /** + * Gets the Level property. + * + * @return + */ + public int getLevel() { + return this.level; + } + + /** + * Sets the Level property. + * + * @param value + */ + public void setLevel(int value) { + this.level = value; + } + + /** + * Gets the Method property. + * + * @return + */ + public String getMethod() { + return this.method; + } + + /** + * Sets the Method property. + * + * @param value + */ + public void setMethod(String value) { + this.method = value; + } + + /** + * Gets the Assembly property. + * + * @return + */ + public String getAssembly() { + return this.assembly; + } + + /** + * Sets the Assembly property. + * + * @param value + */ + public void setAssembly(String value) { + this.assembly = value; + } + + /** + * Gets the FileName property. + * + * @return + */ + public String getFileName() { + return this.fileName; + } + + /** + * Sets the FileName property. + * + * @param value + */ + public void setFileName(String value) { + this.fileName = value; + } + + /** + * Gets the Line property. + * + * @return + */ + public int getLine() { + return this.line; + } + + /** + * Sets the Line property. + * + * @param value + */ + public void setLine(int value) { + this.line = value; + } + +} diff --git a/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/TelemetryUtil.java b/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/TelemetryUtil.java new file mode 100644 index 0000000000..582907e918 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/telemetry/ApplicationInsights/TelemetryUtil.java @@ -0,0 +1,99 @@ +package com.laytonsmith.core.telemetry.ApplicationInsights; + +import com.laytonsmith.PureUtilities.Common.OSUtils; +import com.laytonsmith.core.functions.Meta; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * This package replicates as small a portion of the Application Insights SDK as possible, since we only use a tiny + * fraction of the total package. The Bean classes are ripped from the Java SDK, and in general, are based on the + * bond spec found here https://github.com/microsoft/ApplicationInsights-Home/tree/master/EndpointSpecs/Schemas/Bond + */ +public class TelemetryUtil { + + private final String instrumentationKey; + private final String iKeyDashless; + + public TelemetryUtil(String instrumentationKey) { + this.instrumentationKey = instrumentationKey; + this.iKeyDashless = instrumentationKey.replace("-", ""); + } + + private String sessionName = "untracked"; + private boolean newSession = false; + + public void setSessionName(String sessionName) { + this.sessionName = sessionName; + } + + public String getSessionName() { + return this.sessionName; + } + + public void setNewSession(boolean newSession) { + this.newSession = newSession; + } + + /** + * Returns a new Envelope object, which can be serialized and sent. + * @param metricName The name of the metric. + * @param properties The properties associated with the metric, may be null. + * @param measurements The measurements associated with the metric, may be null. + * @return + */ + public Envelope newEvent(String metricName, + Map properties, + Map measurements) { + Envelope env = new Envelope(); + env.setVer(1); + env.setName("Microsoft.ApplicationInsights." + iKeyDashless + ".Event"); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); + sdf.setTimeZone(TimeZone.getTimeZone("UTC")); + env.setTime(sdf.format(new Date())); + env.setSampleRate(100); + env.setIKey(instrumentationKey); + Map tags = generateStandardTags(); + tags.put("ai.internal.nodeName", sessionName); + tags.put("ai.session.id", sessionName); + tags.put("ai.session.isNew", Boolean.toString(newSession)); + env.setTags(tags); + + EventData eventData = new EventData(); + + eventData.setVer(2); + eventData.setName(metricName); + if(properties != null && !properties.isEmpty()) { + eventData.setProperties(properties); + } + + if(measurements != null && !measurements.isEmpty()) { + eventData.setMeasurements(measurements); + } + + Data data = new Data<>(); + data.setBaseType("EventData"); + data.setBaseData(eventData); + + env.setData(data); + + return env; + } + + // https://github.com/microsoft/ApplicationInsights-Home/blob/master/EndpointSpecs/Schemas/Bond/ContextTagKeys.bond + private ConcurrentMap generateStandardTags() { + ConcurrentMap tags = new ConcurrentHashMap<>(); + tags.put("ai.application.ver", Long.toString(Meta.engine_build_date.GetEngineBuildDate())); + tags.put("ai.device.locale", Locale.getDefault().toString()); + tags.put("ai.device.osVersion", OSUtils.GetOS().name()); + tags.put("ai.location.country", "Untracked"); + tags.put("ai.location.province", "Untracked"); + tags.put("ai.location.city", "Untracked"); + return tags; + } +} diff --git a/src/main/java/com/laytonsmith/core/telemetry/DefaultTelemetry.java b/src/main/java/com/laytonsmith/core/telemetry/DefaultTelemetry.java new file mode 100644 index 0000000000..83c285fbf4 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/telemetry/DefaultTelemetry.java @@ -0,0 +1,28 @@ +package com.laytonsmith.core.telemetry; + +/** + * If you want to add a new telemetry object, you also have to add it to apps.methodscript.com, or the server + * will reject it. + */ +public class DefaultTelemetry { + + @TelemetryCategory(name = "methodscript.startup", + group = TelemetryCategoryGroup.GENERAL_GROUP, + type = TelemetryType.METRIC, + purpose = "This category logs startup of the program.") + public static class StartupMetric implements MetricTelemetryValue {} + + @TelemetryCategory(name = "methodscript.startupMode", + group = TelemetryCategoryGroup.GENERAL_GROUP, + type = TelemetryType.LOG, + purpose = "This category logs the startup mode. For instance, if you run this from the command line," + + " this is a different mode than running it as a plugin.") + public static class StartupModeMetric implements LogTelemetryValue {} + + @TelemetryCategory(name = "methodscript.saOn", + group = TelemetryCategoryGroup.STATIC_ANALYSIS, + type = TelemetryType.METRIC, + purpose = "This category logs whether or not static analysis is globally enabled.") + public static class StaticAnalysisOnMetric implements MetricTelemetryValue {} + +} diff --git a/src/main/java/com/laytonsmith/core/telemetry/LogTelemetryValue.java b/src/main/java/com/laytonsmith/core/telemetry/LogTelemetryValue.java new file mode 100644 index 0000000000..77679c42bd --- /dev/null +++ b/src/main/java/com/laytonsmith/core/telemetry/LogTelemetryValue.java @@ -0,0 +1,10 @@ + +package com.laytonsmith.core.telemetry; + +/** + * + * @author Cailin + */ +public interface LogTelemetryValue extends TelemetryValue { + +} diff --git a/src/main/java/com/laytonsmith/core/telemetry/MetricTelemetryValue.java b/src/main/java/com/laytonsmith/core/telemetry/MetricTelemetryValue.java new file mode 100644 index 0000000000..e4b0576b15 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/telemetry/MetricTelemetryValue.java @@ -0,0 +1,9 @@ +package com.laytonsmith.core.telemetry; + +/** + * + * @author Cailin + */ +public interface MetricTelemetryValue extends TelemetryValue { + +} diff --git a/src/main/java/com/laytonsmith/core/telemetry/Telemetry.java b/src/main/java/com/laytonsmith/core/telemetry/Telemetry.java new file mode 100644 index 0000000000..a91f0fbdd8 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/telemetry/Telemetry.java @@ -0,0 +1,220 @@ +package com.laytonsmith.core.telemetry; + +import com.laytonsmith.core.telemetry.ApplicationInsights.Envelope; +import com.laytonsmith.PureUtilities.Common.StreamUtils; +import com.laytonsmith.PureUtilities.JSONUtil; +import com.laytonsmith.abstraction.Implementation; +import com.laytonsmith.core.MethodScriptFileLocations; +import com.laytonsmith.core.Prefs; +import com.laytonsmith.core.telemetry.ApplicationInsights.TelemetryUtil; +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +/** + * + */ +public class Telemetry { + + private static volatile Telemetry telemetry = null; + // This is not the real instrumentation key, it is replaced with the real one on the server side. + private static final String INSTRUMENTATION_KEY = "28cb72ef-45fe-4634-b7e3-ea672db27cf0"; + + /** + * Gets the default {@link Telemetry} object. + * @return + */ + public static Telemetry GetDefault() { + @SuppressWarnings("LocalVariableHidesMemberVariable") + Telemetry telemetry = Telemetry.telemetry; + if(telemetry == null) { + synchronized(Telemetry.class) { + telemetry = Telemetry.telemetry; + if(telemetry == null) { + Telemetry.telemetry = telemetry = new Telemetry(); + } + } + } + return telemetry; + } + + public static void SetNoOpTelemetry() { + @SuppressWarnings("LocalVariableHidesMemberVariable") + Telemetry telemetry = Telemetry.telemetry; + if(telemetry == null) { + synchronized(Telemetry.class) { + telemetry = Telemetry.telemetry; + if(telemetry == null) { + Telemetry.telemetry = new Telemetry() { + @Override + public void initializeTelemetry() { + // NOOP + } + }; + } + } + } + } + + public static String GetNagMessage() { + return "Help make " + Implementation.GetServerType().getBranding() + " better! Enable telemetry (or disable" + + " this message) by changing the telemetry-on setting in preferences.ini to help us understand what" + + " features you're using and are most important to you. No personal information is collected.\n"; + } + + private boolean enabled = false; + private TelemetryChannel channel; + private TelemetryUtil client; + + private static final TelemetryChannel STDOUT_CHANNEL = new TelemetryChannel() { + + @Override + public void send(Envelope item) { + JSONUtil.Options options = new JSONUtil.Options(); + options.skipNulls = true; + StreamUtils.GetSystemOut().println("Telemetry data: " + new JSONUtil(options).serialize(item)); + } + }; + + /** + * Nags the user, but only if the preference is set to nag them. + */ + public void doNag() { + try { + if(Prefs.TelemetryOn() == null) { + StreamUtils.GetSystemOut().print(Telemetry.GetNagMessage()); + } + } catch (Throwable t) { + t.printStackTrace(StreamUtils.GetSystemErr()); + // Don't propogate upwards, so the program continues, but we also don't want + // to fail completely silently. + } + } + + public void initializeTelemetry() { + try { + // Always configure this, but we won't load any more if the telemetry-on pref isn't "true". + File config = MethodScriptFileLocations.getDefault().getTelemetryConfigFile(); + TelemetryPrefs.init(config); + } catch (Throwable t) { + StreamUtils.GetSystemErr().println("Could not initialize telemetry config!"); + t.printStackTrace(StreamUtils.GetSystemErr()); + return; + } + + if(Objects.equals(Boolean.TRUE, Prefs.TelemetryOn())) { + enabled = true; + } + + if(enabled) { + try { + client = new TelemetryUtil(INSTRUMENTATION_KEY); + if(Prefs.TelemetryAudit()) { + channel = STDOUT_CHANNEL; + } else { + channel = new ProxyTelemetryChannel(new TelemetryProxy()); + } + String session = UUID.randomUUID().toString(); + client.setSessionName(session); + client.setNewSession(true); + + } catch (Throwable t) { + StreamUtils.GetSystemErr().println("Could not initialize telemetry!"); + t.printStackTrace(StreamUtils.GetSystemErr()); + } + } + + metric(DefaultTelemetry.StartupMetric.class); + } + + /** + * Returns the session id, or null if telemetry is off (or not initialized yet). + * @return + */ + public String getSessionId() { + if(client == null) { + return null; + } + return client.getSessionName(); + } + + /** + * Sends a point in time metric. + * @param type + */ + public void metric(Class type) { + // Client is null if we're globally disabled + if(client != null) { + TelemetryCategory tc = TelemetryValue.Helper.GetCategory(type); + if(tc.type() != TelemetryType.METRIC) { + return; + } + + if(!TelemetryPrefs.GetTelemetryLoggable(type)) { + return; + } + + channel.send(client.newEvent(tc.type().getPrefix() + "." + tc.name(), null, null)); + } + } + + /** + * Sends a metric with data. + * @param type + * @param properties A map of string to string properties. May be null. + * @param metrics A map of string to double metrics. May be null. + */ + public void log(Class type, Map properties, Map metrics) { + // Client is null if we're globally disabled + if(client != null) { + TelemetryCategory tc = TelemetryValue.Helper.GetCategory(type); + if(tc.type() != TelemetryType.LOG) { + return; + } + + if(!TelemetryPrefs.GetTelemetryLoggable(type)) { + return; + } + + if(properties == null) { + properties = new HashMap<>(); + } + + if(metrics == null) { + metrics = new HashMap<>(); + } + + channel.send(client.newEvent(tc.type().getPrefix() + "." + tc.name(), + new ConcurrentHashMap<>(properties), + new ConcurrentHashMap<>(metrics))); + } + } + + private interface TelemetryChannel { + /** + * Sends the envelope to the correct channel. + * @param envelope + */ + void send(Envelope envelope); + } + + class ProxyTelemetryChannel implements TelemetryChannel { + + private final TelemetryProxy proxy; + + public ProxyTelemetryChannel(TelemetryProxy proxy) { + this.proxy = proxy; + } + + @Override + public void send(Envelope item) { + JSONUtil.Options options = new JSONUtil.Options(); + options.skipNulls = true; + String body = new JSONUtil(options).serialize(item); + proxy.submit(body); + } + } +} diff --git a/src/main/java/com/laytonsmith/core/telemetry/TelemetryCategory.java b/src/main/java/com/laytonsmith/core/telemetry/TelemetryCategory.java new file mode 100644 index 0000000000..2cc47b2dbc --- /dev/null +++ b/src/main/java/com/laytonsmith/core/telemetry/TelemetryCategory.java @@ -0,0 +1,41 @@ +package com.laytonsmith.core.telemetry; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface TelemetryCategory { + + /** + * Returns the name of the TelemetryCategory. + * @return + */ + String name(); + + /** + * Returns the group that this TelemetryCategory should fit in. Note that this is not extensible. + * @return + */ + TelemetryCategoryGroup group(); + + /** + * Returns the type of the telemetry data. If a category is set as {@link TelemetryType#METRIC}, it is + * prohibited from uploading data, and if it is type {@link TelemetryType#LOG}, it is subject to a higher + * level of scrutiny for addition. + * @return + */ + TelemetryType type(); + + /** + * Returns the documentation that accompanies the value. + * @return + */ + String purpose(); + +} diff --git a/src/main/java/com/laytonsmith/core/telemetry/TelemetryCategoryGroup.java b/src/main/java/com/laytonsmith/core/telemetry/TelemetryCategoryGroup.java new file mode 100644 index 0000000000..3c2a42d91a --- /dev/null +++ b/src/main/java/com/laytonsmith/core/telemetry/TelemetryCategoryGroup.java @@ -0,0 +1,26 @@ +package com.laytonsmith.core.telemetry; + +import com.laytonsmith.PureUtilities.Preferences; + +/** + * + */ +public enum TelemetryCategoryGroup { + GENERAL_GROUP(new Preferences.GroupData("General").setSortOrder(0) + .setDescription("These are general settings" + + " and don't have a more specific category.")), + STATIC_ANALYSIS(new Preferences.GroupData("Static Analysis") + .setDescription("These are settings related to the static" + + " analysis system.")); + private final Preferences.GroupData gd; + + private TelemetryCategoryGroup(Preferences.GroupData gd) { + this.gd = gd; + } + + /*package*/ + Preferences.GroupData getGroupData() { + return this.gd; + } + +} diff --git a/src/main/java/com/laytonsmith/core/telemetry/TelemetryPrefs.java b/src/main/java/com/laytonsmith/core/telemetry/TelemetryPrefs.java new file mode 100644 index 0000000000..df0341073a --- /dev/null +++ b/src/main/java/com/laytonsmith/core/telemetry/TelemetryPrefs.java @@ -0,0 +1,71 @@ +package com.laytonsmith.core.telemetry; + +import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery; +import com.laytonsmith.PureUtilities.Common.StreamUtils; +import com.laytonsmith.PureUtilities.Preferences; +import com.laytonsmith.PureUtilities.Preferences.Preference; +import com.laytonsmith.PureUtilities.Preferences.Type; +import com.laytonsmith.abstraction.Implementation; +import com.laytonsmith.core.Prefs; +import com.laytonsmith.core.Static; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Objects; +import java.util.Set; + +/** + * + */ +public class TelemetryPrefs { + + + private static Preferences prefs; + + public static void init(final File f) throws IOException { + String header = StreamUtils.GetResource("prefs-header.txt"); + ArrayList a = new ArrayList<>(); + String def = Prefs.TelemetryCollectByDefault() ? "true" : "nag"; + Set> vs = ClassDiscovery.getDefaultInstance().loadClassesWithAnnotation(TelemetryCategory.class); + for(Class v : vs) { + TelemetryCategory tc = v.getAnnotation(TelemetryCategory.class); + String prefix = tc.type().getPrefix(); + a.add(new Preference(prefix + "." + tc.name(), def, Type.STRING, tc.purpose(), tc.group().getGroupData())); + } + + prefs = new Preferences(Implementation.GetServerType().getBranding(), Static.getLogger(), a, header); + prefs.init(f); + boolean doNag = false; + for(Class v : vs) { + // Walk through the prefs again, seeing if there are any set to "nag", and nag at this point. + TelemetryCategory tc = v.getAnnotation(TelemetryCategory.class); + String p = prefs.getStringPreference(tc.type().getPrefix() + "." + tc.name()); + if("nag".equalsIgnoreCase(p)) { + doNag = true; + break; + } + } + + if(doNag) { + StreamUtils.GetSystemOut().println("There is one or more new Telemetry Category defined in " + + f.getAbsolutePath() + ". Please take a look at the file to opt in or out of the collection" + + " category."); + } + } + + public static boolean GetTelemetryLoggable(Class type) { + TelemetryCategory tc = type.getAnnotation(TelemetryCategory.class); + if(tc == null) { + // This is an error, but we should notice it because it's never getting logged. This case + // means that the @TelemetryCategory is missing from the class. + return false; + } + String name = tc.name(); + String prefix = tc.type().getPrefix(); + String p = prefs.getStringPreference(prefix + "." + name); + if("nag".equalsIgnoreCase(p)) { + return false; + } + return Objects.equals(Preferences.getBoolean(p), Boolean.TRUE); + } +} diff --git a/src/main/java/com/laytonsmith/core/telemetry/TelemetryProxy.java b/src/main/java/com/laytonsmith/core/telemetry/TelemetryProxy.java new file mode 100644 index 0000000000..66e0379b56 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/telemetry/TelemetryProxy.java @@ -0,0 +1,75 @@ +package com.laytonsmith.core.telemetry; + +import com.laytonsmith.PureUtilities.DaemonManager; +import com.laytonsmith.PureUtilities.RunnableQueue; +import io.swagger.client.ApiException; +import io.swagger.client.api.TelemetryApi; + +/** + * This class manages submitting telemetry data to apps.methodscript.com. + */ +public class TelemetryProxy { + + private String key = null; + private final TelemetryApi api = new TelemetryApi(); + private final RunnableQueue queue; + private final DaemonManager dm = new DaemonManager(); + private boolean enabled = true; + + + public TelemetryProxy() { + queue = new RunnableQueue("TelemetrySubmitter"); + } + + public void submit(String data) { + if(!enabled) { + return; + } + queue.invokeLater(dm, () -> { + submit0(data); + }); + } + + private void submit0(String data) { + if(key == null) { + regenKey(); + if(key == null || !enabled) { + // We've been disabled. + return; + } + } + try { + api.telemetryKeyPost(data, key); + } catch (ApiException ex) { + int code = ex.getCode(); + if(code == 403) { + // Retry after key regen + regenKey(); + if(!enabled) { + // We've been disabled. + return; + } + try { + api.telemetryKeyPost(data, key); + } catch (ApiException ex1) { + // Give up permanently + enabled = false; + } + } else if(code == 502) { + // Hmm, this is unfortunate, but let's not permanently disable. + } + } + } + + private void regenKey() { + try { + key = api.telemetryGet(); + } catch (ApiException ex) { + int code = ex.getCode(); + if(code == 403) { + // We've been blocked! Give up permanently. + enabled = false; + } + } + } +} diff --git a/src/main/java/com/laytonsmith/core/telemetry/TelemetryType.java b/src/main/java/com/laytonsmith/core/telemetry/TelemetryType.java new file mode 100644 index 0000000000..d499977abb --- /dev/null +++ b/src/main/java/com/laytonsmith/core/telemetry/TelemetryType.java @@ -0,0 +1,29 @@ +package com.laytonsmith.core.telemetry; + +/** + * + */ +public enum TelemetryType { + /** + * Logs are events that contain data, rather than just point events. In general, logs are much more highly + * suspect than metrics when it comes to privacy reviews. + */ + LOG("logs"), + + /** + * Metrics are point in time events, and do not contain any additional data other than the fact that they + * happened. The exact time of the event is not necessarily accurately recorded either, but the count of + * events is accurate. + */ + METRIC("metrics"); + private final String prefix; + + private TelemetryType(String prefix) { + this.prefix = prefix; + } + + public String getPrefix() { + return this.prefix; + } + +} diff --git a/src/main/java/com/laytonsmith/core/telemetry/TelemetryValue.java b/src/main/java/com/laytonsmith/core/telemetry/TelemetryValue.java new file mode 100644 index 0000000000..b9c1def8b2 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/telemetry/TelemetryValue.java @@ -0,0 +1,54 @@ +package com.laytonsmith.core.telemetry; + +import java.lang.annotation.Annotation; + +/** + * Classes tagged with {@link TelemetryPrefs.TelemetryCategory} should also implement this interface. + */ +public interface TelemetryValue { + + public static class Helper { + + /** + * Returns the TelemetryCategory. If the class was not annotated, a default TelemetryCategory object + * is returned, which has an empty string as a name, which cannot be registered, and therefore should + * always return false. + * @param value + * @return + */ + public static TelemetryCategory GetCategory(Class value) { + TelemetryCategory tc = value.getAnnotation(TelemetryCategory.class); + if(tc == null) { + return new TelemetryCategory() { + @Override + public String name() { + // This will always cause the comparison to always return false. + return ""; + } + + @Override + public TelemetryCategoryGroup group() { + return TelemetryCategoryGroup.GENERAL_GROUP; + } + + @Override + public TelemetryType type() { + return null; + } + + @Override + public String purpose() { + return ""; + } + + @Override + public Class annotationType() { + return TelemetryCategory.class; + } + }; + } else { + return tc; + } + } + } +} diff --git a/src/main/java/com/laytonsmith/core/tool.java b/src/main/java/com/laytonsmith/core/tool.java new file mode 100644 index 0000000000..ea8804e826 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/tool.java @@ -0,0 +1,43 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.laytonsmith.core; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * CommandLineTools must be tagged with this annotation in order to be fully entered into the tool ecosystem. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@SuppressWarnings("checkstyle:typename") +public @interface tool { + + /** + * This is the tool name, i.e. when the user types mscript -- <tool>, what this should be. This may not + * contain any spaces. To prevent conflicts, it is highly recommended that when this is added from an extension, + * it start with {@code x-}. There is a guarantee that no tools defined directly in MethodScript + * will start with {@code x-}, and hopefully all extension authors follow the same protocol. + * @return + */ + String value(); + + /** + * If the tool has aliases, then they can be returned here. If there are none, that is allowed too. + * @return + */ + String[] aliases() default {}; + + /** + * Default to false, but if set to true, does not show up in the help file, though will still be shown if + * specifically requested. This is meant for incubating features. + * @return + */ + boolean undocumented() default false; + +} diff --git a/src/main/java/com/laytonsmith/core/webserver/InvalidVerbException.java b/src/main/java/com/laytonsmith/core/webserver/InvalidVerbException.java new file mode 100644 index 0000000000..dff65f0cbb --- /dev/null +++ b/src/main/java/com/laytonsmith/core/webserver/InvalidVerbException.java @@ -0,0 +1,22 @@ +package com.laytonsmith.core.webserver; + +/** + * + */ +public class InvalidVerbException extends Exception { + + /** + * Creates a new instance of InvalidVerbException without detail message. + */ + public InvalidVerbException() { + } + + /** + * Constructs an instance of InvalidVerbException with the specified detail message. + * + * @param msg the detail message. + */ + public InvalidVerbException(String msg) { + super(msg); + } +} diff --git a/src/main/java/com/laytonsmith/core/webserver/ReverseProxyListener.java b/src/main/java/com/laytonsmith/core/webserver/ReverseProxyListener.java new file mode 100644 index 0000000000..704a73f7cd --- /dev/null +++ b/src/main/java/com/laytonsmith/core/webserver/ReverseProxyListener.java @@ -0,0 +1,169 @@ +package com.laytonsmith.core.webserver; + +import com.laytonsmith.PureUtilities.ArgumentParser; +import com.laytonsmith.PureUtilities.ArgumentParser.ArgumentBuilder; +import com.laytonsmith.PureUtilities.CommandExecutor; +import com.laytonsmith.PureUtilities.Common.ArrayUtils; +import com.laytonsmith.core.AbstractCommandLineTool; +import com.laytonsmith.core.Main; +import com.laytonsmith.core.tool; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * + */ +public class ReverseProxyListener { + + public static void main(String[] args) throws Exception { + // For easy testing + Main.main(new String[]{"webserver", "--start", "--foreground"}); + } + + @tool("webserver") + public static class WebServerCtrl extends AbstractCommandLineTool { + + @Override + public ArgumentParser getArgumentParser() { + return ArgumentParser.GetParser() + .addDescription("Provides a controller interface for a MethodScript web server. Note that this is" + + " not meant to be used as a front facing web server, and is instead meant to be used" + + " as a backend server behind an Nginx, Apache, or other frontend web server.") + .addExtendedDescription("In addition to providing the installer, this provides an" + + " easy way to control the webserver from the command line. This server cannot" + + " be used as a server frontend, it will only work with MethodScript files, and" + + " cannot serve static content, such as css and javascript files. It provides" + + " a special runtime environment for efficiently running behind another front facing" + + " web server, such as Nginx, Apache, or any other server that supports \"reverse" + + " proxy\" techniques.") + .setErrorOnUnknownArgs(true) + .addArgument(new ArgumentBuilder() + .setDescription("Installs the webserver. This creates the config file templates, and" + + " creates other folders as needed.") + .asFlag() + .setName("install")) + .addArgument(new ArgumentBuilder() + .setDescription("Starts the webserver.") + .asFlag() + .setName("start")) + .addArgument(new ArgumentBuilder() + .setDescription("(Used with the --start flag) Does not create a daemon process," + + " and runs directly" + + " in the current shell. Note that Ctrl-C is supported in this mode, and does a" + + " non-graceful shutdown. Ctrl-D will perform a graceful shutdown.") + .asFlag() + .setName("foreground")) + .addArgument(new ArgumentBuilder() + .setDescription("Stops the webserver.") + .asFlag() + .setName("stop")) + .addArgument(new ArgumentBuilder() + .setDescription("(Used with the --stop flag) By default, the server stops gracefully," + + " allowing running scripts to finish," + + " but stopping accepting new connections. If you wish to force an immediate shutdown" + + " however, you can use this flag.") + .asFlag() + .setName("force")) + .addArgument(new ArgumentBuilder() + .setDescription("Causes the webserver to discard the compiled scripts, and recompile" + + " everything. This has the same effect as restarting the process, but is quicker.") + .asFlag() + .setName("recompile")); + } + + @Override + public void execute(ArgumentParser.ArgumentParserResults parsedArgs) throws Exception { + if(parsedArgs.isFlagSet("install")) { + install(); + } else if(parsedArgs.isFlagSet("start")) { + start(parsedArgs.isFlagSet("foreground")); + } else if(parsedArgs.isFlagSet("stop")) { + stop(!parsedArgs.isFlagSet("force")); + } else if(parsedArgs.isFlagSet("recompile")) { + recompile(); + } else { + throw new Exception("Unsupported command"); + } + } + + @Override + public boolean noExitOnReturn() { + return true; + } + + private void install() throws IOException { + File prefs = ReverseProxySettings.getPrefsFile(); + ReverseProxySettings.init(prefs); + ReverseProxySettings.getCtrlFolder().mkdirs(); + WebServerController.GetWebServer().install(); + } + + private void start(boolean foreground) throws Exception { + if(WebServerController.GetWebServer().isServerUp()) { + System.err.println("Server already running. Stop the server first with --stop."); + System.exit(1); + } + if(!foreground) { + restartInForeground(); + return; + } + + System.out.println("Starting server"); + + WebServerController.GetWebServer().start(); + } + + @SuppressWarnings("SleepWhileInLoop") + private void restartInForeground() throws IOException { + List largs = new ArrayList<>(); + largs.add("mscript"); + largs.add("--"); + largs.add("webserver"); + largs.add("--start"); + largs.add("--foreground"); + CommandExecutor ce = new CommandExecutor(largs.toArray(new String[largs.size()])); + ce.start(); + while(!WebServerController.GetWebServer().isServerUp()) { + try { + Thread.sleep(1000); + } catch (InterruptedException ex) { + // + } + } + System.out.println("Started in background. pid: " + WebServerController.GetWebServer().getPid()); + System.exit(0); + } + + private void stop(boolean graceful) { + try { + WebServerController.GetWebServer().writeCmd(WebServerController.Verb.STOP, new byte[]{graceful ? (byte) 1 : (byte) 0}); + } catch (ServerNotRunningException ex) { + System.err.println("Server not running"); + System.exit(1); + } catch (IOException ex) { + System.err.println("Could not communicate with server:"); + ex.printStackTrace(System.err); + System.exit(1); + } + System.exit(0); + } + + private void recompile() { + try { + WebServerController.GetWebServer().writeCmd(WebServerController.Verb.RECOMPILE, ArrayUtils.EMPTY_BYTE_ARRAY); + // TODO: Wait for and read any error messages printed out + } catch (ServerNotRunningException ex) { + System.err.println("Server not running"); + System.exit(1); + } catch (IOException ex) { + System.err.println("Could not communicate with server:"); + ex.printStackTrace(System.err); + System.exit(1); + } + } + + } + +} diff --git a/src/main/java/com/laytonsmith/core/webserver/ReverseProxySettings.java b/src/main/java/com/laytonsmith/core/webserver/ReverseProxySettings.java new file mode 100644 index 0000000000..3989fdcf8d --- /dev/null +++ b/src/main/java/com/laytonsmith/core/webserver/ReverseProxySettings.java @@ -0,0 +1,67 @@ +package com.laytonsmith.core.webserver; + +import com.laytonsmith.PureUtilities.Common.OSUtils; +import com.laytonsmith.PureUtilities.Preferences; +import com.laytonsmith.PureUtilities.Preferences.GroupData; +import com.laytonsmith.abstraction.Implementation; +import com.laytonsmith.core.MethodScriptFileLocations; +import com.laytonsmith.core.Static; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; + +/** + * + */ +public class ReverseProxySettings { + + private static final GroupData GENERAL_GROUP = new GroupData("General"); + + private static Preferences prefs; + + /** + * The path to the preferences file. By default, this is not created, unlike most pref files. + * + * @return + */ + public static File getPrefsFile() { + return new File(MethodScriptFileLocations.getDefault().getPreferencesDirectory(), "webserver.ini"); + } + + /** + * + * @return + */ + public static File getCtrlFolder() { + return new File(MethodScriptFileLocations.getDefault().getCacheDirectory(), "webserverctrl"); + } + + public static void init(final File f) throws IOException { + ArrayList a = new ArrayList<>(); + a.add(new Preferences.Preference("port", "16438", + Preferences.Type.NUMBER, + "The port to bind to.", GENERAL_GROUP)); + a.add(new Preferences.Preference("root", OSUtils.GetOS().isUnixLike() ? "/var/www" : "C:/inetpub/wwwroot", + Preferences.Type.FILE, + "The root of the web server.", GENERAL_GROUP)); + a.add(new Preferences.Preference("threads", "10", + Preferences.Type.INT, + "The number of concurrent requests to handle. The server will spin up this many threads at most" + + " to handle the incoming requests.", GENERAL_GROUP)); + + prefs = new Preferences(Implementation.GetServerType().getBranding() + " webserver", Static.getLogger(), a); + prefs.init(f); + } + + public static int getPort() { + return prefs.getIntegerPreference("port"); + } + + public static File getRoot() { + return prefs.getFilePreference("root"); + } + + public static int getThreads() { + return prefs.getIntegerPreference("threads"); + } +} diff --git a/src/main/java/com/laytonsmith/core/webserver/ServerAlreadyUpException.java b/src/main/java/com/laytonsmith/core/webserver/ServerAlreadyUpException.java new file mode 100644 index 0000000000..15335301da --- /dev/null +++ b/src/main/java/com/laytonsmith/core/webserver/ServerAlreadyUpException.java @@ -0,0 +1,23 @@ + +package com.laytonsmith.core.webserver; + +/** + * + */ +public class ServerAlreadyUpException extends RuntimeException { + + /** + * Creates a new instance of ServerAlreadyUpException without detail message. + */ + public ServerAlreadyUpException() { + } + + /** + * Constructs an instance of ServerAlreadyUpException with the specified detail message. + * + * @param msg the detail message. + */ + public ServerAlreadyUpException(String msg) { + super(msg); + } +} diff --git a/src/main/java/com/laytonsmith/core/webserver/ServerNotRunningException.java b/src/main/java/com/laytonsmith/core/webserver/ServerNotRunningException.java new file mode 100644 index 0000000000..cc74528eb5 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/webserver/ServerNotRunningException.java @@ -0,0 +1,8 @@ +package com.laytonsmith.core.webserver; + +/** + * + */ +public class ServerNotRunningException extends Exception { + +} diff --git a/src/main/java/com/laytonsmith/core/webserver/WebServer.java b/src/main/java/com/laytonsmith/core/webserver/WebServer.java new file mode 100644 index 0000000000..84b0604ca6 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/webserver/WebServer.java @@ -0,0 +1,37 @@ +package com.laytonsmith.core.webserver; + +import java.io.File; + +/** + * + */ +public class WebServer { + + public WebServer() { + + } + + /** + * Starts the server. + * @param port + * @param root + * @param threads + */ + public void start(int port, File root, int threads) { + + } + + /** + * Recompiles the scripts. + */ + public void recompile() { + + } + + /** + * Stops the server gracefully. + */ + public void stop() { + + } +} diff --git a/src/main/java/com/laytonsmith/core/webserver/WebServerController.java b/src/main/java/com/laytonsmith/core/webserver/WebServerController.java new file mode 100644 index 0000000000..4305c9d3eb --- /dev/null +++ b/src/main/java/com/laytonsmith/core/webserver/WebServerController.java @@ -0,0 +1,353 @@ +package com.laytonsmith.core.webserver; + +import com.laytonsmith.PureUtilities.Common.FileUtil; +import com.laytonsmith.PureUtilities.Common.OSUtils; +import com.laytonsmith.PureUtilities.Common.StackTraceUtils; +import com.laytonsmith.PureUtilities.Common.StreamUtils; +import com.laytonsmith.PureUtilities.SignalHandler; +import com.laytonsmith.PureUtilities.SignalType; +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.file.StandardOpenOption; +import java.util.Timer; +import java.util.TimerTask; + +/** + * + */ +@SuppressWarnings("PointlessBitwiseExpression") +public final class WebServerController { + + private static final int NORMAL_SHUTDOWN = 0; + private static final int HARD_SHUTDOWN = 1 << 0; + + + private final File ctrlDir = ReverseProxySettings.getCtrlFolder(); + private final File pidFile = new File(ctrlDir, "pid"); + private final File cmdFile = new File(ctrlDir, "cmd"); + + private final WebServer server; + + public static void main(String[] args) throws Exception { + ReverseProxyListener.main(args); + } + + /** + * Verbs are the supported out-of-band commands that can be sent to the web server by an external process. + */ + public static enum Verb { + /** + * Stops the server. + * + * Parameters: + * 0th: byte - 0 - Forces a shutdown, stopping scripts as needed. 1 - Gives scripts an unlimited amount of time + * to shut down, but does not accept any new connections. + */ + STOP((byte) 0, (server, cmd) -> { + byte graceful = cmd[1]; + server.shutdown(graceful != (byte) 0); + }), + /** + * Causes scripts to recompile. Currently processing requests will continue with the old script, + * but new requests will be served with the new scripts. + */ + RECOMPILE((byte) 1, (server, cmd) -> { + server.recompile(); + }); + + private final byte id; + private final VerbAction action; + + private Verb(byte id, VerbAction action) { + this.id = id; + this.action = action; + } + + /** + * Returns the ordinal of this Verb + * @return + */ + public byte getByte() { + return id; + } + + /** + * Returns the Verb, given a particular ordinal. + * @param b The ordinal + * @return The given Verb + * @throws InvalidVerbException If the ordinal is out of range. Internally, this should always be consistent + * (or it's a programmer error) but it could be that the commandor process is running a different version + * than the commandee. + */ + public static Verb fromByte(byte b) throws InvalidVerbException { + for(Verb v : values()) { + if(v.id == b) { + return v; + } + } + throw new InvalidVerbException("Unrecognized verb: " + b); + } + + /** + * Executes the given verb against the server. + * @param server + * @param cmd The full command, as some Verbs accept argument, which are individually parsed. + */ + public void execute(WebServerController server, byte[] cmd) { + this.action.activate(server, cmd); + } + + private static interface VerbAction { + void activate(WebServerController server, byte[] cmd); + } + + } + + /** + * Sends the given command to the webserver. + * @param cmd The command to run + * @param params If the verb accepts arguments, these are provided here. The verbs are individually responsible + * for deserializing the parameters. + * @throws ServerNotRunningException If the server does not appear to be running. + * @throws java.io.IOException If an IOException occurred, for instance if the ctrl files can't be read/written to. + */ + public synchronized void writeCmd(Verb cmd, byte[] params) throws ServerNotRunningException, IOException { + if(!isServerUp()) { + throw new ServerNotRunningException(); + } + FileChannel ch = FileChannel.open(cmdFile.toPath(), StandardOpenOption.CREATE, StandardOpenOption.WRITE); + FileLock lock = null; + try { + lock = ch.lock(); + ch.truncate(0); + byte[] data = new byte[params.length + 1]; + data[0] = cmd.getByte(); + System.arraycopy(params, 0, data, 1, params.length); + ByteBuffer contents = ByteBuffer.wrap(data); + ch.write(contents); + } finally { + if(lock != null) { + lock.release(); + } + ch.close(); + } + } + + /** + * Checks if the server is up and running. + * @return + * @throws java.io.IOException + */ + public boolean isServerUp() throws IOException { + if(!ctrlDir.exists()) { + return false; + } + if(!pidFile.exists()) { + return false; + } + + long pid = getPid(); + return OSUtils.GetRunningProcesses().stream().anyMatch(p -> p.getPid() == pid); + } + + public long getPid() throws IOException { + FileChannel ch = FileChannel.open(pidFile.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE); + FileLock lock = null; + try { + lock = ch.lock(); + long pid = Long.parseLong(FileUtil.read(ch)); + return pid; + } finally { + if(lock != null) { + lock.release(); + } + ch.close(); + } + } + + /** + * Given the presumed already initialized prefs file, installs to systemd if on a supported unix system. + * @throws java.io.IOException + */ + public void install() throws IOException { + if(OSUtils.GetOS().isUnixLike()) { + installUnix(); + } else if(OSUtils.GetOS().isWindows()) { + // No op for now + } + } + + /** + * On Windows, this resolves to a bogus file, but doesn't matter, it isn't used. + */ + private static final File SYSTEMD = new File("/lib/systemd/system"); + + private void installUnix() throws IOException { + if(!SYSTEMD.exists()) { + return; + } + FileUtil.write(StreamUtils.GetResource("systemd.service").replace("%%PIDFILE%%", pidFile.getAbsolutePath()), + new File(SYSTEMD, "msws.service"), true); + } + + + public void start() throws ServerAlreadyUpException, IOException { + if(isServerUp()) { + throw new ServerAlreadyUpException(); + } + log("Starting up..."); + File prefs = ReverseProxySettings.getPrefsFile(); + ReverseProxySettings.init(prefs); + ctrlDir.mkdirs(); + registerPid(); + startWatchDog(); + registerShutdownListeners(); + server.start(ReverseProxySettings.getPort(), + ReverseProxySettings.getRoot(), + ReverseProxySettings.getThreads()); + log("Started"); + } + + private void registerShutdownListeners() { + SignalHandler.addStopHandlers((SignalType type) -> { + shutdown(false); + return true; + }); + } + + private void registerPid() throws IOException { + FileChannel ch = FileChannel.open(pidFile.toPath(), StandardOpenOption.CREATE, StandardOpenOption.WRITE); + FileLock lock = null; + try { + lock = ch.lock(); + FileUtil.write(ch, Long.toString(OSUtils.GetMyPid())); + } finally { + pidFile.deleteOnExit(); + cmdFile.deleteOnExit(); + if(lock != null) { + lock.release(); + } + ch.close(); + } + } + + private boolean stopWatchdog = false; + + + private void startWatchDog() { + // Need to run as not a daemon, since we do I/O. + final Timer t = new Timer("MSWS-Watchdog", false); + t.schedule(new TimerTask() { + @Override + public void run() { + if(stopWatchdog) { + t.cancel(); + stopWatchdog = false; + return; + } + try { + readCmd(); + } catch (IOException ex) { + log(ex); + } catch (InvalidVerbException ex) { + log("Invalid verb specified. Are you running the same version?"); + } + } + + }, 0, 1000); + log("Watchdog thread started"); + } + + private void stopWatchdog() throws InterruptedException { + this.stopWatchdog = true; + Thread.sleep(2000); + } + + /** + * The watchdog timer reads commands at a steady rate of 1 per second or so. The command is read, cleared from + * the file, a new thread kicked off to actually handle the command, and then the command file unlocked. + * It is super important that this thread continues to run until explicitly shut down, which is the absolute + * last operation, so that if an operator wishes to try a graceful shutdown at first, but can't, they can still + * issue commands to the server (for instance a non-graceful shutdown). Therefore, we can't make any assumptions + * here about what a given command does. + * @throws IOException + * @throws InvalidVerbException + */ + private void readCmd() throws IOException, InvalidVerbException { + if(!cmdFile.exists()) { + return; + } + FileChannel ch = FileChannel.open(cmdFile.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE); + FileLock lock = null; + try { + lock = ch.lock(); + byte[] cmd = FileUtil.readData(ch); + if(cmd.length < 1) { + // No command. + return; + } + Verb verb = Verb.fromByte(cmd[0]); + FileUtil.write(ch, ""); + new Thread(() -> { + verb.execute(WebServerController.this, cmd); + }, "VerbExecute-" + verb.name()).start(); + } finally { + if(lock != null) { + lock.release(); + } + ch.close(); + } + } + + public void shutdown(boolean graceful) { + log((graceful ? "Graceful" : "Hard") + " shutdown detected"); + if(!graceful) { + System.exit(HARD_SHUTDOWN); + } + server.stop(); + try { + stopWatchdog(); + } catch (InterruptedException ex) { + // + } + System.exit(NORMAL_SHUTDOWN); + } + + public void recompile() { + server.recompile(); + } + + private void log(Throwable t) { + log(StackTraceUtils.GetStacktrace(t)); + } + + private void log(String message) { + StreamUtils.GetSystemOut().println(message); + } + + private WebServerController() { + server = new WebServer(); + } + + private static volatile WebServerController webServerController = null; + private static final Object WEB_SERVER_CONTROLLER_LOCK = new Object(); + + public static WebServerController GetWebServer() { + @SuppressWarnings("LocalVariableHidesMemberVariable") + WebServerController webServer = WebServerController.webServerController; + if(webServer == null) { + synchronized(WEB_SERVER_CONTROLLER_LOCK) { + webServer = WebServerController.webServerController; + if(webServer == null) { + WebServerController.webServerController = webServer = new WebServerController(); + } + } + } + return webServer; + } + + +} diff --git a/src/main/java/com/laytonsmith/database/MSSQLProfile.java b/src/main/java/com/laytonsmith/database/MSSQLProfile.java new file mode 100644 index 0000000000..0641328746 --- /dev/null +++ b/src/main/java/com/laytonsmith/database/MSSQLProfile.java @@ -0,0 +1,132 @@ +package com.laytonsmith.database; + +import com.laytonsmith.PureUtilities.Common.OSUtils; +import com.laytonsmith.PureUtilities.Common.ReflectionUtils; +import com.laytonsmith.core.MethodScriptFileLocations; +import com.laytonsmith.core.Profiles; +import com.microsoft.sqlserver.jdbc.SQLServerDriver; +import java.io.File; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * + */ +@Profiles.ProfileType(type = "mssql") +public class MSSQLProfile extends SQLProfile { + + private final String host; + private final String instance; + private final int port; + private final String database; + private final String username; + private final String password; + private static final List HANDLED_LIST = Arrays.asList( + "database", "username", "password", "host", "instance", "port" + ); + private final Map extraParameters = new HashMap<>(); + + public MSSQLProfile(String id, Map elements) throws Profiles.InvalidProfileException { + super(id, elements); + if(!elements.containsKey("database")) { + throw new Profiles.InvalidProfileException("Required \"database\" tag is missing for profile \"" + + id + "\""); + } + database = elements.get("database"); + if(elements.containsKey("username")) { + username = elements.get("username"); + } else { + username = null; + } + if(elements.containsKey("password")) { + password = elements.get("password"); + } else { + password = null; + } + if(elements.containsKey("azureHost")) { + String host = elements.get("azureHost") + ".database.windows.net"; + elements.remove("azureHost"); + elements.put("host", host); + } + if(elements.containsKey("host")) { + host = elements.get("host"); + } else { + host = "localhost"; + } + if(elements.containsKey("instance")) { + instance = "\\" + elements.get("instance"); + } else { + instance = ""; + } + + if(elements.containsKey("port")) { + try { + port = Integer.parseInt(elements.get("port")); + } catch (NumberFormatException ex) { + throw new Profiles.InvalidProfileException(ex.getMessage()); + } + } else { + port = 1433; + } + + if(elements.containsKey("integratedSecurity")) { + String dllName = (String) ReflectionUtils.get(SQLServerDriver.class, "AUTH_DLL_NAME"); + if(!new File(MethodScriptFileLocations.getDefault() + .getWindowsNativeDirectory(), dllName + ".dll").exists()) { + if(OSUtils.GetOS().isWindows()) { + throw new Profiles.InvalidProfileException("integratedSecurity was configured, but MethodScript is" + + " not properly configured. Please run `mscript -- install-mssql-auth` to automatically" + + " configure your system."); + } else { + throw new Profiles.InvalidProfileException("Integrated Security is only available on Windows."); + } + } + } + + for(Map.Entry entry : elements.entrySet()) { + if(!HANDLED_LIST.contains(entry.getKey())) { + extraParameters.put(entry.getKey(), entry.getValue()); + } + } + } + + @Override + public String getConnectionString() throws SQLException { + try { + Class.forName(com.microsoft.sqlserver.jdbc.SQLServerDriver.class.getName()); + } catch (ClassNotFoundException ex) { + throw new SQLException("Cannot load MSSQL. Check your installation and try again"); + } + + String connectionString; + connectionString = "jdbc:sqlserver://" + host; + if(instance != null) { + connectionString += "\\" + instance; + } + + connectionString += ":" + port; + connectionString += ";"; + if(username != null) { + connectionString += "user=" + username + ";"; + } + if(password != null) { + connectionString += "password=" + password + ";"; + } + + connectionString += "databaseName=" + database + ";"; + + for(Map.Entry params : extraParameters.entrySet()) { + connectionString += params.getKey() + "=" + params.getValue() + ";"; + } + return connectionString; + } + + @Override + public String toString() { + return super.toString() + " " + host + instance + ":" + port; + } + +} diff --git a/src/main/java/com/laytonsmith/database/MySQLProfile.java b/src/main/java/com/laytonsmith/database/MySQLProfile.java index b8969b1345..b2cbd6ae05 100644 --- a/src/main/java/com/laytonsmith/database/MySQLProfile.java +++ b/src/main/java/com/laytonsmith/database/MySQLProfile.java @@ -17,29 +17,30 @@ public class MySQLProfile extends SQLProfile { private final String database; private final String username; private final String password; + private final Boolean useSSL; public MySQLProfile(String id, Map elements) throws Profiles.InvalidProfileException { super(id, elements); - if (!elements.containsKey("database")) { + if(!elements.containsKey("database")) { throw new Profiles.InvalidProfileException("Required \"database\" tag is missing for profile \"" + id + "\""); } database = elements.get("database"); - if (elements.containsKey("username")) { + if(elements.containsKey("username")) { username = elements.get("username"); } else { username = null; } - if (elements.containsKey("password")) { + if(elements.containsKey("password")) { password = elements.get("password"); } else { password = null; } - if (elements.containsKey("host")) { + if(elements.containsKey("host")) { host = elements.get("host"); } else { host = "localhost"; } - if (elements.containsKey("port")) { + if(elements.containsKey("port")) { try { port = Integer.parseInt(elements.get("port")); } catch (NumberFormatException ex) { @@ -48,6 +49,11 @@ public MySQLProfile(String id, Map elements) throws Profiles.Inv } else { port = 3306; } + if(elements.containsKey("useSSL")) { + useSSL = Boolean.parseBoolean(elements.get("useSSL")); + } else { + useSSL = null; + } } public String getDatabase() { @@ -65,15 +71,18 @@ public String getPassword() { @Override public String getConnectionString() throws SQLException { try { - Class.forName(com.mysql.jdbc.Driver.class.getName()); + Class.forName(com.mysql.cj.jdbc.Driver.class.getName()); } catch (ClassNotFoundException ex) { throw new SQLException("Cannot load MySQL. Check your installation and try again"); } try { - return "jdbc:mysql://" + host + ":" + port + "/" + database + "?generateSimpleParameterMetadata=true" + return "jdbc:mysql://" + host + ":" + port + "/" + database + + "?generateSimpleParameterMetadata=true" + "&jdbcCompliantTruncation=false" + + "&autoReconnect=true" + (username == null ? "" : "&user=" + URLEncoder.encode(username, "UTF-8")) - + (password == null ? "" : "&password=" + URLEncoder.encode(password, "UTF-8")); + + (password == null ? "" : "&password=" + URLEncoder.encode(password, "UTF-8")) + + (useSSL == null ? "" : "&useSSL=" + useSSL); } catch (UnsupportedEncodingException ex) { throw new Error(); } diff --git a/src/main/java/com/laytonsmith/database/PostgreSQLProfile.java b/src/main/java/com/laytonsmith/database/PostgreSQLProfile.java index faa4e62e50..e824c9fe0a 100644 --- a/src/main/java/com/laytonsmith/database/PostgreSQLProfile.java +++ b/src/main/java/com/laytonsmith/database/PostgreSQLProfile.java @@ -21,26 +21,26 @@ public class PostgreSQLProfile extends SQLProfile { public PostgreSQLProfile(String id, Map elements) throws Profiles.InvalidProfileException { super(id, elements); - if (!elements.containsKey("database")) { + if(!elements.containsKey("database")) { throw new Profiles.InvalidProfileException("Required \"database\" tag is missing for profile \"" + id + "\""); } database = elements.get("database"); - if (elements.containsKey("username")) { + if(elements.containsKey("username")) { username = elements.get("username"); } else { username = null; } - if (elements.containsKey("password")) { + if(elements.containsKey("password")) { password = elements.get("password"); } else { password = null; } - if (elements.containsKey("host")) { + if(elements.containsKey("host")) { host = elements.get("host"); } else { host = "localhost"; } - if (elements.containsKey("port")) { + if(elements.containsKey("port")) { try { port = Integer.parseInt(elements.get("port")); } catch (NumberFormatException ex) { @@ -73,7 +73,7 @@ public boolean getAutogeneratedKeys(String query) { public String getConnectionString() throws SQLException { try { Class.forName(org.postgresql.Driver.class.getName()); - } catch(ClassNotFoundException ex){ + } catch (ClassNotFoundException ex) { throw new SQLException("Could not load PostgreSQL, check your installation and try again"); } try { diff --git a/src/main/java/com/laytonsmith/database/SQLProfile.java b/src/main/java/com/laytonsmith/database/SQLProfile.java index 1c5862afad..bbfd7b3bd3 100644 --- a/src/main/java/com/laytonsmith/database/SQLProfile.java +++ b/src/main/java/com/laytonsmith/database/SQLProfile.java @@ -14,10 +14,9 @@ public SQLProfile(String id, Map elements) { } /** - * Given the connection details, this should return the proper connection - * string that the actual database connector will use to create a connection - * with this profile. Additionally, during this step, it should be verified - * that the SQL driver is present. + * Given the connection details, this should return the proper connection string that the actual database connector + * will use to create a connection with this profile. Additionally, during this step, it should be verified that the + * SQL driver is present. * * @return * @throws SQLException If the database driver doesn't exist. @@ -25,14 +24,23 @@ public SQLProfile(String id, Map elements) { public abstract String getConnectionString() throws SQLException; /** - * Returns true if the query calls for autogenerated keys to be returned. By default, - * we return true, because most database systems can handle having it always - * be true, but some database systems will fail on things like SELECT statements - * if autogenerated keys are requested on non-INSERT statements. + * Returns true if the query calls for autogenerated keys to be returned. By default, we return true, because most + * database systems can handle having it always be true, but some database systems will fail on things like SELECT + * statements if autogenerated keys are requested on non-INSERT statements. + * * @param query The query to * @return */ public boolean getAutogeneratedKeys(String query) { return true; } + + /** + * Returns true if parameter types are provided by this driver implementation before being set. + * + * @return + */ + public boolean providesParameterTypes() { + return true; + } } diff --git a/src/main/java/com/laytonsmith/database/SQLiteProfile.java b/src/main/java/com/laytonsmith/database/SQLiteProfile.java index 8bc81a5da8..ae0f456aed 100644 --- a/src/main/java/com/laytonsmith/database/SQLiteProfile.java +++ b/src/main/java/com/laytonsmith/database/SQLiteProfile.java @@ -2,6 +2,9 @@ import com.laytonsmith.core.Profiles; import com.laytonsmith.core.MethodScriptFileLocations; +import org.sqlite.SQLiteJDBCLoader; +import org.sqlite.util.OSInfo; + import java.io.File; import java.sql.SQLException; import java.util.Map; @@ -16,16 +19,16 @@ public class SQLiteProfile extends SQLProfile { public SQLiteProfile(String id, Map elements) throws Profiles.InvalidProfileException { super(id, elements); - if(!elements.containsKey("file")){ + if(!elements.containsKey("file")) { throw new Profiles.InvalidProfileException("\"file\" parameter is required for profile \"" + id + "\""); } file = elements.get("file"); } - public File getFile(){ + public File getFile() { File f = new File(file); - if(!f.isAbsolute()){ - f = new File(MethodScriptFileLocations.getDefault().getSQLProfilesFile(), f.getPath()); + if(!f.isAbsolute()) { + f = new File(MethodScriptFileLocations.getDefault().getProfilesFile().getParentFile(), f.getPath()); } return f; } @@ -37,7 +40,28 @@ public String getConnectionString() throws SQLException { } catch (ClassNotFoundException ex) { throw new SQLException("Cannot load SQLite. Check your installation and try again"); } + // Set native library override path if not already set. (e.g. -Dorg.sqlite.lib.path="/path/to/lib") + if(System.getProperty("org.sqlite.lib.path") == null) { + System.setProperty("org.sqlite.lib.path", new File(MethodScriptFileLocations.getDefault().getConfigDirectory(), + "sqlite/native/" + OSInfo.getNativeLibFolderPathForCurrentOS()).getAbsolutePath()); + } + try { + // Load native library before connection to detect when the library is missing. + // This is done in SQLiteDataSource as well. + SQLiteJDBCLoader.initialize(); + } catch (Exception ex) { + throw new SQLException("Failed to load a native sqlite library for your platform." + + " You can download the library file from" + + " https://github.com/xerial/sqlite-jdbc/tree/master/src/main/resources/org/sqlite/native/" + + OSInfo.getNativeLibFolderPathForCurrentOS() + " and place it into " + + System.getProperty("org.sqlite.lib.path")); + } return "jdbc:sqlite:" + getFile(); } + @Override + public boolean providesParameterTypes() { + return false; + } + } diff --git a/src/main/java/com/laytonsmith/persistence/AbstractDataSource.java b/src/main/java/com/laytonsmith/persistence/AbstractDataSource.java index da09b676cc..8faf3b8686 100644 --- a/src/main/java/com/laytonsmith/persistence/AbstractDataSource.java +++ b/src/main/java/com/laytonsmith/persistence/AbstractDataSource.java @@ -9,7 +9,6 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; -import java.util.Arrays; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; @@ -30,8 +29,7 @@ public abstract class AbstractDataSource implements DataSource { private ConnectionMixin connectionMixin; private ConnectionMixinFactory.ConnectionMixinOptions mixinOptions; private boolean inTransaction = false; - - + protected AbstractDataSource() { try { uri = new URI(""); @@ -40,26 +38,29 @@ protected AbstractDataSource() { } } - protected AbstractDataSource(URI uri, ConnectionMixinFactory.ConnectionMixinOptions mixinOptions) throws DataSourceException { + protected AbstractDataSource(URI uri, ConnectionMixinFactory.ConnectionMixinOptions mixinOptions) + throws DataSourceException { this.uri = uri; this.mixinOptions = mixinOptions; setInvalidModifiers(); - DataSourceModifier[] implicit = this.implicitModifiers(); - if (implicit != null) { - for (DataSourceModifier dsm : this.implicitModifiers()) { + EnumSet implicit = this.implicitModifiers(); + if(implicit != null) { + for(DataSourceModifier dsm : this.implicitModifiers()) { addModifier(dsm); } } } - + /** * Gets the connection mixin for this connection type. + * * @return - * @throws DataSourceException + * @throws DataSourceException */ - protected ConnectionMixin getConnectionMixin() throws DataSourceException{ - if(connectionMixin == null){ - connectionMixin = ConnectionMixinFactory.GetConnectionMixin(uri, modifiers, mixinOptions, getBlankDataModel()); + protected ConnectionMixin getConnectionMixin() throws DataSourceException { + if(connectionMixin == null) { + connectionMixin = ConnectionMixinFactory.GetConnectionMixin(uri, modifiers, mixinOptions, + getBlankDataModel()); } return connectionMixin; } @@ -78,15 +79,18 @@ public final Map getValues(String[] leadKey) throws DataSource checkGet(leadKey); return getValues0(leadKey); } - + /** - * By default, we use the naive method to get the values, by getting the keys in step 1, then - * performing x gets to retrieve the values. This can probably be optimized - * to reduce the number of get calls in some data sources, and should be overridden if so. + * By default, we use the naive method to get the values, by getting the keys in step 1, then performing x gets to + * retrieve the values. This can probably be optimized to reduce the number of get calls in some data sources, and + * should be overridden if so. + * @param leadKey + * @return + * @throws com.laytonsmith.persistence.DataSourceException */ protected Map getValues0(String[] leadKey) throws DataSourceException { Map map = new HashMap<>(); - for(String [] key : getNamespace(leadKey)){ + for(String[] key : getNamespace(leadKey)) { map.put(key, get(key)); } return map; @@ -103,57 +107,65 @@ public final void stopTransaction(DaemonManager dm, boolean rollback) throws Dat inTransaction = false; stopTransaction0(dm, rollback); } - + /** - * Returns true if we are currently in a transaction. Inside of the call to - * stopTransaction, this will be false. - * @return + * Returns true if we are currently in a transaction. Inside of the call to stopTransaction, this will be false. + * + * @return */ - public boolean inTransaction(){ + public boolean inTransaction() { return inTransaction; } - + protected abstract void startTransaction0(DaemonManager dm); - - protected abstract void stopTransaction0(DaemonManager dm, boolean rollback) throws DataSourceException, IOException; + + protected abstract void stopTransaction0(DaemonManager dm, boolean rollback) throws DataSourceException, + IOException; @Override - public final boolean set(DaemonManager dm, String[] key, String value) throws ReadOnlyException, DataSourceException, IOException { + public final boolean set(DaemonManager dm, String[] key, String value) throws ReadOnlyException, + DataSourceException, IOException { checkSet(key); return set0(dm, key, value); } - + /** - * Subclasses should implement this, instead of set(), as our version of set() does some standard validation - * on the input. + * Subclasses should implement this, instead of set(), as our version of set() does some standard validation on the + * input. + * + * @param dm * @param key * @param value * @return * @throws ReadOnlyException * @throws DataSourceException - * @throws IOException + * @throws IOException */ - protected abstract boolean set0(DaemonManager dm, String[] key, String value) throws ReadOnlyException, DataSourceException, IOException; - + protected abstract boolean set0(DaemonManager dm, String[] key, String value) throws ReadOnlyException, + DataSourceException, IOException; + /** - * Subclasses should implement this, instead of get(), as our version of get() does - * some standard validation on the input. + * Subclasses should implement this, instead of get(), as our version of get() does some standard validation on the + * input. + * * @param key - * @return + * @return + * @throws com.laytonsmith.persistence.DataSourceException */ protected abstract String get0(String[] key) throws DataSourceException; /** - * The default implementation of string simply walks through keySet, and - * manually joins the keys together. If an implementation can provide a - * more efficient method, this should be overridden. + * The default implementation of string simply walks through keySet, and manually joins the keys together. If an + * implementation can provide a more efficient method, this should be overridden. * + * @param keyBase * @return + * @throws com.laytonsmith.persistence.DataSourceException */ @Override - public Set stringKeySet(String [] keyBase) throws DataSourceException { - Set keys = new TreeSet(); - for (String[] key : keySet(keyBase)) { + public Set stringKeySet(String[] keyBase) throws DataSourceException { + Set keys = new TreeSet<>(); + for(String[] key : keySet(keyBase)) { keys.add(StringUtils.Join(key, ".")); } return keys; @@ -161,10 +173,10 @@ public Set stringKeySet(String [] keyBase) throws DataSourceException { @Override public Set getNamespace(String[] namespace) throws DataSourceException { - Set list = new HashSet(); + Set list = new HashSet<>(); String ns = StringUtils.Join(namespace, "."); - for (String key : stringKeySet(namespace)) { - if ("".equals(ns) //Blank string; this means they want it to always match. + for(String key : stringKeySet(namespace)) { + if("".equals(ns) //Blank string; this means they want it to always match. || key.matches(Pattern.quote(ns) + "(?:$|\\..*)")) { String[] split = key.split("\\."); list.add(split); @@ -174,11 +186,11 @@ public Set getNamespace(String[] namespace) throws DataSourceException } private void setInvalidModifiers() { - DataSourceModifier[] invalid = this.invalidModifiers(); - if (invalid == null) { + EnumSet invalid = this.invalidModifiers(); + if(invalid == null) { return; } - this.invalidModifiers = EnumSet.copyOf(Arrays.asList(invalid)); + this.invalidModifiers = EnumSet.copyOf(invalid); } @Override @@ -188,83 +200,89 @@ public final String getName() { @Override public final void addModifier(DataSourceModifier modifier) { - if (invalidModifiers != null && invalidModifiers.contains(modifier)) { + if(invalidModifiers != null && invalidModifiers.contains(modifier)) { return; } - if (modifier == DataSourceModifier.HTTP || modifier == DataSourceModifier.HTTPS) { + if(modifier == DataSourceModifier.HTTP || modifier == DataSourceModifier.HTTPS) { modifiers.add(DataSourceModifier.READONLY); modifiers.add(DataSourceModifier.ASYNC); } - if (modifier == DataSourceModifier.SSH){ + if(modifier == DataSourceModifier.SSH) { modifiers.add(DataSourceModifier.ASYNC); } modifiers.add(modifier); } - @Override public final boolean hasKey(String[] key) throws DataSourceException { checkGet(key); return hasKey0(key); } - + /** - * By default, returns true if the value stored is non-null. In general, - * if clearKey0 is overridden, this should be as well. + * By default, returns true if the value stored is non-null. In general, if clearKey0 is overridden, this should be + * as well. + * * @param key * @return - * @throws DataSourceException + * @throws DataSourceException */ - protected boolean hasKey0(String[] key) throws DataSourceException{ + protected boolean hasKey0(String[] key) throws DataSourceException { return get(key) != null; } - + @Override - public final void clearKey(DaemonManager dm, String [] key) throws ReadOnlyException, DataSourceException, IOException{ + public final void clearKey(DaemonManager dm, String[] key) throws ReadOnlyException, DataSourceException, + IOException { checkSet(key); clearKey0(dm, key); } - + /** - * By default, setting the value to null should clear the value, - * but that can be overridden if a data source has a better method. + * By default, setting the value to null should clear the value, but that can be overridden if a data source has a + * better method. + * * @param key * @throws ReadOnlyException * @throws DataSourceException - * @throws IOException + * @throws IOException */ - protected void clearKey0(DaemonManager dm, String [] key) throws ReadOnlyException, DataSourceException, IOException{ - set(dm, key, null); + protected void clearKey0(DaemonManager dm, String[] key) throws ReadOnlyException, DataSourceException, + IOException { + set(dm, key, null); } /** - * This method checks for invalid or non-sensical combinations of - * modifiers, and throws an exception if any combinations exist that are - * strange. + * This method checks for invalid or non-sensical combinations of modifiers, and throws an exception if any + * combinations exist that are strange. * * @throws DataSourceException */ public final void checkModifiers() throws DataSourceException { List errors = new ArrayList(); - if (invalidModifiers != null) { - for (DataSourceModifier dsm : invalidModifiers) { - if (modifiers.contains(dsm)) { - errors.add(uri.toString() + " contains the modifier " + dsm.getName() + ", which is not applicable. This will be ignored."); + if(invalidModifiers != null) { + for(DataSourceModifier dsm : invalidModifiers) { + if(modifiers.contains(dsm)) { + errors.add(uri.toString() + " contains the modifier " + dsm.getName() + ", which is not applicable." + + " This will be ignored."); } } } - if (modifiers.contains(DataSourceModifier.PRETTYPRINT) && modifiers.contains(DataSourceModifier.READONLY)) { - errors.add(uri.toString() + " contains both prettyprint and readonly modifiers, which doesn't make sense, because we cannot write out the file; prettyprint will be ignored."); + if(modifiers.contains(DataSourceModifier.PRETTYPRINT) && modifiers.contains(DataSourceModifier.READONLY)) { + errors.add(uri.toString() + " contains both prettyprint and readonly modifiers, which doesn't make sense," + + " because we cannot write out the file; prettyprint will be ignored."); modifiers.remove(DataSourceModifier.PRETTYPRINT); } - if ((modifiers.contains(DataSourceModifier.HTTP) || modifiers.contains(DataSourceModifier.HTTPS)) && modifiers.contains(DataSourceModifier.SSH)) { + if((modifiers.contains(DataSourceModifier.HTTP) || modifiers.contains(DataSourceModifier.HTTPS)) + && modifiers.contains(DataSourceModifier.SSH)) { errors.add(uri.toString() + " contains both http(s) and ssh modifiers."); } - if (modifiers.contains(DataSourceModifier.HTTP) && modifiers.contains(DataSourceModifier.HTTPS)) { - errors.add(uri.toString() + " contains both http and https modifiers. Because these are mutually exclusive, this doesn't make sense, and https will be assumed."); + if(modifiers.contains(DataSourceModifier.HTTP) && modifiers.contains(DataSourceModifier.HTTPS)) { + errors.add(uri.toString() + " contains both http and https modifiers. Because these are mutually exclusive," + + " this doesn't make sense, and https will be assumed."); modifiers.remove(DataSourceModifier.HTTP); } - if (!errors.isEmpty()) { + if(!errors.isEmpty()) { throw new DataSourceException(StringUtils.Join(errors, "\n")); } } @@ -274,37 +292,33 @@ public final boolean hasModifier(DataSourceModifier modifier) { return modifiers.contains(modifier); } - /** - * This method checks to see if a set operation should simply throw a - * ReadOnlyException based on the modifiers. - */ - private void checkSet(String [] key) throws ReadOnlyException { - for(String namespace : key){ - if("_".equals(namespace)){ - throw new IllegalArgumentException("In the key \"" + StringUtils.Join(key, ".") + ", the namespace \"_\" is not allowed." + private void checkKey(String[] key) { + for(String namespace : key) { + if("_".equals(namespace)) { + throw new IllegalArgumentException("In the key \"" + StringUtils.Join(key, ".") + ", the namespace" + + " \"_\" is not allowed." + " (Namespaces may contain an underscore, but may not be just an underscore.)"); } } - if (modifiers.contains(DataSourceModifier.READONLY)) { + } + + /** + * This method checks to see if a set operation should simply throw a ReadOnlyException based on the modifiers. + */ + private void checkSet(String[] key) throws ReadOnlyException { + checkKey(key); + if(modifiers.contains(DataSourceModifier.READONLY)) { throw new ReadOnlyException(); } } /** - * This method checks to see if get operations should re-populate at - * this time. If the data set is transient, it will do so. + * This method checks to see if get operations should re-populate at this time. If the data set is transient, it + * will do so. */ private void checkGet(String[] key) throws DataSourceException { - for(String namespace : key){ - if("_".equals(namespace)){ - throw new IllegalArgumentException("In the key \"" + StringUtils.Join(key, ".") + ", the namespace \"_\" is not allowed." - + " (Namespaces may contain an underscore, but may not be just an underscore.)"); - } - } - if(this.getModifiers().contains(DataSource.DataSourceModifier.TRANSIENT)){ - this.populate(); - } - if (hasModifier(DataSourceModifier.TRANSIENT)) { + checkKey(key); + if(hasModifier(DataSourceModifier.TRANSIENT)) { populate(); } } @@ -313,26 +327,25 @@ private void checkGet(String[] key) throws DataSourceException { public final Set getModifiers() { return EnumSet.copyOf(modifiers); } - + /** - * Subclasses that need a certain type of file to be the "blank" version - * of a data model can override this. By default, null is - * returned. + * Subclasses that need a certain type of file to be the "blank" version of a data model can override this. By + * default, empty string is returned. * * @return */ protected String getBlankDataModel() { return ""; } - + @Override - public String toString(){ + public String toString() { StringBuilder b = new StringBuilder(); - for(DataSourceModifier m : modifiers){ + for(DataSourceModifier m : modifiers) { b.append(m.getName().toLowerCase()).append(":"); } b.append(uri.toString()); return b.toString(); } - + } diff --git a/src/main/java/com/laytonsmith/persistence/CSVDataSource.java b/src/main/java/com/laytonsmith/persistence/CSVDataSource.java index 9c86e09294..e8786c9309 100644 --- a/src/main/java/com/laytonsmith/persistence/CSVDataSource.java +++ b/src/main/java/com/laytonsmith/persistence/CSVDataSource.java @@ -1,27 +1,28 @@ package com.laytonsmith.persistence; -import com.laytonsmith.core.CHVersion; +import com.laytonsmith.core.MSVersion; import com.laytonsmith.persistence.io.ConnectionMixinFactory; import java.net.URI; +import java.util.EnumSet; /** * - * + * */ //@datasource("csv") public class CSVDataSource extends StringSerializableDataSource { - + private CSVDataSource() { - + } - - public CSVDataSource(URI uri, ConnectionMixinFactory.ConnectionMixinOptions options) throws DataSourceException{ + + public CSVDataSource(URI uri, ConnectionMixinFactory.ConnectionMixinOptions options) throws DataSourceException { super(uri, options); } @Override protected void populateModel(String data) throws DataSourceException { - + } @Override @@ -30,28 +31,28 @@ protected String serializeModel() { } @Override - public DataSourceModifier[] implicitModifiers() { + public EnumSet implicitModifiers() { return null; } @Override - public DataSourceModifier[] invalidModifiers() { + public EnumSet invalidModifiers() { return null; } @Override public String docs() { return "CSV {csv:///path/to/csv/file.csv} This type stores data" - + " in a CSV format. All the pros and cons of yml apply" - + " here, but instead of using the yml style to store the" - + " data, values are stored as a CSV file. The CSV file" - + " must have exactly two entries per line, the key, then" - + " the value, then a newline."; + + " in a CSV format. All the pros and cons of yml apply" + + " here, but instead of using the yml style to store the" + + " data, values are stored as a CSV file. The CSV file" + + " must have exactly two entries per line, the key, then" + + " the value, then a newline."; } @Override - public CHVersion since() { - return CHVersion.V0_0_0; + public MSVersion since() { + return MSVersion.V0_0_0; } - + } diff --git a/src/main/java/com/laytonsmith/persistence/DataSource.java b/src/main/java/com/laytonsmith/persistence/DataSource.java index f18f9bc034..c0cbf63167 100644 --- a/src/main/java/com/laytonsmith/persistence/DataSource.java +++ b/src/main/java/com/laytonsmith/persistence/DataSource.java @@ -2,46 +2,43 @@ import com.laytonsmith.PureUtilities.DaemonManager; import com.laytonsmith.annotations.MustUseOverride; -import com.laytonsmith.core.CHVersion; +import com.laytonsmith.core.MSVersion; import com.laytonsmith.core.SimpleDocumentation; import java.io.IOException; +import java.util.EnumSet; import java.util.Map; import java.util.Set; /** - * All data sources must implement this interface. It provides methods to gather - * data about a data source, gather data from the data source, and (possibly) - * write to the data source. + * All data sources must implement this interface. It provides methods to gather data about a data source, gather data + * from the data source, and (possibly) write to the data source. + * * - * */ @MustUseOverride public interface DataSource extends SimpleDocumentation { /** - * Returns a list of keys stored in this interface. If keyBase is empty, - * all keys are returned, otherwise, only keys matching the - * namespace will be returned. + * Returns a list of keys stored in this interface. If keyBase is empty, all keys are returned, otherwise, only keys + * matching the namespace will be returned. + * * @return */ - public Set keySet(String [] keyBase) throws DataSourceException; + public Set keySet(String[] keyBase) throws DataSourceException; /** - * Returns a list of keys, pre-concatenated into dot notation. This may - * be equally inefficient for all data sources (getting keySet, then - * doing the concatenation one at a time), however if a data source is - * able to optimize for this, it is able. The same rule applies as keySet, - * if keyBase is empty, all keys are returned, otherwise, only the - * keys for that namespace are returned. + * Returns a list of keys, pre-concatenated into dot notation. This may be equally inefficient for all data sources + * (getting keySet, then doing the concatenation one at a time), however if a data source is able to optimize for + * this, it is able. The same rule applies as keySet, if keyBase is empty, all keys are returned, otherwise, only + * the keys for that namespace are returned. * * @return */ - public Set stringKeySet(String [] keyBase) throws DataSourceException; + public Set stringKeySet(String[] keyBase) throws DataSourceException; /** - * Given a namespace, returns all the keys in this data source that are - * in the namespace. For instance, if a.b.c is requested, then both the - * keys a.b.c.d and a.b.c.e would be returned. + * Given a namespace, returns all the keys in this data source that are in the namespace. For instance, if a.b.c is + * requested, then both the keys a.b.c.d and a.b.c.e would be returned. * * @param namespace * @return @@ -49,81 +46,71 @@ public interface DataSource extends SimpleDocumentation { public Set getNamespace(String[] namespace) throws DataSourceException; /** - * Retrieves a single value from the data source. If the value doesn't exist - * at all, null should be returned. + * Retrieves a single value from the data source. If the value doesn't exist at all, null should be returned. + * * @param key - * @return + * @return */ public String get(String[] key) throws DataSourceException; /** - * Retrieves a namespace from the data source. Unlike get, this - * will return multiple values, mapped to their key. For instance, - * getValues({"a"}) might return {"a": "value1", "a.b": "value2"}, where - * the "lead value" "a" is the namespace for which we retrieve all values - * under that key, including the value at "a" itself, if set. + * Retrieves a namespace from the data source. Unlike get, this will return multiple values, mapped to their key. + * For instance, getValues({"a"}) might return {"a": "value1", "a.b": "value2"}, where the "lead value" "a" is the + * namespace for which we retrieve all values under that key, including the value at "a" itself, if set. + * * @param leadKey * @return - * @throws DataSourceException + * @throws DataSourceException */ public Map getValues(String[] leadKey) throws DataSourceException; /** - * Sets a value in the data source. If value is null, the key is - * removed. + * Sets a value in the data source. If value is null, the key is removed. * * @param key * @param value * @return True if the value was changed, false otherwise. - * @throws ReadOnlyException If this data source is inherently read - * only, it will throw a read only exception if this method is called. + * @throws ReadOnlyException If this data source is inherently read only, it will throw a read only exception if + * this method is called. * @throws IllegalArgumentException If the key is invalid */ public boolean set(DaemonManager dm, String[] key, String value) throws ReadOnlyException, DataSourceException, IOException, IllegalArgumentException; /** - * Instructs this data source to repopulate its internal structure based - * on this data provided. The method will be called if the data source - * needs to refresh itself, though for inherently transient data sources - * (or if the transient data source flag is set), this method may do - * nothing. If the data source is unable to populate itself, it may - * throw an exception informing the user that there is no way to read - * the data at this time. + * Instructs this data source to repopulate its internal structure based on this data provided. The method will be + * called if the data source needs to refresh itself, though for inherently transient data sources (or if the + * transient data source flag is set), this method may do nothing. If the data source is unable to populate itself, + * it may throw an exception informing the user that there is no way to read the data at this time. */ public void populate() throws DataSourceException; /** - * For this instance of the data source, adds a modifier flag to the - * data source. This does not preclude a data source from inherently - * having certain flags, nor will having a flag in the array returned by - * invalidModifiers() preclude it from being set here. Some settings may - * not need to be inherently acted upon, however they may be referenced - * for informational purposes if nothing else. + * For this instance of the data source, adds a modifier flag to the data source. This does not preclude a data + * source from inherently having certain flags, nor will having a flag in the array returned by invalidModifiers() + * preclude it from being set here. Some settings may not need to be inherently acted upon, however they may be + * referenced for informational purposes if nothing else. * * @param modifier */ public void addModifier(DataSourceModifier modifier); /** - * If a data source always has a particular modifier, it should return - * those here. This is used to determine when to display configuration - * warnings, if a modifier is used in cases where it is implied. If the - * array would be empty, null may be returned. + * If a data source always has a particular modifier, it should return those here. This is used to determine when to + * display configuration warnings, if a modifier is used in cases where it is implied. If the array would be empty, + * null may be returned. * - * @param modifier * @return */ - public DataSourceModifier[] implicitModifiers(); + public EnumSet implicitModifiers(); /** - * If a data source has no possible way of acting on a modifier, it - * should return those here. This is used to determine when to display - * configuration warnings, if a modifier is used in cases where it can't - * be acted on. If the array would be empty, null may be returned. + * If a data source has no possible way of acting on a modifier, it should return those here. This is used to + * determine when to display configuration warnings, if a modifier is used in cases where it can't be acted on. If + * the array would be empty, null may be returned. * * @return */ - public DataSourceModifier[] invalidModifiers(); + public EnumSet invalidModifiers(); /** * Returns a list of modifiers attached to this data source instance. @@ -131,9 +118,10 @@ public interface DataSource extends SimpleDocumentation { * @return */ public Set getModifiers(); - + /** * Returns true if this data source has the specified modifier. + * * @param modifier The modifier to check * @return True if the modifier is present */ @@ -141,85 +129,93 @@ public interface DataSource extends SimpleDocumentation { /** * Returns true if the data source contains this key or not. + * * @param key - * @return + * @return */ public boolean hasKey(String[] key) throws DataSourceException; - + /** * Removes the key entirely from this data source. + * * @param key - * @throws DataSourceException + * @throws DataSourceException */ - public void clearKey(DaemonManager dm, String [] key) throws DataSourceException, ReadOnlyException, IOException; - + public void clearKey(DaemonManager dm, String[] key) throws DataSourceException, ReadOnlyException, IOException; + + /** + * Clears out all values from the data source. This may not be implemented for all data source types, in which + * case nothing happens. + * @param dm + * @throws DataSourceException + * @throws ReadOnlyException + * @throws IOException + */ + default void clearDatabase(DaemonManager dm) throws DataSourceException, ReadOnlyException, IOException { + + } + /** - * Starts a transaction for this data source. If the data source is not - * transient, this has no effect, but if it is, then all reads and writes - * will not be transient until the transaction is stopped. + * Starts a transaction for this data source. If the data source is not transient, this has no effect, but if it is, + * then all reads and writes will not be transient until the transaction is stopped. */ public void startTransaction(DaemonManager dm); - - /** - * When stopping the transaction, any pending writes will potentially - * occur then, so this can throw an exception at that point, - * much like set could, however, it will not throw ReadOnlyExceptions. - * If an exception is thrown during the middle of the transaction, it is up - * to the calling code to call stopTransaction(). If rollback is true, then - * any changes since the last startTransaction call will be rolled - * back. If any exceptions are thrown from this method, and rollback is - * true, then no changes will have been made to the data set. - * @param rollback If true, changes since the transaction started will - * be rolled back. + + /** + * When stopping the transaction, any pending writes will potentially occur then, so this can throw an exception at + * that point, much like set could, however, it will not throw ReadOnlyExceptions. If an exception is thrown during + * the middle of the transaction, it is up to the calling code to call stopTransaction(). If rollback is true, then + * any changes since the last startTransaction call will be rolled back. If any exceptions are thrown from this + * method, and rollback is true, then no changes will have been made to the data set. + * + * @param rollback If true, changes since the transaction started will be rolled back. * @throws IOException If any cached data couldn't be written out * @throws DataSourceException If any other exception occurs */ public void stopTransaction(DaemonManager dm, boolean rollback) throws DataSourceException, IOException; - + /** - * Disconnects the Data Source. This may not do anything for some connection types, but - * for streaming connection types, this will close the connection. Regardless, after - * calling this method, it should be assumed that future calls to this data source - * will be invalid, and might throw errors if used. - * @throws DataSourceException In the event that some kind of internal error occurs, this - * may be thrown. + * Disconnects the Data Source. This may not do anything for some connection types, but for streaming connection + * types, this will close the connection. Regardless, after calling this method, it should be assumed that future + * calls to this data source will be invalid, and might throw errors if used. + * + * @throws DataSourceException In the event that some kind of internal error occurs, this may be thrown. */ public void disconnect() throws DataSourceException; /** - * These are the valid modifiers for a generic connection. Not all data - * sources can support all of these, and some are inherently present or - * unsupportable on certain connection types. + * These are the valid modifiers for a generic connection. Not all data sources can support all of these, and some + * are inherently present or unsupportable on certain connection types. */ public enum DataSourceModifier implements SimpleDocumentation { - READONLY("Makes the connection read-only. That is to say, calls to store_data() on the keys mapped to this data source will always fail.", CHVersion.V3_3_1), + READONLY("Makes the connection read-only. That is to say, calls to store_data() on the keys mapped to this data source will always fail.", MSVersion.V3_3_1), TRANSIENT("The data from this source is not cached. Note that for file based data sources, this makes it incredibly inefficient for large data sources," - + " but makes it possible for multiple things to read and write to a source at the same time. If the connection is not read-only, a lock file will" - + " be created while the file is being written to (which will be the filename with .lock appended), which should be respected by other applications" - + " to prevent corruption. During read/write operations, if the lock file exists, the call to retrieve that data will block until the lock file" - + " goes away. File based connections that are NOT transient are loaded up at startup, and only writes require file system access from that point" - + " on. It is assumed that nothing else will be editing the data source, and so data is not re-read again, which means that leaving off the transient" - + " flag makes connections much more efficient. Database driven connections are always transient. ", CHVersion.V3_3_1), + + " but makes it possible for multiple things to read and write to a source at the same time. If the connection is not read-only, a lock file will" + + " be created while the file is being written to (which will be the filename with .lock appended), which should be respected by other applications" + + " to prevent corruption. During read/write operations, if the lock file exists, the call to retrieve that data will block until the lock file" + + " goes away. File based connections that are NOT transient are loaded up at startup, and only writes require file system access from that point" + + " on. It is assumed that nothing else will be editing the data source, and so data is not re-read again, which means that leaving off the transient" + + " flag makes connections much more efficient. Database driven connections are always transient. ", MSVersion.V3_3_1), HTTP("Makes the connection source be retrieved via http instead of assuming a local file. Connections via http are always read-only." - + " If the connection is also transient, a call to get_value() cannot be used in synchronous mode, and will fail if async" - + " mode is not used. ", CHVersion.V3_3_1), + + " If the connection is also transient, a call to get_value() cannot be used in synchronous mode, and will fail if async" + + " mode is not used. ", MSVersion.V3_3_1), HTTPS("Makes the connection source be retrieved via https instead of assuming a local file. Connections via http are always read-only." - + " If the connection is also transient, a call to get_value() cannot be used in synchronous mode, and will fail if async" - + " mode is not used. ", CHVersion.V3_3_1), + + " If the connection is also transient, a call to get_value() cannot be used in synchronous mode, and will fail if async" + + " mode is not used. ", MSVersion.V3_3_1), ASYNC("Forces retrievals to this connection to require asyncronous usage. This is handy if an otherwise blocking data source has gotten" - + " too large to allow synchonous connections, or if you are using a medium/large data source transiently.", CHVersion.V3_3_1), + + " too large to allow synchonous connections, or if you are using a medium/large data source transiently.", MSVersion.V3_3_1), PRETTYPRINT("For text based files, where it is applicable and possible, if there is a way to \"Pretty Print\" the data, do so. This usually comes" - + " at the cost of file size, but makes it easier to read in a text editor. For some data sources, this is not possible, due to the file" - + " layout requirements of the protocol itself.", CHVersion.V3_3_1), + + " at the cost of file size, but makes it easier to read in a text editor. For some data sources, this is not possible, due to the file" + + " layout requirements of the protocol itself.", MSVersion.V3_3_1), SSH("Retrieves the file via SSH. This cannot be used in combination with the HTTP or HTTPS flags. The file path must match the syntax used" - + " by SCP connections, for instance: ssh:yml://user@host:/path/to/file/over/ssh.yml. This will only work with public-key authentication" - + " however, since there is no practical way to input your password otherwise. Since this is a remote IO connection, async is implied if this" - + " modifier is set.", CHVersion.V3_3_1); - private CHVersion since; - private String documentation; + + " by SCP connections, for instance: ssh:yml://user@host:/path/to/file/over/ssh.yml. This will only work with public-key authentication" + + " however, since there is no practical way to input your password otherwise. Since this is a remote IO connection, async is implied if this" + + " modifier is set.", MSVersion.V3_3_1); + private final MSVersion since; + private final String documentation; - private DataSourceModifier(String documentation, CHVersion since) { + private DataSourceModifier(String documentation, MSVersion since) { this.documentation = documentation; this.since = since; } @@ -235,13 +231,13 @@ public String docs() { } @Override - public CHVersion since() { + public MSVersion since() { return since; } public static boolean isModifier(String scheme) { - for (DataSourceModifier modifier : DataSourceModifier.values()) { - if (modifier.getName().equalsIgnoreCase(scheme)) { + for(DataSourceModifier modifier : DataSourceModifier.values()) { + if(modifier.getName().equalsIgnoreCase(scheme)) { return true; } } diff --git a/src/main/java/com/laytonsmith/persistence/DataSourceException.java b/src/main/java/com/laytonsmith/persistence/DataSourceException.java index c33469c285..3c44d5b06e 100644 --- a/src/main/java/com/laytonsmith/persistence/DataSourceException.java +++ b/src/main/java/com/laytonsmith/persistence/DataSourceException.java @@ -2,33 +2,32 @@ /** * - * + * */ public class DataSourceException extends Exception { - /** - * Constructs an instance of - * DataSourceException with the specified detail message. - * - * @param msg the detail message. - */ - public DataSourceException(String msg){ - super(msg); - } - - public DataSourceException(String msg, Throwable reason) { - super(msg, reason); - } - - public DataSourceException(Throwable reason){ + /** + * Constructs an instance of DataSourceException with the specified detail message. + * + * @param msg the detail message. + */ + public DataSourceException(String msg) { + super(msg); + } + + public DataSourceException(String msg, Throwable reason) { + super(msg, reason); + } + + public DataSourceException(Throwable reason) { super(reason); } - - public Throwable getRootCause(){ - Throwable t = this.getCause(); - while(t != null){ - t = t.getCause(); - } - return t; - } + + public Throwable getRootCause() { + Throwable t = this.getCause(); + while(t != null) { + t = t.getCause(); + } + return t; + } } diff --git a/src/main/java/com/laytonsmith/persistence/DataSourceFactory.java b/src/main/java/com/laytonsmith/persistence/DataSourceFactory.java index a8ef522681..d3636e2c48 100644 --- a/src/main/java/com/laytonsmith/persistence/DataSourceFactory.java +++ b/src/main/java/com/laytonsmith/persistence/DataSourceFactory.java @@ -2,7 +2,7 @@ import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery; import com.laytonsmith.annotations.datasource; -import com.laytonsmith.core.CHLog; +import com.laytonsmith.core.MSLog; import com.laytonsmith.core.LogLevel; import com.laytonsmith.core.constructs.Target; import com.laytonsmith.persistence.io.ConnectionMixinFactory; @@ -19,65 +19,47 @@ /** * This utility class provides the means to interact with given data sources. * - * + * */ public class DataSourceFactory { - - private static Map dataSourcePool = new HashMap<>(); - /** - * Given a connection uri and the connection options, creates and returns a - * new DataSource object, which can be used to do direct operations on the - * data source. Generally, you should go through the PersistenceNetwork - * class to perform operations on the network as a whole, however. - * - * @param uri The full connection uri - * @param options The connection mixin options - * @return A new DataSource object - * @throws DataSourceException If there is a problem connecting to the data - * source - * @throws URISyntaxException If the URI is invalid - */ - public static DataSource GetDataSource(String uri, ConnectionMixinFactory.ConnectionMixinOptions options) throws DataSourceException, URISyntaxException { - return GetDataSource(new URI(uri), options); - } - - /** - * Internally, DataSourceFactory re-uses connections, for efficiency reasons. When - * the server is shutdown, a clean shutdown of all the cached connections is - * desired. This method will disconnect all persistently connecting connections, - * as well as delete them from the cache. - */ - public static void DisconnectAll(){ - for(DataSource ds : dataSourcePool.values()){ - try { - ds.disconnect(); - } catch (DataSourceException ex) { - CHLog.GetLogger().Log(CHLog.Tags.PERSISTENCE, LogLevel.WARNING, ex.getMessage(), Target.UNKNOWN); + private static final Map DATA_SOURCE_POOL = new HashMap<>(); + private static Map protocolHandlers; + + private static void init() { + if(protocolHandlers == null) { + protocolHandlers = new HashMap<>(); + Set> classes = ClassDiscovery.getDefaultInstance().loadClassesWithAnnotation(datasource.class); + for(Class c : classes) { + if(DataSource.class.isAssignableFrom(c)) { + protocolHandlers.put((c.getAnnotation(datasource.class)).value(), c); + } else { + throw new Error(c.getName() + " does not implement DataSource!"); + } } } - dataSourcePool.clear(); } /** - * Given a connection uri and the connection options, creates and returns a - * new DataSource object, which can be used to do direct operations on the - * data source. Generally, you should go through the PersistenceNetwork - * class to perform operations on the network as a whole, however. + * Given a connection uri and the connection options, creates and returns a new DataSource object, which can be used + * to do direct operations on the data source. Generally, you should go through the PersistenceNetwork class to + * perform operations on the network as a whole, however. * * @param uri The full connection uri * @param options The connection mixin options * @return A new DataSource object - * @throws DataSourceException If there is a problem connecting to the data - * source + * @throws DataSourceException If there is a problem connecting to the data source */ - public static DataSource GetDataSource(URI uri, ConnectionMixinFactory.ConnectionMixinOptions options) throws DataSourceException { + public static DataSource GetDataSource(URI uri, ConnectionMixinFactory.ConnectionMixinOptions options) + throws DataSourceException { init(); - if(dataSourcePool.containsKey(uri)){ - return dataSourcePool.get(uri); + URI uriKey = uri; + DataSource source = DATA_SOURCE_POOL.get(uriKey); + if(source != null) { + return source; } - List modifiers = new ArrayList(); - while (DataSource.DataSourceModifier.isModifier(uri.getScheme())) { + List modifiers = new ArrayList<>(); + while(DataSource.DataSourceModifier.isModifier(uri.getScheme())) { modifiers.add(DataSource.DataSourceModifier.getModifier(uri.getScheme())); try { uri = new URI(uri.getSchemeSpecificPart()); @@ -86,52 +68,56 @@ public static DataSource GetDataSource(URI uri, ConnectionMixinFactory.Connectio } } Class c = protocolHandlers.get(uri.getScheme()); - if (c == null) { + if(c == null) { throw new DataSourceException("Invalid scheme: " + uri.getScheme()); } try { - DataSource ds = (DataSource) c.getConstructor(URI.class, ConnectionMixinFactory.ConnectionMixinOptions.class).newInstance(uri, options); - for (DataSource.DataSourceModifier m : modifiers) { + DataSource ds = (DataSource) c.getConstructor(URI.class, + ConnectionMixinFactory.ConnectionMixinOptions.class).newInstance(uri, options); + for(DataSource.DataSourceModifier m : modifiers) { ds.addModifier(m); } try { - if (ds instanceof AbstractDataSource) { + if(ds instanceof AbstractDataSource) { ((AbstractDataSource) ds).checkModifiers(); } } catch (DataSourceException e) { //Warning, for invalid modifiers. This isn't an error, invalid modifiers will just be //ignored, but the user probably meant something else if they're getting this warning, //so we still alert them to the issue. - CHLog.GetLogger().Log(CHLog.Tags.PERSISTENCE, LogLevel.WARNING, e.getMessage(), Target.UNKNOWN); + MSLog.GetLogger().Log(MSLog.Tags.PERSISTENCE, LogLevel.WARNING, e.getMessage(), Target.UNKNOWN); } //If the data source is transient, it will populate itself later, as needed. //Otherwise, we can go ahead and populate it now. - if (!ds.getModifiers().contains(DataSource.DataSourceModifier.TRANSIENT)) { + if(!ds.getModifiers().contains(DataSource.DataSourceModifier.TRANSIENT)) { ds.populate(); } - dataSourcePool.put(uri, ds); + DATA_SOURCE_POOL.put(uriKey, ds); return ds; - } catch (InvocationTargetException | NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | DataSourceException ex) { - if(ex instanceof InvocationTargetException && ex.getCause() instanceof DataSourceException){ - throw (DataSourceException)ex.getCause(); + } catch (InvocationTargetException | NoSuchMethodException | SecurityException | InstantiationException + | IllegalAccessException | IllegalArgumentException | DataSourceException ex) { + if(ex instanceof InvocationTargetException && ex.getCause() instanceof DataSourceException) { + throw (DataSourceException) ex.getCause(); } - throw new DataSourceException("Could not instantiate a DataSource for " + c.getName() + ": " + ex.getMessage(), ex); + throw new DataSourceException("Could not instantiate a DataSource for " + c.getName() + ": " + + ex.getMessage(), ex); } } - private static Map protocolHandlers; - private static void init() { - if (protocolHandlers == null) { - protocolHandlers = new HashMap(); - Set classes = ClassDiscovery.getDefaultInstance().loadClassesWithAnnotation(datasource.class); - for (Class c : classes) { - if (DataSource.class.isAssignableFrom(c)) { - protocolHandlers.put(((datasource) c.getAnnotation(datasource.class)).value(), c); - } else { - throw new Error(c.getName() + " does not implement DataSource!"); - } - } - } + /** + * Given a connection uri and the connection options, creates and returns a new DataSource object, which can be used + * to do direct operations on the data source. Generally, you should go through the PersistenceNetwork class to + * perform operations on the network as a whole, however. + * + * @param uri The full connection uri + * @param options The connection mixin options + * @return A new DataSource object + * @throws DataSourceException If there is a problem connecting to the data source + * @throws URISyntaxException If the URI is invalid + */ + public static DataSource GetDataSource(String uri, ConnectionMixinFactory.ConnectionMixinOptions options) + throws DataSourceException, URISyntaxException { + return GetDataSource(new URI(uri), options); } /** @@ -141,6 +127,22 @@ private static void init() { */ public static Set GetSupportedProtocols() { init(); - return new HashSet(protocolHandlers.keySet()); + return new HashSet<>(protocolHandlers.keySet()); + } + + /** + * Internally, DataSourceFactory re-uses connections, for efficiency reasons. When the server is shutdown, a clean + * shutdown of all the cached connections is desired. This method will disconnect all persistently connecting + * connections, as well as delete them from the cache. + */ + public static void DisconnectAll() { + for(DataSource ds : DATA_SOURCE_POOL.values()) { + try { + ds.disconnect(); + } catch (DataSourceException ex) { + MSLog.GetLogger().Log(MSLog.Tags.PERSISTENCE, LogLevel.WARNING, ex.getMessage(), Target.UNKNOWN); + } + } + DATA_SOURCE_POOL.clear(); } } diff --git a/src/main/java/com/laytonsmith/persistence/DataSourceFilter.java b/src/main/java/com/laytonsmith/persistence/DataSourceFilter.java index 60f969f118..eae21fa063 100644 --- a/src/main/java/com/laytonsmith/persistence/DataSourceFilter.java +++ b/src/main/java/com/laytonsmith/persistence/DataSourceFilter.java @@ -20,63 +20,56 @@ /** * Given a File, creates a data source filter, which can be * - * + * */ public class DataSourceFilter { /** - * Maps the regex-converted filter to a URI string. This is not stored - * as a URI, because it might have capture usages in it. + * Maps the regex-converted filter to a URI string. This is not stored as a URI, because it might have capture + * usages in it. */ - private Map mappings = new HashMap(); + private Map mappings = new HashMap<>(); /** - * This maps the compiled pattern to the original in-config filter. - * So, hi\..*? would map to hi.** + * This maps the compiled pattern to the original in-config filter. So, hi\..*? would map to hi.** */ - private Map original = new HashMap(); + private Map original = new HashMap<>(); /** * This maps the split key to the URI string, for use in namespace comparisons. */ - private Map namespaced = new HashMap(); + private Map namespaced = new HashMap<>(); /** * Since data lookups are expensive, cache them. */ - private Map cache = new TreeMap(); + private Map cache = new TreeMap<>(); /** * Namespace lookups are also expensive, so let's also cache the results. */ - private Map> namespaceCache = new TreeMap>(); + private Map> namespaceCache = new TreeMap<>(); /** - * Creates a new data source filter. This is represented by a file that - * contains mappings to the filters. + * Creates a new data source filter. This is represented by a file that contains mappings to the filters. * * @param file The file that contains the filter network - * @param defaultURI The URI to be used in the case that no ** filter is - * found in the file + * @param defaultURI The URI to be used in the case that no ** filter is found in the file * @throws FileNotFoundException If the file cannot be found - * @throws DataSourceException If any condition causes the data sources - * to be unreadable + * @throws DataSourceException If any condition causes the data sources to be unreadable * @throws NullPointerException If the defaultURI is null */ public DataSourceFilter(File file, URI defaultURI) throws IOException, DataSourceException { try { process(FileUtil.read(file), defaultURI); } catch (DataSourceException e) { - throw new DataSourceException("Could not process filter file located at " + file.getAbsolutePath() + ": " + e.getMessage()); + throw new DataSourceException("Could not process filter file located at " + file.getAbsolutePath() + ": " + + e.getMessage()); } } /** - * Creates a new data source filter. This is represented by a string - * that contains mappings to the filters. + * Creates a new data source filter. This is represented by a string that contains mappings to the filters. * - * @param filters The filter configuration that contains the filter - * network - * @param defaultURI The URI to be used in the case that no ** filter is - * found in the file - * @throws DataSourceException If any condition causes the data sources - * to be unreadable + * @param filters The filter configuration that contains the filter network + * @param defaultURI The URI to be used in the case that no ** filter is found in the file + * @throws DataSourceException If any condition causes the data sources to be unreadable * @throws NullPointerException If the defaultURI is null */ public DataSourceFilter(String filters, URI defaultURI) throws DataSourceException { @@ -84,128 +77,189 @@ public DataSourceFilter(String filters, URI defaultURI) throws DataSourceExcepti } private void process(String filters, URI defaultURI) throws DataSourceException { - if (defaultURI == null) { + if(defaultURI == null) { throw new NullPointerException("defaultURI cannot be null"); } PropertiesManager p = new PropertiesManager(filters); - //We now have the whole list of filters=connection. We need to parse out and - //find the connection aliases and normal filters, then parse those individually. - //First, find the aliases, so we can go ahead and distribute those out when - //we get to them. - Map aliases = new HashMap(); + // We now have the whole list of filters=connection. We need to parse out and + // find the connection aliases and normal filters, then parse those individually. + // First, find the aliases, so we can go ahead and distribute those out when + // we get to them. + Map aliases = new HashMap<>(); boolean hasDefault = false; - for (String key : p.keySet()) { + for(String key : p.keySet()) { key = key.trim(); - if (key.matches("\\$[^a-zA-Z_]+")) { - //Bad alias, bail + if(key.matches("\\$[^a-zA-Z_]+")) { + // Bad alias, bail throw new DataSourceException("Aliases in your filters may not start with a digit."); } - if (key.matches("\\$[a-zA-Z_][a-zA-Z0-9_]*")) { - //It's an alias - if(aliases.containsKey(key)){ + if(key.matches("\\$[a-zA-Z_][a-zA-Z0-9_]*")) { + // It's an alias + if(aliases.containsKey(key)) { throw new DataSourceException("Duplicate aliases defined: " + key); } aliases.put(key, p.get(key)); } - if(key.equals("**")){ + if(key.equals("**")) { hasDefault = true; } } - - //Ok, now let's load up the actual connections. - for (String key : p.keySet()) { - if (!key.matches("\\$.*")) { - if (key.matches("[^a-zA-Z0-9_\\*]")) { - //Bad character in the filter. Bail. + // Ok, now let's load up the actual connections. + for(String key : p.keySet()) { + if(!key.matches("\\$.*")) { + if(key.matches("[^a-zA-Z0-9_\\*]")) { + // Bad character in the filter. Bail. throw new DataSourceException("Invalid character in filter. Only" - + " the following characters are allowed: a-zA-Z0-9_()*" - + " Found this instead: " + key); + + " the following characters are allowed: a-zA-Z0-9_()*" + + " Found this instead: " + key); } - + String regexKey = toRegex(key); - - Pattern pattern = Pattern.compile(regexKey + "$"); - //Ok, have the pattern, now lets see if the value is an alias + Pattern pattern = Pattern.compile(regexKey + "$"); + // Ok, have the pattern, now lets see if the value is an alias String value = p.get(key); String originalValue = value; - //Used for more meaningful error messages below + // Used for more meaningful error messages below boolean isAlias = false; - if (value.matches("\\$.*")) { - if (!aliases.containsKey(value)) { + if(value.matches("\\$.*")) { + if(!aliases.containsKey(value)) { throw new DataSourceException("Invalid alias: " + value + " is trying to be" - + " used, but has not been defined."); + + " used, but has not been defined."); } else { value = aliases.get(value); isAlias = true; } } - //Is this pattern already in the mapping? If so, we need to throw an error. - if (mappings.containsKey(pattern)) { + // Is this pattern already in the mapping? If so, we need to throw an error. + if(mappings.containsKey(pattern)) { throw new DataSourceException("Multiple definitions exist for the key: " + key); } - //Ok, finally, one last validation, we want to make sure that the URI at least - //looks valid enough. Dollar signs aren't valid though, so lets replace our otherwise - //valid capture usages. + // Ok, finally, one last validation, we want to make sure that the URI at least + // looks valid enough. Dollar signs aren't valid though, so lets replace our otherwise + // valid capture usages. URI uriValue; try { uriValue = new URI(value); } catch (URISyntaxException e) { throw new DataSourceException("Invalid URI for " + value - + (isAlias ? "(Defined for alias " + originalValue + ")" : "") + "."); + + (isAlias ? "(Defined for alias " + originalValue + ")" : "") + "."); } - //Alright. It's cool. Add it to the list. + // Alright. It's cool. Add it to the list. mappings.put(pattern, value); original.put(pattern, key); namespaced.put(key.split("\\."), value); } - //else it's an alias, and we've already dealt with it + // else it's an alias, and we've already dealt with it } - if(!hasDefault){ + if(!hasDefault) { Pattern m = Pattern.compile(".*?"); mappings.put(m, defaultURI.toString()); original.put(m, "**"); namespaced.put(new String[]{"**"}, defaultURI.toString()); } } - + /** - * Given a key filter, returns a regex pattern that is suitable for - * matching against actual keys. + * Given a key filter, returns a regex pattern that is suitable for matching against actual keys. + * * @param key - * @return + * @return */ - public static String toRegex(String key){ - //We need to change * into [^\.]*? and ** into .*? and . into \. - //Parenthesis are kept as is. - String newKey = key.replaceAll("\\.", "\\\\."); + public static String toRegex(String key) { + // We need to change * into [^\.]*? and ** into .*? and . into \. + // Parenthesis are kept as is. + String newKey = key.replace(".", "\\."); StringBuilder b = new StringBuilder("^"); - for (int i = 0; i < newKey.length(); i++) { + for(int i = 0; i < newKey.length(); i++) { Character c1 = newKey.charAt(i); Character c2 = null; - if (i + 1 < newKey.length()) { + if(i + 1 < newKey.length()) { c2 = newKey.charAt(i + 1); } - if (c1 == '*' && c2 != null && c2 == '*') { - //Double star + if(c1 == '*' && c2 != null && c2 == '*') { + // Double star b.append(".*?"); i++; - } else if (c1 == '*') { - //Single star + } else if(c1 == '*') { + // Single star b.append("[^\\.]*?"); } else { - //Some other character + // Some other character b.append(c1); } } return b.toString(); } + /** + * Given a full key, returns the connection that contains it. + * + * @param key + * @return + */ + public URI getConnection(String key) { + // Since looking through these patterns, doing the matches, calculating string distance are all + // fairly expensive operations, let's improve the runtime complexity by using a cache + // TODO: We shouldn't have an unlimited size cache though, this should be just the most frequently used + // values, and the cache should discard rarely used values. + URI cached = cache.get(key); + if(cached != null) { + return cached; + } + List matches = new ArrayList<>(); + for(Pattern p : mappings.keySet()) { + if(p.matcher(key).matches()) { + matches.add(p); + } + } + // Ok, we have a list of the actual matches, we have to narrow it down to the closest + // match. + Pattern closest = null; + if(matches.isEmpty()) { + //This shouldn't happen unless there's an errant new line character in the key + if(key.contains("\\n")) { + throw new IllegalArgumentException("Invalid new line character found in persistence key here:" + + key.substring(0, key.indexOf("\\n")) + " <---"); + } + throw new RuntimeException("Persistence mappings missing match for key: " + key); + } else if(matches.size() == 1) { + // Yay! Also a trivial case! + closest = matches.get(0); + } else { + int lowest = Integer.MAX_VALUE; + for(Pattern p : matches) { + // The closest match is defined as a filter that, minus wild cards, matches more characters. + // So, for instance, if the key is a.b.c.d, then this matches a.*.c.d better than a.*.*.d + // The easiest way to detect this is to simply remove * characters, and do a Levenshtein distance on the + // strings, and whichever one is lowest, is the closest. + String originalKey = original.get(p); + int dist = StringUtils.LevenshteinDistance(key, originalKey.replaceAll("\\*", "") + .replaceAll("[\\(\\)]", "")); + if(dist < lowest) { + closest = p; + lowest = dist; + } + } + } + + try { + String uri = mappings.get(closest); + URI u = new URI(uri); + // Store it in our cache + cache.put(key, u); + return u; + } catch (URISyntaxException ex) { + // We already verified that this won't happen, so yeah. + return null; + } + } + /** * Given a full key, returns the connection that contains it. * @@ -215,18 +269,19 @@ public static String toRegex(String key){ public URI getConnection(String[] key) { return getConnection(StringUtils.Join(key, ".")); } - + /** * Returns a set of ALL connections. - * @return + * + * @return */ - public Set getAllConnections(){ + public Set getAllConnections() { Set set = new HashSet<>(); - for(String uri : namespaced.values()){ + for(String uri : namespaced.values()) { try { set.add(new URI(uri)); - } catch (URISyntaxException ex){ - //Won't happen + } catch (URISyntaxException ex) { + // Won't happen throw new Error(ex); } } @@ -244,131 +299,76 @@ public Set getAllConnections(String[] key) { } /** - * Returns all the connections that actually match this namespace part. - * This is typically used to get a subset of keys based on namespace. + * Returns all the connections that actually match this namespace part. This is typically used to get a subset of + * keys based on namespace. * * @param key * @return */ + @SuppressWarnings("UnnecessaryLabelOnBreakStatement") public Set getAllConnections(String key) { - if(namespaceCache.containsKey(key)){ + if(namespaceCache.containsKey(key)) { return new HashSet<>(namespaceCache.get(key)); } - Map matches = new HashMap(); - String [] split = key.split("\\."); - outer: for(String [] comparison : namespaced.keySet()){ - inner: for(int comparing = 0; comparing < split.length; comparing++){ - //If the length of the key is greater than this comparison, it's not a match, unless - //the key had a ** in it at some point before this index - if(arrayContains(comparison, "**", 0, comparing)){ - //Yes, it matches, regardless. + Map matches = new HashMap<>(); + String[] split = key.split("\\."); + outer: + for(String[] comparison : namespaced.keySet()) { + inner: + for(int comparing = 0; comparing < split.length; comparing++) { + // If the length of the key is greater than this comparison, it's not a match, unless + // the key had a ** in it at some point before this index + if(arrayContains(comparison, "**", 0, comparing)) { + // Yes, it matches, regardless. matches.put(comparison, namespaced.get(comparison)); break inner; - } else if(comparison.length > comparing){ - //Ok, so we know that it has the correct number of parts. + } else if(comparison.length > comparing) { + // Ok, so we know that it has the correct number of parts. String requestedPart = split[comparing]; String myPart = comparison[comparing]; - if(myPart.contains("*")){ - //It's got a wildcard, so we need to convert it to a regex and compare from there + if(myPart.contains("*")) { + // It's got a wildcard, so we need to convert it to a regex and compare from there String regexPart = toRegex(myPart); - if(!requestedPart.matches(regexPart)){ + if(!requestedPart.matches(regexPart)) { continue outer; } } else { - //Else it's a simple a string match - if(!requestedPart.equals(myPart)){ + // Else it's a simple a string match + if(!requestedPart.equals(myPart)) { continue outer; } } - //It matched this part, so let's continue with the investigation. - //If this was the last part we need to compare, it's good, we can add it to the list now. - if(comparing == split.length - 1){ + // It matched this part, so let's continue with the investigation. + // If this was the last part we need to compare, it's good, we can add it to the list now. + if(comparing == split.length - 1) { matches.put(comparison, namespaced.get(comparison)); break inner; } } } - } - + } + Set list = new HashSet<>(); - for(String [] match : matches.keySet()){ + for(String[] match : matches.keySet()) { String uri = matches.get(match); try { list.add(new URI(uri)); } catch (URISyntaxException ex) { - //Won't happen + // Won't happen throw new Error(ex); } } namespaceCache.put(key, list); return new HashSet<>(list); } - - private boolean arrayContains(String[] array, String contains, int from, int to){ - for(int i = from; i < (to + 1<=array.length?to + 1:array.length); i++){ + + private boolean arrayContains(String[] array, String contains, int from, int to) { + for(int i = from; i < (to + 1 <= array.length ? to + 1 : array.length); i++) { String part = array[i]; - if(part.contains(contains)){ + if(part.contains(contains)) { return true; } } return false; } - - /** - * Given a full key, returns the connection that contains it. - * - * @param key - * @return - */ - public URI getConnection(String key) { - //Since looking through these patterns, doing the matches, calculating string distance are all - //fairly expensive operations, let's improve the runtime complexity by using a cache - if (cache.containsKey(key)) { - return cache.get(key); - } - List matches = new ArrayList(); - for (Pattern p : mappings.keySet()) { - if (p.matcher(key).matches()) { - matches.add(p); - } - } - //Ok, we have a list of the actual matches, we have to narrow it down to the closest - //match. - Pattern closest = null; - if (matches.isEmpty()) { - //Trivial case - return null; - } else if (matches.size() == 1) { - //Yay! Also a trivial case! - closest = matches.get(0); - } else { - int lowest = Integer.MAX_VALUE; - for (Pattern p : matches) { - //The closest match is defined as a filter that, minus wild cards, matches more characters. - //So, for instance, if the key is a.b.c.d, then this matches a.*.c.d better than a.*.*.d - //The easiest way to detect this is to simply remove * characters, and do a Levenshtein distance on the strings, and - //whichever one is lowest, is the closest. - String originalKey = original.get(p); - int dist = StringUtils.LevenshteinDistance(key, originalKey.replaceAll("\\*", "").replaceAll("[\\(\\)]", "")); - if (dist < lowest) { - closest = p; - lowest = dist; - } - } - } - - try { - if (closest == null) { - return null; - } - String uri = mappings.get(closest); - URI u = new URI(uri); - //Store it in our cache - cache.put(key, u); - return u; - } catch (URISyntaxException ex) { - //We already verified that this won't happen, so yeah. - return null; - } - } } diff --git a/src/main/java/com/laytonsmith/persistence/DataSourceModel.java b/src/main/java/com/laytonsmith/persistence/DataSourceModel.java index 89a818bb37..cdc51c68b0 100644 --- a/src/main/java/com/laytonsmith/persistence/DataSourceModel.java +++ b/src/main/java/com/laytonsmith/persistence/DataSourceModel.java @@ -11,15 +11,12 @@ import java.util.Set; /** - * This class represents a data source model. The underlying model is just a map - * of values or maps, but this class abstracts accessing and storing the data. - * If a value in a model can be more efficiently lazily loaded, this class - * perhaps should not be used, but for non event driven models, it should - * suffice for many cases. If a data source inherently has a better way to store - * the data, then it may choose to not use this class. Note: Not all data - * sources can store a key in both namespace.value and namespace.value.other, in - * that case, to make namespace.value's actual value, it should be stored as - * namespace.value._ + * This class represents a data source model. The underlying model is just a map of values or maps, but this class + * abstracts accessing and storing the data. If a value in a model can be more efficiently lazily loaded, this class + * perhaps should not be used, but for non event driven models, it should suffice for many cases. If a data source + * inherently has a better way to store the data, then it may choose to not use this class. Note: Not all data sources + * can store a key in both namespace.value and namespace.value.other, in that case, to make namespace.value's actual + * value, it should be stored as namespace.value._ * */ public final class DataSourceModel { @@ -28,7 +25,7 @@ public final class DataSourceModel { public DataSourceModel(Map model) { //We have to do a depth first traversal here to get all the keys - if (model != null) { + if(model != null) { build(model, tree); } } @@ -39,35 +36,35 @@ public DataSourceModel(Map model) { * @param list */ public DataSourceModel(List> list) { - for (Pair pair : list) { + for(Pair pair : list) { String[] key = pair.getKey().split("\\."); set(key, pair.getValue()); } } private void build(Object node, GenericTreeNode> treeNode) { - if (node instanceof Map) { + if(node instanceof Map) { //We need to iterate through all the keys, creating children as we go - for (String key : ((Map) node).keySet()) { - if (key.equals("_")) { + for(String key : ((Map) node).keySet()) { + if(key.equals("_")) { //Special case, this is a reserved key build(((Map) node).get(key), treeNode); } else { - GenericTreeNode> newNode = - new GenericTreeNode<>(new Pair(key, null)); + GenericTreeNode> newNode + = new GenericTreeNode<>(new Pair(key, null)); treeNode.addChild(newNode); build(((Map) node).get(key), newNode); } } } else { //This is the node we want to put the data in - treeNode.data.setValue(node==null?null:node.toString()); + treeNode.data.setValue(node == null ? null : node.toString()); } } public Map toMap() { Map map = new HashMap<>(); - for (GenericTreeNode> child : tree.getChildren()) { + for(GenericTreeNode> child : tree.getChildren()) { decompose(map, child); } return map; @@ -80,15 +77,15 @@ public List> toList() { } private void decompose(Map node, GenericTreeNode> treeNode) { - if (treeNode.hasChildren()) { - //If it's not a leaf node, we need to add a new child to the map. + if(treeNode.hasChildren()) { + //If it's not a leaf node, we need to add a new child to the map. //However, if the data isn't null, we need to add the data now as a _ key Map map = new HashMap<>(); - if (treeNode.getData().getValue() != null) { + if(treeNode.getData().getValue() != null) { map.put("_", treeNode.getData().getValue()); } node.put(treeNode.getData().getKey(), map); - for (GenericTreeNode> child : treeNode.getChildren()) { + for(GenericTreeNode> child : treeNode.getChildren()) { decompose(map, child); } } else { @@ -111,11 +108,11 @@ public void clearKey(String[] key) { private String getValue(List keys, GenericTreeNode> treeNode) { String value = null; - if (!keys.isEmpty()) { + if(!keys.isEmpty()) { String key = keys.get(0); keys.remove(0); - for (GenericTreeNode> child : treeNode.getChildren()) { - if (child.getData().getKey().equals(key)) { + for(GenericTreeNode> child : treeNode.getChildren()) { + if(child.getData().getKey().equals(key)) { return getValue(keys, child); } } @@ -126,29 +123,29 @@ private String getValue(List keys, GenericTreeNode> } private void setValue(List keys, GenericTreeNode> treeNode, String value) { - if (keys.isEmpty()) { + if(keys.isEmpty()) { treeNode.getData().setValue(value); } else { GenericTreeNode> found = null; String key = keys.get(0); keys.remove(0); - for (GenericTreeNode> child : treeNode.getChildren()) { - if (child.getData().getKey().equals(key)) { + for(GenericTreeNode> child : treeNode.getChildren()) { + if(child.getData().getKey().equals(key)) { found = child; break; } } - if (found == null) { + if(found == null) { found = new GenericTreeNode<>(new Pair(key, null)); treeNode.addChild(found); } - if (value == null) { - if (keys.isEmpty()) { + if(value == null) { + if(keys.isEmpty()) { //We need to remove this node. //TODO: This fails to remove the parent - for (int i = 0; i < treeNode.getNumberOfChildren(); i++) { + for(int i = 0; i < treeNode.getNumberOfChildren(); i++) { GenericTreeNode> node = treeNode.getChildAt(i); - if (node.getData().getKey().equals(key)) { + if(node.getData().getKey().equals(key)) { treeNode.removeChildAt(i); break; } @@ -162,20 +159,20 @@ private void setValue(List keys, GenericTreeNode> t public Set keySet() { Set keys = new HashSet<>(); - for (GenericTreeNode child : tree.getChildren()) { + for(GenericTreeNode child : tree.getChildren()) { traverse(child, new ArrayList(), keys); } return keys; } private void traverse(GenericTreeNode> treeNode, List ongoingKey, Set keys) { - if (treeNode.hasChildren()) { + if(treeNode.hasChildren()) { ongoingKey.add(treeNode.getData().getKey()); - if (treeNode.getData().getValue() != null) { + if(treeNode.getData().getValue() != null) { //Data and children keys.add(ongoingKey.toArray(new String[ongoingKey.size()])); } - for (GenericTreeNode> child : treeNode.getChildren()) { + for(GenericTreeNode> child : treeNode.getChildren()) { //recurse down now traverse(child, ongoingKey, keys); } diff --git a/src/main/java/com/laytonsmith/persistence/INIDataSource.java b/src/main/java/com/laytonsmith/persistence/INIDataSource.java index 5002acf932..6ff6f6ae5b 100644 --- a/src/main/java/com/laytonsmith/persistence/INIDataSource.java +++ b/src/main/java/com/laytonsmith/persistence/INIDataSource.java @@ -3,76 +3,77 @@ import com.laytonsmith.PureUtilities.Common.StringUtils; import com.laytonsmith.PureUtilities.Pair; import com.laytonsmith.annotations.datasource; -import com.laytonsmith.core.CHVersion; +import com.laytonsmith.core.MSVersion; import com.laytonsmith.persistence.io.ConnectionMixinFactory; import java.io.IOException; import java.io.StringReader; import java.net.URI; import java.util.ArrayList; +import java.util.EnumSet; import java.util.List; import java.util.Properties; /** - * + * */ @datasource("ini") public class INIDataSource extends StringSerializableDataSource { - + private INIDataSource() { - + } - public INIDataSource(URI uri, ConnectionMixinFactory.ConnectionMixinOptions options) throws DataSourceException{ - super(uri, options); - } - - @Override - protected void populateModel(String data) throws DataSourceException { - Properties props = new Properties(); - try { - props.load(new StringReader(data)); - } catch (IOException ex) { - //Won't ever happen, but sure. - throw new DataSourceException(null, ex); - } - List> list = new ArrayList>(); - for(String key : props.stringPropertyNames()){ - list.add(new Pair(key, props.getProperty(key))); - } - model = new DataSourceModel(list); - } + public INIDataSource(URI uri, ConnectionMixinFactory.ConnectionMixinOptions options) throws DataSourceException { + super(uri, options); + } - @Override - protected String serializeModel() { - StringBuilder b = new StringBuilder(); - for(String [] key : model.keySet()){ - b.append(StringUtils.Join(key, ".")).append("=").append(model.get(key)).append("\n"); - } - return b.toString(); - } + @Override + protected void populateModel(String data) throws DataSourceException { + Properties props = new Properties(); + try { + props.load(new StringReader(data)); + } catch (IOException ex) { + //Won't ever happen, but sure. + throw new DataSourceException(null, ex); + } + List> list = new ArrayList>(); + for(String key : props.stringPropertyNames()) { + list.add(new Pair(key, props.getProperty(key))); + } + model = new DataSourceModel(list); + } @Override - public DataSourceModifier[] implicitModifiers() { - return null; - } + protected String serializeModel() { + StringBuilder b = new StringBuilder(); + for(String[] key : model.keySet()) { + b.append(StringUtils.Join(key, ".")).append("=").append(model.get(key)).append("\n"); + } + return b.toString(); + } @Override - public DataSourceModifier[] invalidModifiers() { - return new DataSourceModifier[]{DataSourceModifier.PRETTYPRINT}; - } + public EnumSet implicitModifiers() { + return null; + } @Override - public String docs() { - return "INI {ini:///path/to/ini/file.ini} This type stores data in plain" - + " text, in a ini style. All the pros and cons of yml apply here," - + " but instead of using the yml style to store the data, values" - + " are stored with key=value\\n signatures. Pretty print is not supported," - + " since whitespace is relevant to the meta information."; - } + public EnumSet invalidModifiers() { + return EnumSet.of(DataSourceModifier.PRETTYPRINT); + } + + @Override + public String docs() { + return "INI {ini:///path/to/ini/file.ini} This type stores data in plain" + + " text, in a ini style. All the pros and cons of yml apply here," + + " but instead of using the yml style to store the data, values" + + " are stored with key=value\\n signatures. Pretty print is not supported," + + " since whitespace is relevant to the meta information."; + } @Override - public CHVersion since() { - return CHVersion.V3_3_1; - } - + public MSVersion since() { + return MSVersion.V3_3_1; + } + } diff --git a/src/main/java/com/laytonsmith/persistence/JSONDataSource.java b/src/main/java/com/laytonsmith/persistence/JSONDataSource.java index 38c731c0a8..726481eb6a 100644 --- a/src/main/java/com/laytonsmith/persistence/JSONDataSource.java +++ b/src/main/java/com/laytonsmith/persistence/JSONDataSource.java @@ -1,11 +1,12 @@ package com.laytonsmith.persistence; import com.laytonsmith.annotations.datasource; -import com.laytonsmith.core.CHVersion; +import com.laytonsmith.core.MSVersion; import com.laytonsmith.persistence.io.ConnectionMixinFactory; import java.io.IOException; import java.io.StringWriter; import java.net.URI; +import java.util.EnumSet; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; @@ -13,13 +14,13 @@ /** * - * + * */ @datasource("json") public class JSONDataSource extends StringSerializableDataSource { - + private JSONDataSource() { - + } public JSONDataSource(URI uri, ConnectionMixinFactory.ConnectionMixinOptions options) throws DataSourceException { @@ -49,27 +50,27 @@ protected String serializeModel() { } @Override - public DataSourceModifier[] implicitModifiers() { + public EnumSet implicitModifiers() { return null; } @Override - public DataSourceModifier[] invalidModifiers() { - return new DataSourceModifier[]{DataSourceModifier.PRETTYPRINT}; + public EnumSet invalidModifiers() { + return EnumSet.of(DataSourceModifier.PRETTYPRINT); } @Override public String docs() { return "JSON {json:///path/to/file.json} This type stores data in JSON" - + " format. All the pros and cons of yml apply here, but instead" - + " of using the yml style to store the data, values are stored" - + " in a JSON medium. The JSON will be an array, where each" - + " namespace is its own array or value, so 'name.of.key'" - + " = 'value' would be stored as such:" - + " {\"name\":{\"of\":{\"key\":\"value\"}}}. Due to lack of" - + " support for pretty printing in the json library currently used," - + " prettyprint is unsupported, however it is intended to be" - + " supported in the future."; + + " format. All the pros and cons of yml apply here, but instead" + + " of using the yml style to store the data, values are stored" + + " in a JSON medium. The JSON will be an array, where each" + + " namespace is its own array or value, so 'name.of.key'" + + " = 'value' would be stored as such:" + + " {\"name\":{\"of\":{\"key\":\"value\"}}}. Due to lack of" + + " support for pretty printing in the json library currently used," + + " prettyprint is unsupported, however it is intended to be" + + " supported in the future."; } @Override @@ -78,7 +79,7 @@ protected String getBlankDataModel() { } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } } diff --git a/src/main/java/com/laytonsmith/persistence/MSSQLServerDataSource.java b/src/main/java/com/laytonsmith/persistence/MSSQLServerDataSource.java new file mode 100644 index 0000000000..808db49681 --- /dev/null +++ b/src/main/java/com/laytonsmith/persistence/MSSQLServerDataSource.java @@ -0,0 +1,348 @@ +package com.laytonsmith.persistence; + +import com.laytonsmith.PureUtilities.Common.StreamUtils; +import com.laytonsmith.PureUtilities.Common.StringUtils; +import com.laytonsmith.PureUtilities.DaemonManager; +import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.PureUtilities.Web.WebUtility; +import com.laytonsmith.annotations.datasource; +import com.laytonsmith.core.MSVersion; +import com.laytonsmith.persistence.io.ConnectionMixinFactory; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + */ +@datasource("mssql") +public final class MSSQLServerDataSource extends SQLDataSource { + + private MSSQLServerDataSource() { + super(); + } + + private String host; + private String instance; + private int port; + private String username; + private String password; + private String database; + private String table; + private Map extraParameters = new HashMap<>(); + + private String connectionString; + + public MSSQLServerDataSource(URI uri, ConnectionMixinFactory.ConnectionMixinOptions options) + throws DataSourceException { + super(uri, options); + try { + Class.forName(com.microsoft.sqlserver.jdbc.SQLServerDriver.class.getName()); + } catch (ClassNotFoundException ex) { + throw new DataSourceException("Could not instantiate a MSSQL data source, no driver appears to exist.", ex); + } + host = uri.getHost(); + if(host == null) { + throw new DataSourceException("Invalid URI specified for data source \"" + uri.toString() + "\""); + } + if(host.contains("\\")) { + String[] split = host.split("\\\\"); + host = split[0]; + instance = split[1]; + } + port = uri.getPort(); + if(port < 0) { + port = 1433; + } + if(uri.getUserInfo() != null) { + String[] split = uri.getUserInfo().split(":"); + username = split[0]; + if(split.length > 1) { + password = split[1]; + } + } + if(uri.getPath().split("/").length != 3 || !uri.getPath().startsWith("/")) { + throw new DataSourceException("Invalid path information for mssql connection \"" + uri.toString() + "\"." + + " Path requires a database name and a table name, for instance \"/testDatabase/tableName"); + } else { + String[] split = uri.getPath().split("/"); + //First one should be empty + database = split[1]; + table = split[2]; + } + + extraParameters.putAll(WebUtility.getQueryMap(uri.getQuery())); + connectionString = "jdbc:sqlserver://" + host; + if(instance != null) { + connectionString += "\\" + instance; + } + + connectionString += ":" + port; + connectionString += ";"; + if(username != null) { + connectionString += "user=" + username + ";"; + } + if(password != null) { + connectionString += "password=" + password + ";"; + } + + connectionString += "databaseName=" + database + ";"; + + for(Map.Entry params : extraParameters.entrySet()) { + connectionString += params.getKey() + "=" + params.getValue() + ";"; + } + + try { + connect(); + //Create the table if it doesn't exist + //The columns in the table + for(String query : getTableCreationQueries(database, table)) { + try(Statement statement = getConnection().createStatement()) { + statement.executeUpdate(query); + } + } + } catch (IOException | SQLException ex) { + throw new DataSourceException("Could not connect to MySQL data source \"" + + (password != null ? uri.toString().replace(password, "") : uri.toString()) + "\"" + + " (using \"" + + (password != null ? getConnectionString().replace(password, "") : getConnectionString()) + + "\" to connect): " + ex.getMessage(), ex); + } + } + + @Override + protected String getTable() { + return table; + } + + @Override + protected String getConnectionString() { + return connectionString; + } + + @Override + protected void startTransaction0(DaemonManager dm) { + try { + try(Statement statement = getConnection().createStatement()) { + statement.execute("BEGIN TRANSACTION"); + } + } catch (SQLException ex) { + Logger.getLogger(MSSQLServerDataSource.class.getName()).log(Level.SEVERE, null, ex); + } + } + + @Override + protected void stopTransaction0(DaemonManager dm, boolean rollback) throws DataSourceException, IOException { + try { + try(Statement statement = getConnection().createStatement()) { + statement.execute(rollback ? "ROLLBACK" : "COMMIT"); + } + } catch (SQLException ex) { + Logger.getLogger(MSSQLServerDataSource.class.getName()).log(Level.SEVERE, null, ex); + } + } + + private InputStream getKeyHash(String key) { + try { + MessageDigest digest = java.security.MessageDigest.getInstance("MD5"); + digest.update(key.getBytes()); + return StreamUtils.GetInputStream(digest.digest()); + } catch (NoSuchAlgorithmException ex) { + throw new Error(ex); + } + } + + @Override + protected boolean set0(DaemonManager dm, String[] key, String value) + throws ReadOnlyException, DataSourceException, IOException { + try { + connect(); + if(value == null) { + clearKey0(dm, key); + } else { + try(PreparedStatement statement = getConnection() + .prepareStatement("EXEC [dbo].[" + table + "_upsert] @keyHash = ?, @key = ?, @value = ?")) { + String joinedKey = StringUtils.Join(key, "."); + statement.setBinaryStream(1, getKeyHash(joinedKey)); + statement.setString(2, joinedKey); + statement.setString(3, value); + statement.executeUpdate(); + } + } + updateLastConnected(); + return true; + } catch (SQLException ex) { + throw new DataSourceException(ex.getMessage(), ex); + } + } + + @Override + protected Map getValues0(String[] leadKey) throws DataSourceException { + try { + connect(); + Map map; + try(PreparedStatement statement = connection.prepareStatement("SELECT [key], [value] FROM [dbo].[" + + table + "]" + + " WHERE [key] LIKE ?")) { + statement.setString(1, StringUtils.Join(leadKey, ".") + "%"); + map = new HashMap<>(); + try(ResultSet results = statement.executeQuery()) { + while(results.next()) { + map.put(results.getString(getKeyColumn()).split("\\."), results.getString(getValueColumn())); + } + } + updateLastConnected(); + } + return map; + } catch (SQLException | IOException ex) { + throw new DataSourceException(ex.getMessage(), ex); + } + } + + @Override + protected void clearKey0(DaemonManager dm, String[] key) + throws ReadOnlyException, DataSourceException, IOException { + try { + connect(); + try(PreparedStatement statement = getConnection() + .prepareStatement("DELETE FROM [dbo].[" + table + "] WHERE [key_hash]=?")) { + String joinedKey = StringUtils.Join(key, "."); + statement.setBinaryStream(1, getKeyHash(joinedKey)); + statement.executeUpdate(); + } + updateLastConnected(); + } catch (SQLException ex) { + throw new DataSourceException(ex.getMessage(), ex); + } + } + + @Override + protected String get0(String[] key) throws DataSourceException { + try { + connect(); + String ret; + try(PreparedStatement statement = getConnection().prepareStatement("SELECT TOP(1) [" + getValueColumn() + + "] FROM [dbo].[" + getTable() + "] WHERE [key_hash]=?")) { + String joinedKey = StringUtils.Join(key, "."); + statement.setBinaryStream(1, getKeyHash(joinedKey)); + ret = null; + try(ResultSet result = statement.executeQuery()) { + if(result.next()) { + ret = result.getString(getValueColumn()); + } + } + } + updateLastConnected(); + return ret; + } catch (SQLException | IOException ex) { + throw new DataSourceException(ex.getMessage(), ex); + } + } + + @Override + public String docs() { + String docs = "MSSQL {mssql://[user[:password]@]host[\\instanceName][:port]/database/table?extraParameters}" + + " This type stores data in a MSSQL database. Unlike the" + + " file based systems, this is extremely efficient, but" + + " requires a database connection already set up to work." + + " This also always allows for simultaneous connections" + + " from multiple data sink/sources at once, which is not" + + " possible without the potential for corruption in file" + + " based data sources, without risking either data corruption," + + " or extremely low efficiency. To set up the database properly, it is required to" + + " run several commands, these are run automatically on first run, but you may choose" + + " to manually run the following sequence yourself: <%SYNTAX|sql|" + + StringUtils.Join(getTableCreationQueries("testDatabase", "testTable"), "%>\n\n<%SYNTAX|sql|") + + "%>\n\n" + + "The allowed extra parameters in the connection string follows the general values described" + + " [https://docs.microsoft.com/en-us/sql/connect/jdbc/setting-the-connection-properties" + + "?view=sql-server-ver15 here]," + + " but the format follows a standard URI query string syntax," + + " and the protocol is \"mssql\" rather than \"jdbc:sqlserver\"." + + " For instance, the following connection string" + + " jdbc:sqlserver://localhost\\MSSQLDB;user=username;password=1234;applicationIntent=ReadOnly;" + + "applicationName=myApp" + + " with a PN connection to a table named \"myTable\" would be written as" + + " mssql://user:password@localhost\\myInstance:1433/myDatabase/myTable" + + "?applicationIntent=ReadOnly&applicationName=myApp." + + " The host information is not optional in MethodScript. The port, if not specified, defaults to 1433." + + " Additional configuration is needed for Windows Authentication, and for general configuration of" + + " SQL Server, but it is the same for general SQL connections using query()." + + " Please see the detailed information under the SQL Server section on the [[SQL]] page for further" + + " information."; + return docs; + } + + @SuppressWarnings({"checkstyle:operatorwrap", "checkstyle:nowhitespacebefore", "checkstyle:separatorwrap"}) + public String[] getTableCreationQueries(String database, String tableName) { + return new String[] { + "USE [" + database + "]\n" + , + "/****** Object: Table [dbo].[" + tableName + "] ******/\n" + + "SET ANSI_NULLS ON\n" + , + "SET QUOTED_IDENTIFIER ON\n" + , + "IF NOT (EXISTS (SELECT * \n" + + " FROM INFORMATION_SCHEMA.TABLES \n" + + " WHERE TABLE_SCHEMA = 'dbo' \n" + + " AND TABLE_NAME = '" + tableName + "'))\n" + + "BEGIN\n" + + "\n" + + " CREATE TABLE [dbo].[" + tableName + "](\n" + + " -- This is the binary hash of the unlimited length key column, so the table may have a" + + " primary key.\n" + + " [key_hash] [BINARY](16) NOT NULL,\n" + + " -- The key itself, stored for plaintext readability, and full text searches for getting" + + " values.\n" + + " [key] [VARCHAR](MAX) NOT NULL,\n" + + " -- The value itself, which may be null.\n" + + " [value] [NVARCHAR](MAX) NULL,\n" + + " CONSTRAINT [PK_" + tableName + "] PRIMARY KEY CLUSTERED \n" + + " (\n" + + " [key_hash] ASC\n" + + " )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON," + + " ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]\n" + + " ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]\n" + + "END\n" + , + "DROP PROCEDURE IF EXISTS [dbo].[" + tableName + "_upsert]\n" + , + "CREATE PROCEDURE [dbo].[" + tableName + "_upsert] ( @keyHash BINARY(16), @key VARCHAR(MAX)," + + " @value NVARCHAR(MAX) )\n" + + "AS \n" + + " SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;\n" + + " BEGIN TRAN\n" + + " \n" + + " IF EXISTS ( SELECT * FROM [dbo].[" + tableName + "] WITH (UPDLOCK)" + + " WHERE [key_hash] = @keyHash )\n" + + " \n" + + " UPDATE [dbo].[" + tableName + "]\n" + + " SET [value] = @value\n" + + " WHERE [key] = @key;\n" + + " \n" + + " ELSE \n" + + " \n" + + " INSERT [dbo].[" + tableName + "] ( [key_hash], [key], [value] )\n" + + " VALUES ( @keyHash, @key, @value );\n" + + " \n" + + " COMMIT" + }; + } + + @Override + public Version since() { + return MSVersion.V3_3_4; + } + +} diff --git a/src/main/java/com/laytonsmith/persistence/MemoryDataSource.java b/src/main/java/com/laytonsmith/persistence/MemoryDataSource.java index 28bc2945be..b89fd214c6 100644 --- a/src/main/java/com/laytonsmith/persistence/MemoryDataSource.java +++ b/src/main/java/com/laytonsmith/persistence/MemoryDataSource.java @@ -3,12 +3,13 @@ import com.laytonsmith.PureUtilities.Common.StringUtils; import com.laytonsmith.PureUtilities.DaemonManager; import com.laytonsmith.annotations.datasource; -import com.laytonsmith.core.CHVersion; +import com.laytonsmith.core.MSVersion; import com.laytonsmith.persistence.io.ConnectionMixinFactory; import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.Collections; +import java.util.EnumSet; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -20,76 +21,76 @@ import java.util.logging.Logger; /** - * This class provides a temporary data source that is in memory only, and is - * never written out to disk. All methods in this class are thread safe. + * This class provides a temporary data source that is in memory only, and is never written out to disk. All methods in + * this class are thread safe. */ @datasource("mem") public final class MemoryDataSource extends AbstractDataSource { - - private static final Map> databasePool = new TreeMap>(); - + + private static final Map> DATABASE_POOL = new TreeMap<>(); + /** * Clears all data from all databases. Should be called when a natural reload type operation is called. */ - public synchronized static void ClearDatabases(){ - for(String s : databasePool.keySet()){ - databasePool.get(s).clear(); + public static synchronized void ClearDatabases() { + for(String s : DATABASE_POOL.keySet()) { + DATABASE_POOL.get(s).clear(); } - databasePool.clear(); + DATABASE_POOL.clear(); } @Override public void disconnect() { ClearDatabases(); } - + /** - * Retrieves the underlying database for a given database name. - * The actual database instance is returned, not a copy, and if - * the database doesn't exist, a new one is returned, therefore - * this will never return null. + * Retrieves the underlying database for a given database name. The actual database instance is returned, not a + * copy, and if the database doesn't exist, a new one is returned, therefore this will never return null. + * * @param name - * @return + * @return */ - public synchronized static Map getDatabase(String name){ - if(!databasePool.containsKey(name)){ - databasePool.put(name, Collections.synchronizedMap(new TreeMap())); + public static synchronized Map getDatabase(String name) { + if(!DATABASE_POOL.containsKey(name)) { + DATABASE_POOL.put(name, Collections.synchronizedMap(new TreeMap<>())); } - return databasePool.get(name); + return DATABASE_POOL.get(name); } - - private String dbName; - private List transactionList = new ArrayList(); - + + private final String dbName; + private final List transactionList = new ArrayList<>(); + private static enum Action { CLEAR, SET } - + private static class Transaction { + public Action action; public String key; public String value; } - - private synchronized void addTransaction(Transaction transaction){ + + private synchronized void addTransaction(Transaction transaction) { //If the transaction list contains this key already, we can clear it //and add this one Iterator it = transactionList.iterator(); - while(it.hasNext()){ + while(it.hasNext()) { Transaction t = it.next(); - if(t.key.equals(transaction.key)){ + if(t.key.equals(transaction.key)) { it.remove(); } } transactionList.add(transaction); } - - private synchronized void replayTransactions(){ - for(Transaction t : transactionList){ + + private synchronized void replayTransactions() { + for(Transaction t : transactionList) { try { - if(t.action == Action.CLEAR){ + if(t.action == Action.CLEAR) { clearKey0(null, t.key.split("\\.")); - } else if(t.action == Action.SET){ + } else if(t.action == Action.SET) { set0(null, t.key.split("\\."), t.value); } } catch (Exception ex) { @@ -98,12 +99,8 @@ private synchronized void replayTransactions(){ } transactionList.clear(); } - - private MemoryDataSource(){ - - } - - public MemoryDataSource(URI uri, ConnectionMixinFactory.ConnectionMixinOptions options) throws DataSourceException{ + + public MemoryDataSource(URI uri, ConnectionMixinFactory.ConnectionMixinOptions options) throws DataSourceException { super(uri, options); dbName = uri.getSchemeSpecificPart(); } @@ -115,7 +112,7 @@ protected void startTransaction0(DaemonManager dm) { @Override protected synchronized void stopTransaction0(DaemonManager dm, boolean rollback) throws DataSourceException, IOException { - if(rollback){ + if(rollback) { transactionList.clear(); } else { replayTransactions(); @@ -125,7 +122,7 @@ protected synchronized void stopTransaction0(DaemonManager dm, boolean rollback) @Override protected boolean set0(DaemonManager dm, String[] key, String value) throws ReadOnlyException, DataSourceException, IOException { String fKey = StringUtils.Join(key, "."); - if(inTransaction()){ + if(inTransaction()) { Transaction t = new Transaction(); t.action = Action.SET; t.key = fKey; @@ -134,16 +131,16 @@ protected boolean set0(DaemonManager dm, String[] key, String value) throws Read } else { getDatabase(dbName).put(fKey, value); } - + return true; } @Override protected synchronized String get0(String[] key) throws DataSourceException { String fKey = StringUtils.Join(key, "."); - if(inTransaction()){ - for(Transaction t : transactionList){ - if(t.action == Action.SET && t.key.equals(fKey)){ + if(inTransaction()) { + for(Transaction t : transactionList) { + if(t.action == Action.SET && t.key.equals(fKey)) { return t.value; } } @@ -161,7 +158,7 @@ protected boolean hasKey0(String[] key) throws DataSourceException { @Override protected void clearKey0(DaemonManager dm, String[] key) throws ReadOnlyException, DataSourceException, IOException { String fKey = StringUtils.Join(key, "."); - if(inTransaction()){ + if(inTransaction()) { Transaction t = new Transaction(); t.action = Action.CLEAR; t.key = fKey; @@ -170,12 +167,18 @@ protected void clearKey0(DaemonManager dm, String[] key) throws ReadOnlyExceptio getDatabase(dbName).remove(StringUtils.Join(key, ".")); } } - + + @Override + public void clearDatabase(DaemonManager dm) throws DataSourceException, ReadOnlyException, IOException { + ClearDatabases(); + } + + @Override public Set stringKeySet(String[] keyBase) throws DataSourceException { - Set keys = new TreeSet(); - for(String[] key : keySet(keyBase)){ + Set keys = new TreeSet<>(); + for(String[] key : keySet(keyBase)) { keys.add(StringUtils.Join(key, ".")); } return keys; @@ -183,25 +186,25 @@ public Set stringKeySet(String[] keyBase) throws DataSourceException { @Override public synchronized Set keySet(String[] keyBase) throws DataSourceException { - Set set = new HashSet(); - for(String key : getDatabase(dbName).keySet()){ + Set set = new HashSet<>(); + for(String key : getDatabase(dbName).keySet()) { set.add(key); } //Now go through the transactions and add things that are set, and //remove things that are cleared - if(inTransaction()){ - for(Transaction t : transactionList){ - if(t.action == Action.CLEAR){ + if(inTransaction()) { + for(Transaction t : transactionList) { + if(t.action == Action.CLEAR) { set.remove(t.key); - } else if(t.action == Action.SET){ + } else if(t.action == Action.SET) { set.add(t.key); } } } - Set ret = new HashSet(); + Set ret = new HashSet<>(); String kb = StringUtils.Join(keyBase, "."); - for(String key : set){ - if(key.startsWith(kb)){ + for(String key : set) { + if(key.startsWith(kb)) { ret.add(key.split("\\.")); } } @@ -214,14 +217,14 @@ public void populate() throws DataSourceException { } @Override - public DataSourceModifier[] implicitModifiers() { - return new DataSourceModifier[]{}; + public EnumSet implicitModifiers() { + return null; } @Override - public DataSourceModifier[] invalidModifiers() { + public EnumSet invalidModifiers() { //No modifiers are appropriate on here - return DataSourceModifier.values(); + return EnumSet.allOf(DataSourceModifier.class); } @Override @@ -236,8 +239,8 @@ public String docs() { } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } - + } diff --git a/src/main/java/com/laytonsmith/persistence/MySQLDataSource.java b/src/main/java/com/laytonsmith/persistence/MySQLDataSource.java index aa63a063f3..edcafc6495 100644 --- a/src/main/java/com/laytonsmith/persistence/MySQLDataSource.java +++ b/src/main/java/com/laytonsmith/persistence/MySQLDataSource.java @@ -2,8 +2,9 @@ import com.laytonsmith.PureUtilities.Common.StringUtils; import com.laytonsmith.PureUtilities.DaemonManager; +import com.laytonsmith.PureUtilities.Web.WebUtility; import com.laytonsmith.annotations.datasource; -import com.laytonsmith.core.CHVersion; +import com.laytonsmith.core.MSVersion; import com.laytonsmith.persistence.io.ConnectionMixinFactory; import java.io.IOException; import java.io.UnsupportedEncodingException; @@ -13,6 +14,8 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.util.HashMap; +import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; @@ -21,9 +24,8 @@ */ @datasource("mysql") public class MySQLDataSource extends SQLDataSource { - - /* These values may not be changed without creating an upgrade routine */ + /* These values may not be changed without creating an upgrade routine */ private static final String KEY_HASH_COLUMN = "key_hash"; private String host; private int port; @@ -31,68 +33,77 @@ public class MySQLDataSource extends SQLDataSource { private String password; private String database; private String table; - - private MySQLDataSource(){ + private Map extraParameters = new HashMap<>(); + + private MySQLDataSource() { super(); } - - public MySQLDataSource(URI uri, ConnectionMixinFactory.ConnectionMixinOptions options) throws DataSourceException{ + + public MySQLDataSource(URI uri, ConnectionMixinFactory.ConnectionMixinOptions options) throws DataSourceException { super(uri, options); try { - Class.forName(com.mysql.jdbc.Driver.class.getName()); + Class.forName(com.mysql.cj.jdbc.Driver.class.getName()); } catch (ClassNotFoundException ex) { throw new DataSourceException("Could not instantiate a MySQL data source, no driver appears to exist.", ex); } host = uri.getHost(); - if(host == null){ + if(host == null) { throw new DataSourceException("Invalid URI specified for data source \"" + uri.toString() + "\""); } port = uri.getPort(); - if(port < 0){ + if(port < 0) { port = 3306; } - if(uri.getUserInfo() != null){ + if(uri.getUserInfo() != null) { String[] split = uri.getUserInfo().split(":"); username = split[0]; - if(split.length > 1){ + if(split.length > 1) { password = split[1]; } } - if(uri.getPath().split("/").length != 3 || !uri.getPath().startsWith("/")){ + if(uri.getPath().split("/").length != 3 || !uri.getPath().startsWith("/")) { throw new DataSourceException("Invalid path information for mysql connection \"" + uri.toString() + "\"." + " Path requires a database name and a table name, for instance \"/testDatabase/tableName"); } else { - String [] split = uri.getPath().split("/"); + String[] split = uri.getPath().split("/"); //First one should be empty database = split[1]; table = split[2]; } //Escape any quotes in the table name, because we can't use prepared statements here table = table.replace("`", "``"); + extraParameters.putAll(WebUtility.getQueryMap(uri.getQuery())); try { connect(); //Create the table if it doesn't exist //The columns in the table - try (Statement statement = getConnection().createStatement()) { + try(Statement statement = getConnection().createStatement()) { statement.executeUpdate(getTableCreationQuery(table)); } } catch (IOException | SQLException ex) { - throw new DataSourceException("Could not connect to MySQL data source \"" + uri.toString() + "\": " + ex.getMessage(), ex); + throw new DataSourceException("Could not connect to MySQL data source \"" + + (password != null ? uri.toString().replace(password, "") : uri.toString()) + "\"" + + " (using \"" + + (password != null ? getConnectionString().replace(password, "") : getConnectionString()) + + "\" to connect): " + ex.getMessage(), ex); } - + } - + /** - * Returns the table creation query that should be used to create the table specified. - * This is public for documentation, but is used internally. + * Returns the table creation query that should be used to create the table specified. This is public for + * documentation, but is used internally. + * * @param table - * @return + * @return */ public final String getTableCreationQuery(String table) { return "CREATE TABLE IF NOT EXISTS `" + table + "` (\n" - + " -- This is an UNHEX(MD5('key')) binary hash of the unlimited length key column, so the table may have a primary key.\n" + + " -- This is an UNHEX(MD5('key')) binary hash of the unlimited\n" + + " -- length key column, so the table may have a primary key.\n" + " `" + KEY_HASH_COLUMN + "` BINARY(16) PRIMARY KEY NOT NULL,\n" - + " -- This is the key itself, stored for plaintext readability, and for full text searches for getting values\n" + + " -- This is the key itself, stored for plaintext readability,\n" + + " -- and for full text searches for getting values\n" + " `" + getKeyColumn() + "` TEXT NOT NULL,\n" + " -- The value itself, which may be null\n" + " `" + getValueColumn() + "` MEDIUMTEXT\n" @@ -111,47 +122,51 @@ public final String getTableCreationQuery(String table) { @Override protected String getConnectionString() { try { - return "jdbc:mysql://" + host + ":" + port + "/" + database + "?generateSimpleParameterMetadata=true" + String s = "jdbc:mysql://" + host + ":" + port + "/" + database + "?generateSimpleParameterMetadata=true" + "&jdbcCompliantTruncation=false" + (username == null ? "" : "&user=" + URLEncoder.encode(username, "UTF-8")) + (password == null ? "" : "&password=" + URLEncoder.encode(password, "UTF-8")); + if(!extraParameters.isEmpty()) { + s += "&" + WebUtility.encodeParameters(extraParameters); + } + return s; } catch (UnsupportedEncodingException ex) { throw new Error(ex); } } - + @Override public String get0(String[] key) throws DataSourceException { try { connect(); - String ret; - try (PreparedStatement statement = getConnection().prepareStatement("SELECT `" + getValueColumn() + "` FROM `" + String ret; + try(PreparedStatement statement = getConnection().prepareStatement("SELECT `" + getValueColumn() + "` FROM `" + getEscapedTable() + "` WHERE `" + KEY_HASH_COLUMN + "`=UNHEX(MD5(?))" + " LIMIT 1")) { String joinedKey = StringUtils.Join(key, "."); statement.setString(1, joinedKey); ret = null; - try (ResultSet result = statement.executeQuery()) { - if(result.next()){ + try(ResultSet result = statement.executeQuery()) { + if(result.next()) { ret = result.getString(getValueColumn()); } } } updateLastConnected(); return ret; - } catch(SQLException | IOException ex){ + } catch (SQLException | IOException ex) { throw new DataSourceException(ex.getMessage(), ex); } } - + @Override public boolean set0(DaemonManager dm, String[] key, String value) throws ReadOnlyException, DataSourceException, IOException { try { connect(); - if(value == null){ + if(value == null) { clearKey0(dm, key); } else { - try (PreparedStatement statement = getConnection().prepareStatement("REPLACE INTO" + try(PreparedStatement statement = getConnection().prepareStatement("REPLACE INTO" + " `" + getEscapedTable() + "`" + " (`" + KEY_HASH_COLUMN + "`, `" + getKeyColumn() + "`, `" + getValueColumn() + "`)" + " VALUES (UNHEX(MD5(?)), ?, ?)")) { @@ -168,20 +183,20 @@ public boolean set0(DaemonManager dm, String[] key, String value) throws ReadOnl throw new DataSourceException(ex.getMessage(), ex); } } - + @Override protected void clearKey0(DaemonManager dm, String[] key) throws ReadOnlyException, DataSourceException, IOException { - if(hasKey(key)){ - try{ - connect(); - try (PreparedStatement statement = getConnection().prepareStatement("DELETE FROM `" + getEscapedTable() + "`" + if(hasKey(key)) { + try { + connect(); + try(PreparedStatement statement = getConnection().prepareStatement("DELETE FROM `" + getEscapedTable() + "`" + " WHERE `" + KEY_HASH_COLUMN + "`=UNHEX(MD5(?))")) { String joinedKey = StringUtils.Join(key, "."); statement.setString(1, joinedKey); statement.executeUpdate(); } updateLastConnected(); - } catch(Exception e){ + } catch (Exception e) { throw new DataSourceException(e.getMessage(), e); } } @@ -189,28 +204,31 @@ protected void clearKey0(DaemonManager dm, String[] key) throws ReadOnlyExceptio @Override public String docs() { - return "MySQL {mysql://[user[:password]@]host[:port]/database/table}" - + " This type stores data in a MySQL database. Unlike the" - + " file based systems, this is extremely efficient, but" - + " requires a database connection already set up to work." - + " This also always allows for simultaneous connections" - + " from multiple data sink/sources at once, which is not" - + " possible without the potential for corruption in file" - + " based data sources, without risking either data corruption," - + " or extremely low efficiency. The layout of the table" - + " in the database is required to be of a specific format:" - + " " + getTableCreationQuery("testTable") + ""; + return "MySQL {mysql://[user[:password]@]host[:port]/database/table?extraParameters}" + + " This type stores data in a MySQL database. Unlike the" + + " file based systems, this is extremely efficient, but" + + " requires a database connection already set up to work." + + " This also always allows for simultaneous connections" + + " from multiple data sink/sources at once, which is not" + + " possible without the potential for corruption in file" + + " based data sources, without risking either data corruption," + + " or extremely low efficiency. The layout of the table" + + " in the database is required to be of a specific format: <%SYNTAX|sql|" + + getTableCreationQuery("testTable") + "%>\n\n" + + "Extra parameters may provided to the MySQL connection, and they are" + + " merged with the existing required parameters and sent through as" + + " is to the server. They should be in the format \"a=1&b=2\"."; } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } @Override protected void startTransaction0(DaemonManager dm) { try { - try(Statement statement = getConnection().createStatement()){ + try(Statement statement = getConnection().createStatement()) { statement.execute("START TRANSACTION"); } } catch (SQLException ex) { @@ -221,12 +239,12 @@ protected void startTransaction0(DaemonManager dm) { @Override protected void stopTransaction0(DaemonManager dm, boolean rollback) throws DataSourceException, IOException { try { - if (rollback) { - try(PreparedStatement statement = getConnection().prepareStatement("ROLLBACK")){ + if(rollback) { + try(PreparedStatement statement = getConnection().prepareStatement("ROLLBACK")) { statement.execute(); } } else { - try(PreparedStatement statement = getConnection().prepareStatement("COMMIT")){ + try(PreparedStatement statement = getConnection().prepareStatement("COMMIT")) { statement.execute(); } } @@ -240,5 +258,5 @@ protected void stopTransaction0(DaemonManager dm, boolean rollback) throws DataS protected String getTable() { return table; } - + } diff --git a/src/main/java/com/laytonsmith/persistence/PersistenceNetwork.java b/src/main/java/com/laytonsmith/persistence/PersistenceNetwork.java index 5760fd501b..9e55f6599a 100644 --- a/src/main/java/com/laytonsmith/persistence/PersistenceNetwork.java +++ b/src/main/java/com/laytonsmith/persistence/PersistenceNetwork.java @@ -1,183 +1,92 @@ package com.laytonsmith.persistence; -import com.laytonsmith.PureUtilities.Common.FileUtil; import com.laytonsmith.PureUtilities.DaemonManager; -import com.laytonsmith.persistence.io.ConnectionMixinFactory; -import java.io.File; import java.io.IOException; import java.net.URI; -import java.util.HashMap; import java.util.Map; -import java.util.Set; /** - * A persistence network is a group of data sources that can act transparently - * as a single data source. The defining feature of a network is the filter - * configuration, which defines where exactly all the keys are mapped to, and - * the type of data sources in the network. To build a network, all you need it - * the configuration and a default storage protocol. Everything else is handled - * accordingly. The main operations of a persistence network are setting values, - * getting values, and getting multiple values at once, based on a namespace - * match. All other aspects of how the data is stored and retrieved are - * abstracted, so you needn't worry about any of those details. * - * */ -public class PersistenceNetwork { - - private DataSourceFilter filter; - private ConnectionMixinFactory.ConnectionMixinOptions options; - - /** - * Given a configuration and a default URI, constructs a new persistence - * network. The defaultURI is used in the event that the configuration does - * not specify a "**" key, to ensure that all keys will be matched. - * - * @param configuration - * @param defaultURI - */ - public PersistenceNetwork(File configuration, URI defaultURI, ConnectionMixinFactory.ConnectionMixinOptions options) throws IOException, DataSourceException { - this(FileUtil.read(ensureCreated(configuration)), defaultURI, options); - } - - private static File ensureCreated(File f) throws IOException { - if (!f.exists()) { - if (f.getParentFile() != null) { - f.getParentFile().mkdirs(); - } - f.createNewFile(); - } - return f; - } +public interface PersistenceNetwork { /** - * Given a configuration and a default URI, constructs a new persistence - * network. The defaultURI is used in the event that the configuration does - * not specify a "**" key, to ensure that all keys will be matched. + * Removes the key entirely. * - * @param configuration - * @param defaultURI - */ - public PersistenceNetwork(String configuration, URI defaultURI, ConnectionMixinFactory.ConnectionMixinOptions options) throws DataSourceException { - filter = new DataSourceFilter(configuration, defaultURI); - this.options = options; - //Data sources are lazily loaded, so we don't need to do anything right now to load them. - } - - /** - * Returns the URI describing where this key currently lives, given the filter - * configuration. This is meant for debug purposes only, and not for general use, - as it breaks the transparency of the PersistenceNetwork. - * @param key - * @return + * @param dm The system DaemonManager. + * @param key The key to clear. + * @throws DataSourceException If there is an error with the data source + * @throws ReadOnlyException If the data source is marked as read only + * @throws IOException If an IO exception occurs during the operation + * @throws IllegalArgumentException If the key is invalid */ - public URI getKeySource(String [] key){ - return filter.getConnection(key); - } + void clearKey(DaemonManager dm, String[] key) + throws DataSourceException, ReadOnlyException, IOException, IllegalArgumentException; /** - * Returns the data source object for this URI. The returned DataSource is - * threadsafe. - * - * @param uri - * @return - * @throws DataSourceException + * Clears the entire database. This is a permanent removal of everything! This is not guaranteed to complete. + * It could complete some deletions, then throw, succeed, or totally fail. + * @param dm The system DaemonManager. + * @throws com.laytonsmith.persistence.DataSourceException If there is an error with the data source + * @throws ReadOnlyException If the data source is marked as read only + * @throws IOException If an IO exception occurs during the operation */ - private DataSource getDataSource(URI uri) throws DataSourceException { - return ThreadsafeDataSource.GetDataSource(uri, options); - } + void clearDatabase(DaemonManager dm) throws DataSourceException, ReadOnlyException, IOException; /** * Returns the value for this key, or null if it doesn't exist. * * @param key - * @param isMainThread If true, then async connections will fail. This should be set by the calling - * code to determine whether or not this thread is considered the "main thread" and if blocking calls - * are acceptable. * @return - * @throws DataSourceException + * @throws DataSourceException If there is an error with the data source * @throws IllegalArgumentException If the key is invalid */ - public synchronized String get(String[] key/*boolean isMainThread*/) throws DataSourceException, IllegalArgumentException { - //TODO: Use isMainThread here - DataSource ds = getDataSource(filter.getConnection(key)); - return ds.get(key); - } + String get(String[] key) throws DataSourceException, IllegalArgumentException; /** - * Sets the value for this key, and returns true if it was actually changed. - * (Which typically implies that the model was changed.) + * Returns the URI describing where this key currently lives, given the filter configuration. This is meant for + * debug purposes only, and not for general use, as it breaks the transparency of the PersistenceNetwork. * * @param key - * @param value * @return - * @throws DataSourceException - * @throws ReadOnlyException - * @throws IOException - * @throws IllegalArgumentException If the key is invalid */ - public synchronized boolean set(DaemonManager dm, String[] key, String value) throws DataSourceException, ReadOnlyException, IOException, IllegalArgumentException { - DataSource ds = getDataSource(filter.getConnection(key)); - return ds.set(dm, key, value); - } + URI getKeySource(String[] key); /** - * Returns true if the key is actually set; that is if a call to get() would - * not return null. + * This method returns a list of all keys and values that match the namespace. If a.b.c is requested, then keys (and + * values) a.b.c.d and a.b.c.e would be returned. If the namespace is empty, it will match all keys. * - * @param key + * @param namespace * @return - * @throws DataSourceException + * @throws DataSourceException If there is an error with the data source * @throws IllegalArgumentException If the key is invalid */ - public synchronized boolean hasKey(String[] key) throws DataSourceException, IllegalArgumentException { - DataSource ds = getDataSource(filter.getConnection(key)); - return ds.hasKey(key); - } + Map getNamespace(String[] namespace) throws DataSourceException, IllegalArgumentException; /** - * Removes the key entirely. + * Returns true if the key is actually set; that is if a call to get() would not return null. * * @param key * @return - * @throws DataSourceException + * @throws DataSourceException If there is an error with the data source * @throws IllegalArgumentException If the key is invalid */ - public synchronized void clearKey(DaemonManager dm, String[] key) throws DataSourceException, ReadOnlyException, IOException, IllegalArgumentException { - DataSource ds = getDataSource(filter.getConnection(key)); - ds.clearKey(dm, key); - } + boolean hasKey(String[] key) throws DataSourceException, IllegalArgumentException; /** - * This method returns a list of all keys and values that match the - * namespace. If a.b.c is requested, then keys (and values) a.b.c.d and - * a.b.c.e would be returned. If the namespace is empty, it will match - * all keys. + * Sets the value for this key, and returns true if it was actually changed. (Which typically implies that the model + * was changed.) * - * @param namespace + * @param dm The system DaemonManager + * @param key + * @param value * @return + * @throws DataSourceException If there is an error with the data source + * @throws ReadOnlyException If the data source is marked as read only + * @throws IOException If an IO exception occurs during the operation * @throws IllegalArgumentException If the key is invalid */ - public synchronized Map getNamespace(String[] namespace /*, boolean isMainThread*/) throws DataSourceException, IllegalArgumentException { - //TODO: isMainThread needs to be used here somewhere, I think? - //This is a slight optimization, instead of looking through ALL the connections, just - //look through the ones that have data matching this namespace in them. - Set uris = filter.getAllConnections(namespace); - - Map map = new HashMap(); - for (URI uri : uris) { - Map db = getDataSource(uri).getValues(namespace); - //We have to loop through and make sure that this key should actually - //come from this connection. It may not, if there are "hidden" keys. - //The easiest way to do so is to ask the filter if this key == this uri. - //The .get here isn't terribly inefficient, because in this case it won't actually - //poll the data source. - for(String[] key : db.keySet()){ - if(filter.getConnection(key).equals(uri)){ - map.put(key, db.get(key)); - } - } - } - return map; - } + boolean set(DaemonManager dm, String[] key, String value) + throws DataSourceException, ReadOnlyException, IOException, IllegalArgumentException; + } diff --git a/src/main/java/com/laytonsmith/persistence/PersistenceNetworkImpl.java b/src/main/java/com/laytonsmith/persistence/PersistenceNetworkImpl.java new file mode 100644 index 0000000000..b3656949af --- /dev/null +++ b/src/main/java/com/laytonsmith/persistence/PersistenceNetworkImpl.java @@ -0,0 +1,192 @@ +package com.laytonsmith.persistence; + +import com.laytonsmith.PureUtilities.Common.FileUtil; +import com.laytonsmith.PureUtilities.DaemonManager; +import com.laytonsmith.persistence.io.ConnectionMixinFactory; +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * A persistence network is a group of data sources that can act transparently as a single data source. The defining + * feature of a network is the filter configuration, which defines where exactly all the keys are mapped to, and the + * type of data sources in the network. To build a network, all you need it the configuration and a default storage + * protocol. Everything else is handled accordingly. The main operations of a persistence network are setting values, + * getting values, and getting multiple values at once, based on a namespace match. All other aspects of how the data is + * stored and retrieved are abstracted, so you needn't worry about any of those details. + * + * + */ +public class PersistenceNetworkImpl implements PersistenceNetwork { + + private DataSourceFilter filter; + private ConnectionMixinFactory.ConnectionMixinOptions options; + + /** + * Given a configuration and a default URI, constructs a new persistence network. The defaultURI is used in the + * event that the configuration does not specify a "**" key, to ensure that all keys will be matched. + * + * @param configuration + * @param defaultURI The URI to be used in the case that no ** filter is found in the file + */ + public PersistenceNetworkImpl(File configuration, URI defaultURI, ConnectionMixinFactory.ConnectionMixinOptions options) throws IOException, DataSourceException { + this(FileUtil.read(ensureCreated(configuration)), defaultURI, options); + } + + private static File ensureCreated(File f) throws IOException { + if(!f.exists()) { + if(f.getParentFile() != null) { + f.getParentFile().mkdirs(); + } + + try { + f.createNewFile(); + } catch (IOException ex) { + throw new IOException("IOException when trying to create file: " + f.getAbsolutePath(), ex); + } + } + return f; + } + + /** + * Given a configuration and a default URI, constructs a new persistence network. The defaultURI is used in the + * event that the configuration does not specify a "**" key, to ensure that all keys will be matched. + * + * @param configuration + * @param defaultURI The URI to be used in the case that no ** filter is found in the file + */ + public PersistenceNetworkImpl(String configuration, URI defaultURI, ConnectionMixinFactory.ConnectionMixinOptions options) throws DataSourceException { + filter = new DataSourceFilter(configuration, defaultURI); + this.options = options; + //Data sources are lazily loaded, so we don't need to do anything right now to load them. + } + + /** + * Returns the URI describing where this key currently lives, given the filter configuration. This is meant for + * debug purposes only, and not for general use, as it breaks the transparency of the PersistenceNetwork. + * + * @param key + * @return + */ + @Override + public URI getKeySource(String[] key) { + return filter.getConnection(key); + } + + /** + * Returns the data source object for this URI. The returned DataSource is threadsafe. + * + * @param uri + * @return + * @throws DataSourceException + */ + private DataSource getDataSource(URI uri) throws DataSourceException { + return ThreadsafeDataSource.GetDataSource(uri, options); + } + + /** + * Returns the value for this key, or null if it doesn't exist. + * + * @param key + * @param isMainThread If true, then async connections will fail. This should be set by the calling code to + * determine whether or not this thread is considered the "main thread" and if blocking calls are acceptable. + * @return + * @throws DataSourceException + * @throws IllegalArgumentException If the key is invalid + */ + @Override + public synchronized String get(String[] key/*boolean isMainThread*/) throws DataSourceException, IllegalArgumentException { + //TODO: Use isMainThread here + DataSource ds = getDataSource(filter.getConnection(key)); + return ds.get(key); + } + + /** + * Sets the value for this key, and returns true if it was actually changed. (Which typically implies that the model + * was changed.) + * + * @param key + * @param value + * @return + * @throws DataSourceException + * @throws ReadOnlyException + * @throws IOException + * @throws IllegalArgumentException If the key is invalid + */ + @Override + public synchronized boolean set(DaemonManager dm, String[] key, String value) throws DataSourceException, ReadOnlyException, IOException, IllegalArgumentException { + DataSource ds = getDataSource(filter.getConnection(key)); + return ds.set(dm, key, value); + } + + /** + * Returns true if the key is actually set; that is if a call to get() would not return null. + * + * @param key + * @return + * @throws DataSourceException + * @throws IllegalArgumentException If the key is invalid + */ + @Override + public synchronized boolean hasKey(String[] key) throws DataSourceException, IllegalArgumentException { + DataSource ds = getDataSource(filter.getConnection(key)); + return ds.hasKey(key); + } + + /** + * Removes the key entirely. + * + * @param key + * @return + * @throws DataSourceException + * @throws IllegalArgumentException If the key is invalid + */ + @Override + public synchronized void clearKey(DaemonManager dm, String[] key) throws DataSourceException, ReadOnlyException, IOException, IllegalArgumentException { + DataSource ds = getDataSource(filter.getConnection(key)); + ds.clearKey(dm, key); + } + + @Override + public void clearDatabase(DaemonManager dm) throws DataSourceException, ReadOnlyException, IOException { + for(URI uri : filter.getAllConnections()) { + DataSource ds = getDataSource(uri); + ds.clearDatabase(dm); + } + } + + /** + * This method returns a list of all keys and values that match the namespace. If a.b.c is requested, then keys (and + * values) a.b.c.d and a.b.c.e would be returned. If the namespace is empty, it will match all keys. + * + * @param namespace + * @return + * @throws IllegalArgumentException If the key is invalid + */ + @Override + public synchronized Map getNamespace(String[] namespace /*, boolean isMainThread*/) throws DataSourceException, IllegalArgumentException { + //TODO: isMainThread needs to be used here somewhere, I think? + //This is a slight optimization, instead of looking through ALL the connections, just + //look through the ones that have data matching this namespace in them. + Set uris = filter.getAllConnections(namespace); + + Map map = new HashMap(); + for(URI uri : uris) { + Map db = getDataSource(uri).getValues(namespace); + //We have to loop through and make sure that this key should actually + //come from this connection. It may not, if there are "hidden" keys. + //The easiest way to do so is to ask the filter if this key == this uri. + //The .get here isn't terribly inefficient, because in this case it won't actually + //poll the data source. + for(String[] key : db.keySet()) { + if(filter.getConnection(key).equals(uri)) { + map.put(key, db.get(key)); + } + } + } + return map; + } +} diff --git a/src/main/java/com/laytonsmith/persistence/ReadOnlyException.java b/src/main/java/com/laytonsmith/persistence/ReadOnlyException.java index 4d21c76257..5dcdac59e5 100644 --- a/src/main/java/com/laytonsmith/persistence/ReadOnlyException.java +++ b/src/main/java/com/laytonsmith/persistence/ReadOnlyException.java @@ -2,24 +2,22 @@ /** * - * + * */ public class ReadOnlyException extends Exception { - /** - * Creates a new instance of - * ReadOnlyException without detail message. - */ - public ReadOnlyException() { - } + /** + * Creates a new instance of ReadOnlyException without detail message. + */ + public ReadOnlyException() { + } - /** - * Constructs an instance of - * ReadOnlyException with the specified detail message. - * - * @param msg the detail message. - */ - public ReadOnlyException(String msg) { - super(msg); - } + /** + * Constructs an instance of ReadOnlyException with the specified detail message. + * + * @param msg the detail message. + */ + public ReadOnlyException(String msg) { + super(msg); + } } diff --git a/src/main/java/com/laytonsmith/persistence/RedisDataSource.java b/src/main/java/com/laytonsmith/persistence/RedisDataSource.java index 19c762ee5e..9411539e99 100644 --- a/src/main/java/com/laytonsmith/persistence/RedisDataSource.java +++ b/src/main/java/com/laytonsmith/persistence/RedisDataSource.java @@ -4,112 +4,116 @@ import com.laytonsmith.PureUtilities.DaemonManager; import com.laytonsmith.PureUtilities.Web.WebUtility; import com.laytonsmith.annotations.datasource; -import com.laytonsmith.core.CHVersion; +import com.laytonsmith.core.MSVersion; import com.laytonsmith.persistence.io.ConnectionMixinFactory; import java.io.IOException; import java.net.URI; +import java.util.EnumSet; import java.util.HashSet; import java.util.Map; import java.util.Set; +import redis.clients.jedis.DefaultJedisClientConfig; +import redis.clients.jedis.HostAndPort; import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisShardInfo; +import redis.clients.jedis.JedisClientConfig; import redis.clients.jedis.Transaction; import redis.clients.jedis.exceptions.JedisConnectionException; /** * - * + * */ @datasource("redis") public class RedisDataSource extends AbstractDataSource { private Jedis connection; private Transaction transaction; - private JedisShardInfo shardInfo; + private JedisClientConfig shardInfo; private String host; private int port; private int timeout; private String password; private long lastConnected = 0; - - private RedisDataSource(){ - + + private RedisDataSource() { + } - + + @SuppressWarnings("UseSpecificCatch") public RedisDataSource(URI uri, ConnectionMixinFactory.ConnectionMixinOptions options) throws DataSourceException { super(uri, options); - try{ + try { host = uri.getHost(); port = uri.getPort(); Map queryString = WebUtility.getQueryMap(uri.getQuery()); - if(port == -1){ - shardInfo = new JedisShardInfo(host); - } else { - shardInfo = new JedisShardInfo(host, port); - } - if(queryString.containsKey("timeout")){ + DefaultJedisClientConfig.Builder builder = DefaultJedisClientConfig.builder(); + if(queryString.containsKey("timeout")) { timeout = Integer.parseInt(queryString.get("timeout")); - shardInfo.setTimeout(timeout); + builder.socketTimeoutMillis(timeout); } - if(queryString.containsKey("password")){ + if(queryString.containsKey("password")) { password = queryString.get("password"); - shardInfo.setPassword(password); + builder.password(password); } + shardInfo = builder.build(); connect(); - } catch(Exception e){ + } catch (Exception e) { throw new DataSourceException(e.getMessage(), e); } } - - private void connect(){ + + private void connect() { boolean needToConnect = false; - if(connection == null){ + if(connection == null) { needToConnect = true; - } else if(!connection.isConnected()){ + } else if(!connection.isConnected()) { needToConnect = true; - } else if(lastConnected < System.currentTimeMillis() - 10000){ + } else if(lastConnected < System.currentTimeMillis() - 10000) { // If we connected more than 10 seconds ago, we should re-test - // the connection explicitely, because isConnected may return true, + // the connection explicitly, because isConnected may return true, // even if the connection will fail. The only real way to test // if the connection is actually open is to run a test query, but - // doing that too often will cause unneccessary delay, so we + // doing that too often will cause unnecessary delay, so we // wait an arbitrary amount, in this case, 10 seconds. try { // We don't actually care if this value exists or not, just // that it doesn't break. connection.exists("connection.test"); // Nope, don't need to connect. - } catch(JedisConnectionException ex){ + } catch (JedisConnectionException ex) { // Need to connect, since this broke. needToConnect = true; } } - if(needToConnect){ - connection = new Jedis(shardInfo); + if(needToConnect) { + int oPort = 6379; + if(port != -1) { + oPort = port; + } + connection = new Jedis(new HostAndPort(host, oPort), shardInfo); } } @Override public void disconnect() throws DataSourceException { - if(connection != null){ + if(connection != null) { connection.disconnect(); } } - @Override protected boolean set0(DaemonManager dm, String[] key, String value) throws ReadOnlyException, DataSourceException, IOException { connect(); String ckey = StringUtils.Join(key, "."); String status; - try{ - if(inTransaction()){ + try { + if(inTransaction()) { status = transaction.set(ckey, value).get(); } else { status = connection.set(ckey, value); } lastConnected = System.currentTimeMillis(); - } catch(JedisConnectionException e){ + } catch (JedisConnectionException e) { throw new DataSourceException(e); } return "OK".equals(status); @@ -119,14 +123,14 @@ protected boolean set0(DaemonManager dm, String[] key, String value) throws Read protected void clearKey0(DaemonManager dm, String[] key) throws ReadOnlyException, DataSourceException, IOException { connect(); String ckey = StringUtils.Join(key, "."); - try{ - if(inTransaction()){ + try { + if(inTransaction()) { transaction.del(ckey); } else { - connection.del(ckey); + connection.del(ckey); } lastConnected = System.currentTimeMillis(); - } catch(JedisConnectionException e){ + } catch (JedisConnectionException e) { throw new DataSourceException(e); } } @@ -135,16 +139,16 @@ protected void clearKey0(DaemonManager dm, String[] key) throws ReadOnlyExceptio protected String get0(String[] key) throws DataSourceException { connect(); String ckey = StringUtils.Join(key, "."); - try{ + try { String ret; - if(inTransaction()){ + if(inTransaction()) { ret = transaction.get(ckey).get(); } else { - ret = connection.get(ckey); + ret = connection.get(ckey); } lastConnected = System.currentTimeMillis(); return ret; - } catch(JedisConnectionException e){ + } catch (JedisConnectionException e) { throw new DataSourceException(e); } } @@ -154,18 +158,18 @@ public Set keySet(String[] keyBase) throws DataSourceException { connect(); Set ret; String kb = StringUtils.Join(keyBase, ".") + "*"; - try{ - if(inTransaction()){ + try { + if(inTransaction()) { ret = transaction.keys(kb).get(); } else { ret = connection.keys(kb); } lastConnected = System.currentTimeMillis(); - } catch(JedisConnectionException e){ + } catch (JedisConnectionException e) { throw new DataSourceException(e); } - Set parsed = new HashSet(); - for(String s : ret){ + Set parsed = new HashSet<>(); + for(String s : ret) { parsed.add(s.split("\\.")); } return parsed; @@ -177,20 +181,18 @@ public void populate() throws DataSourceException { } @Override - public DataSourceModifier[] implicitModifiers() { - return new DataSourceModifier[]{ - DataSourceModifier.TRANSIENT - }; + public EnumSet implicitModifiers() { + return EnumSet.of(DataSourceModifier.TRANSIENT); } @Override - public DataSourceModifier[] invalidModifiers() { - return new DataSourceModifier[]{ + public EnumSet invalidModifiers() { + return EnumSet.of( DataSourceModifier.HTTP, DataSourceModifier.HTTPS, DataSourceModifier.PRETTYPRINT, DataSourceModifier.SSH - }; + ); } @Override @@ -204,8 +206,8 @@ public String docs() { } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } @Override @@ -218,12 +220,12 @@ protected void startTransaction0(DaemonManager dm) { @Override protected void stopTransaction0(DaemonManager dm, boolean rollback) throws DataSourceException, IOException { dm.activateThread(null); - if(rollback){ + if(rollback) { transaction.discard(); } else { transaction.exec(); } dm.deactivateThread(null); } - + } diff --git a/src/main/java/com/laytonsmith/persistence/SQLDataSource.java b/src/main/java/com/laytonsmith/persistence/SQLDataSource.java index 39a011cbcc..4cc63d1bfa 100644 --- a/src/main/java/com/laytonsmith/persistence/SQLDataSource.java +++ b/src/main/java/com/laytonsmith/persistence/SQLDataSource.java @@ -10,6 +10,7 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -19,6 +20,7 @@ * */ public abstract class SQLDataSource extends AbstractDataSource { + private static final String KEY_COLUMN = "key"; private static final String VALUE_COLUMN = "value"; protected Connection connection; @@ -33,8 +35,8 @@ protected SQLDataSource(URI uri, ConnectionMixinFactory.ConnectionMixinOptions o } /** - * Gets the connection object. There is no guarantee it will be connected or - * valid, so {@link #connect()} should be called first if necessary. + * Gets the connection object. There is no guarantee it will be connected or valid, so {@link #connect()} should be + * called first if necessary. * * @return */ @@ -68,8 +70,7 @@ protected String getValueColumn() { protected abstract String getTable(); /** - * Gets the connection string that is used to establish a new connection, if - * needed. + * Gets the connection string that is used to establish a new connection, if needed. * * @return */ @@ -85,53 +86,53 @@ protected String getEscapedTable() { } /** - * All calls to connect must have a corresponding call to disconnect() in a - * finally block. + * All calls to connect must have a corresponding call to disconnect() in a finally block. */ protected void connect() throws IOException, SQLException { boolean needToConnect = false; - if (connection == null) { + if(connection == null) { needToConnect = true; - } else if (connection.isClosed()) { + } else if(connection.isClosed()) { needToConnect = true; - } else if (lastConnected < System.currentTimeMillis() - 10000) { + } else if(lastConnected < System.currentTimeMillis() - 10000) { // If we connected more than 10 seconds ago, we should re-test - // the connection explicitely, because isClosed may return false, + // the connection explicitly, because isClosed may return false, // even if the connection will fail. The only real way to test // if the connection is actually open is to run a test query, but // doing that too often will cause unneccessary delay, so we // wait an arbitrary amount, in this case, 10 seconds. try { - if(!connection.isValid(3)){ + if(!connection.isValid(3)) { needToConnect = true; } - } catch(AbstractMethodError ex){ + } catch (AbstractMethodError ex) { // isValid was added later, some connection types may not have that method. try { connection.createStatement().execute(getTestQuery()); - } catch(SQLException e){ + } catch (SQLException e) { needToConnect = true; } } } - if (needToConnect) { + if(needToConnect) { connection = DriverManager.getConnection(getConnectionString()); } } - + /** - * If a connection type doesn't support isValid, and "SELECT 1" won't work - * as a test query, this should be overridden. - * @return + * If a connection type doesn't support isValid, and "SELECT 1" won't work as a test query, this should be + * overridden. + * + * @return */ - protected String getTestQuery(){ + protected String getTestQuery() { return "SELECT 1"; } @Override public void disconnect() throws DataSourceException { try { - if (connection != null) { + if(connection != null) { connection.close(); connection = null; } @@ -139,25 +140,25 @@ public void disconnect() throws DataSourceException { throw new DataSourceException(ex.getMessage(), ex); } } - + @Override public Set keySet(String[] keyBase) throws DataSourceException { String searchPrefix = StringUtils.Join(keyBase, "."); try { connect(); Set set; - try(PreparedStatement statement = connection.prepareStatement("SELECT `" + KEY_COLUMN + "` FROM `" + getEscapedTable() + "` WHERE `" + KEY_COLUMN + "` LIKE ?")){ + try(PreparedStatement statement = connection.prepareStatement("SELECT `" + KEY_COLUMN + "` FROM `" + getEscapedTable() + "` WHERE `" + KEY_COLUMN + "` LIKE ?")) { statement.setString(1, searchPrefix + "%"); set = new HashSet<>(); - try(ResultSet result = statement.executeQuery()){ - while(result.next()){ + try(ResultSet result = statement.executeQuery()) { + while(result.next()) { set.add(result.getString(KEY_COLUMN).split("\\.")); } } lastConnected = System.currentTimeMillis(); } return set; - } catch(SQLException | IOException ex){ + } catch (SQLException | IOException ex) { throw new DataSourceException(ex.getMessage(), ex); } } @@ -167,34 +168,34 @@ protected Map getValues0(String[] leadKey) throws DataSourceEx try { connect(); Map map; - try (PreparedStatement statement = connection.prepareStatement("SELECT `" + KEY_COLUMN + "`, `" + VALUE_COLUMN + "` FROM `" + getEscapedTable() + "`" - + " WHERE `" + KEY_COLUMN + "` LIKE ?")){ + try(PreparedStatement statement = connection.prepareStatement("SELECT `" + KEY_COLUMN + "`, `" + VALUE_COLUMN + "` FROM `" + getEscapedTable() + "`" + + " WHERE `" + KEY_COLUMN + "` LIKE ?")) { statement.setString(1, StringUtils.Join(leadKey, ".") + "%"); map = new HashMap<>(); - try (ResultSet results = statement.executeQuery()){ - while(results.next()){ + try(ResultSet results = statement.executeQuery()) { + while(results.next()) { map.put(results.getString(KEY_COLUMN).split("\\."), results.getString(VALUE_COLUMN)); } } lastConnected = System.currentTimeMillis(); } return map; - } catch(SQLException | IOException ex){ + } catch (SQLException | IOException ex) { throw new DataSourceException(ex.getMessage(), ex); } } - + @Override protected void clearKey0(DaemonManager dm, String[] key) throws ReadOnlyException, DataSourceException, IOException { - if(hasKey(key)){ - try{ - connect(); - try (PreparedStatement statement = connection.prepareStatement("DELETE FROM `" + getEscapedTable() + "` WHERE `" + KEY_COLUMN + "`=?")) { + if(hasKey(key)) { + try { + connect(); + try(PreparedStatement statement = connection.prepareStatement("DELETE FROM `" + getEscapedTable() + "` WHERE `" + KEY_COLUMN + "`=?")) { statement.setString(1, StringUtils.Join(key, ".")); statement.executeUpdate(); } lastConnected = System.currentTimeMillis(); - } catch(Exception e){ + } catch (Exception e) { throw new DataSourceException(e.getMessage(), e); } } @@ -206,18 +207,18 @@ public void populate() throws DataSourceException { } @Override - public DataSourceModifier[] implicitModifiers() { - return new DataSourceModifier[]{DataSourceModifier.TRANSIENT}; + public EnumSet implicitModifiers() { + return EnumSet.of(DataSourceModifier.TRANSIENT); } @Override - public DataSourceModifier[] invalidModifiers() { - return new DataSourceModifier[]{DataSourceModifier.HTTP, DataSourceModifier.HTTPS, DataSourceModifier.SSH, + public EnumSet invalidModifiers() { + return EnumSet.of(DataSourceModifier.HTTP, DataSourceModifier.HTTPS, DataSourceModifier.SSH, DataSourceModifier.PRETTYPRINT - }; + ); } - - protected void updateLastConnected(){ + + protected void updateLastConnected() { lastConnected = System.currentTimeMillis(); } diff --git a/src/main/java/com/laytonsmith/persistence/SQLiteDataSource.java b/src/main/java/com/laytonsmith/persistence/SQLiteDataSource.java index 7c0ee053ad..6ed04b014c 100644 --- a/src/main/java/com/laytonsmith/persistence/SQLiteDataSource.java +++ b/src/main/java/com/laytonsmith/persistence/SQLiteDataSource.java @@ -3,9 +3,16 @@ import com.laytonsmith.PureUtilities.Common.StringUtils; import com.laytonsmith.PureUtilities.DaemonManager; import com.laytonsmith.annotations.datasource; -import com.laytonsmith.core.CHVersion; +import com.laytonsmith.core.MSLog; +import com.laytonsmith.core.MSVersion; +import com.laytonsmith.core.MethodScriptFileLocations; +import com.laytonsmith.core.constructs.Target; import com.laytonsmith.persistence.io.ConnectionMixin; import com.laytonsmith.persistence.io.ConnectionMixinFactory; +import org.sqlite.SQLiteJDBCLoader; +import org.sqlite.util.OSInfo; + +import java.io.File; import java.io.IOException; import java.net.URI; import java.sql.DriverManager; @@ -22,63 +29,75 @@ /** * - * + * */ @datasource("sqlite") public class SQLiteDataSource extends SQLDataSource { - + /* These values may not be changed without creating an upgrade routine */ private static final String TABLE_NAME = "persistance"; //Note the misspelling! private String path; private ConnectionMixin mixin; /** - * If true, the connection will not be recycled, and will be disconnected - * after each call, and re-established before each call. If false, the - * connection is recycled using the AbstractDataSource connect logic. - * - * It appears as though this needs to remain true for connections to work correctly - * if multiple connections are established. Further research may need to be done - * to establish if this is a Windows only problem, or if there is some other - * complicating factor that causes connections to have to continually retry. - * Additionally, it may be that the issue could be solved using a more complex - * transaction system. Regardless, it seems to work in all cases without transactions, - * so long as this remains true. + * If true, the connection will not be recycled, and will be disconnected after each call, and re-established before + * each call. If false, the connection is recycled using the AbstractDataSource connect logic. + * + * It appears as though this needs to remain true for connections to work correctly if multiple connections are + * established. Further research may need to be done to establish if this is a Windows only problem, or if there is + * some other complicating factor that causes connections to have to continually retry. Additionally, it may be that + * the issue could be solved using a more complex transaction system. Regardless, it seems to work in all cases + * without transactions, so long as this remains true. */ - private final static boolean DO_DISCONNECTS = true; + private static final boolean DO_DISCONNECTS = true; /** - * If the connection takes this long to connect, it will give up, throw an exception, - * and continue on. This timeout should be large enough to never cause false positives, - * but small enough that the host itself won't decide the connection has completely stalled - * out. + * If the connection takes this long to connect, it will give up, throw an exception, and continue on. This timeout + * should be large enough to never cause false positives, but small enough that the host itself won't decide the + * connection has completely stalled out. */ - private final static int TIMEOUT = 30000; - - private SQLiteDataSource(){ - + private static final int TIMEOUT = 30000; + + private SQLiteDataSource() { + } - + @SuppressWarnings("SleepWhileInLoop") - public SQLiteDataSource(URI uri, ConnectionMixinFactory.ConnectionMixinOptions options) throws DataSourceException{ - super(uri, options); - mixin = getConnectionMixin(); + public SQLiteDataSource(URI uri, ConnectionMixinFactory.ConnectionMixinOptions options) throws DataSourceException { + super(uri, options); + mixin = getConnectionMixin(); try { Class.forName(org.sqlite.JDBC.class.getName()); + // Set native library override path if not already set. (e.g. -Dorg.sqlite.lib.path="/path/to/lib") + if(System.getProperty("org.sqlite.lib.path") == null) { + System.setProperty("org.sqlite.lib.path", new File(MethodScriptFileLocations.getDefault().getConfigDirectory(), + "sqlite/native/" + OSInfo.getNativeLibFolderPathForCurrentOS()).getAbsolutePath()); + } + try { + // Load native library before connection to detect when the library is missing. + // This is done in SQLiteProfile as well. + SQLiteJDBCLoader.initialize(); + } catch (Exception ex) { + throw new DataSourceException("Failed to load a native sqlite library for your platform." + + " You can download the library file from" + + " https://github.com/xerial/sqlite-jdbc/tree/master/src/main/resources/org/sqlite/native/" + + OSInfo.getNativeLibFolderPathForCurrentOS() + " and place it into " + + System.getProperty("org.sqlite.lib.path")); + } path = mixin.getPath(); connect(); long startTime = System.currentTimeMillis(); - while(true){ - if(System.currentTimeMillis() - TIMEOUT > startTime){ - throw new DataSourceException("Data source at " + uri + " could not connect for " + while(true) { + if(System.currentTimeMillis() - TIMEOUT > startTime) { + throw new DataSourceException("Data source at " + uri + " could not connect for " + (TIMEOUT / 1000) + " seconds, so we're giving up on retrying."); } try { - try (Statement statement = getConnection().createStatement()) { + try(Statement statement = getConnection().createStatement()) { statement.executeUpdate(getTableCreationQuery()); } updateLastConnected(); break; - } catch(SQLException ex){ - if(ex.getMessage().startsWith("[SQLITE_BUSY]") || ex.getMessage().equals("database is locked")){ + } catch (SQLException ex) { + if(ex.getMessage().startsWith("[SQLITE_BUSY]") || ex.getMessage().equals("database is locked")) { try { Thread.sleep(getRandomSleepTime()); } catch (InterruptedException ex1) { @@ -90,9 +109,9 @@ public SQLiteDataSource(URI uri, ConnectionMixinFactory.ConnectionMixinOptions o } } } catch (ClassNotFoundException | UnsupportedOperationException | IOException | SQLException ex) { - throw new DataSourceException("An error occured while setting up a connection to the SQLite database", ex); + throw new DataSourceException("An error occurred while setting up a connection to the SQLite database", ex); } finally { - if(DO_DISCONNECTS){ + if(DO_DISCONNECTS) { disconnect(); } } @@ -100,9 +119,9 @@ public SQLiteDataSource(URI uri, ConnectionMixinFactory.ConnectionMixinOptions o @Override protected void connect() throws IOException, SQLException { - if(DO_DISCONNECTS){ + if(DO_DISCONNECTS) { //Speculative fix. Just kill the connection each time, then renew it. - if(connection != null){ + if(connection != null) { connection.close(); } connection = DriverManager.getConnection(getConnectionString()); @@ -110,50 +129,54 @@ protected void connect() throws IOException, SQLException { super.connect(); } } - - - + /** * {@inheritDoc} - * - * SQLite connections support INSERT OR REPLACE, which prevents duplicate keys from mattering, so this method needs to be overridden for - * SQLite. + * + * SQLite connections support INSERT OR REPLACE, which prevents duplicate keys from mattering, so this method needs + * to be overridden for SQLite. + * * @param dm * @param key * @param value * @return * @throws ReadOnlyException * @throws DataSourceException - * @throws IOException + * @throws IOException */ @Override @SuppressWarnings("SleepWhileInLoop") public boolean set0(DaemonManager dm, String[] key, String value) throws ReadOnlyException, DataSourceException, IOException { try { connect(); - if(value == null){ + if(value == null) { clearKey0(dm, key); } else { long startTime = System.currentTimeMillis(); - while (true) { - if (System.currentTimeMillis() - TIMEOUT > startTime) { + while(true) { + if(System.currentTimeMillis() - TIMEOUT > startTime) { throw new DataSourceException("Data source at " + uri + " could not connect for " + (TIMEOUT / 1000) + " seconds, so we're giving up on retrying."); } try { - try (PreparedStatement statement = getConnection().prepareStatement("INSERT OR REPLACE INTO `" + TABLE_NAME + try(PreparedStatement statement = getConnection().prepareStatement("INSERT OR REPLACE INTO `" + TABLE_NAME + "` (`" + getKeyColumn() + "`, `" + getValueColumn() + "`) VALUES (?, ?)")) { statement.setString(1, StringUtils.Join(key, ".")); statement.setString(2, value); statement.executeUpdate(); } break; - } catch(SQLException ex){ - if(ex.getMessage().startsWith("[SQLITE_BUSY]") + } catch (SQLException ex) { + if(ex.getMessage().startsWith("[SQLITE_BUSY]") // This one only happens with SETs - || ex.getMessage().equals("cannot commit transaction - SQL statements in progress")){ + || ex.getMessage().equals("cannot commit transaction - SQL statements in progress")) { try { - Thread.sleep(getRandomSleepTime()); + int sleepTime = getRandomSleepTime(); + MSLog.GetLogger().d(MSLog.Tags.PERSISTENCE, "Got recoverable error from SQLite DB," + + " sleeping for " + sleepTime + " then potentially retrying. (" + + ex.getMessage() + ")", + Target.UNKNOWN); + Thread.sleep(sleepTime); } catch (InterruptedException ex1) { // } @@ -168,12 +191,12 @@ public boolean set0(DaemonManager dm, String[] key, String value) throws ReadOnl } catch (SQLException ex) { throw new DataSourceException(ex.getMessage(), ex); } finally { - if(DO_DISCONNECTS){ + if(DO_DISCONNECTS) { disconnect(); } } } - + @Override @SuppressWarnings("SleepWhileInLoop") public Set keySet(String[] keyBase) throws DataSourceException { @@ -182,25 +205,25 @@ public Set keySet(String[] keyBase) throws DataSourceException { connect(); Set set = new HashSet<>(); long startTime = System.currentTimeMillis(); - while (true) { - if (System.currentTimeMillis() - TIMEOUT > startTime) { + while(true) { + if(System.currentTimeMillis() - TIMEOUT > startTime) { throw new DataSourceException("Data source at " + uri + " could not connect for " + (TIMEOUT / 1000) + " seconds, so we're giving up on retrying."); } try { - try(PreparedStatement statement = getConnection().prepareStatement("SELECT `" + getKeyColumn() + "` FROM `" + getEscapedTable() - + "` WHERE `" + getKeyColumn() + "` LIKE ?")){ + try(PreparedStatement statement = getConnection().prepareStatement("SELECT `" + getKeyColumn() + "` FROM `" + getEscapedTable() + + "` WHERE `" + getKeyColumn() + "` LIKE ?")) { statement.setString(1, searchPrefix + "%"); - try(ResultSet result = statement.executeQuery()){ - while(result.next()){ + try(ResultSet result = statement.executeQuery()) { + while(result.next()) { set.add(result.getString(getKeyColumn()).split("\\.")); } } } updateLastConnected(); break; - } catch(SQLException ex){ - if(ex.getMessage().startsWith("[SQLITE_BUSY]")){ + } catch (SQLException ex) { + if(ex.getMessage().startsWith("[SQLITE_BUSY]")) { try { Thread.sleep(getRandomSleepTime()); } catch (InterruptedException ex1) { @@ -212,10 +235,10 @@ public Set keySet(String[] keyBase) throws DataSourceException { } } return set; - } catch(SQLException | IOException ex){ + } catch (SQLException | IOException ex) { throw new DataSourceException(ex.getMessage(), ex); } finally { - if(DO_DISCONNECTS){ + if(DO_DISCONNECTS) { disconnect(); } } @@ -228,26 +251,31 @@ public String get0(String[] key) throws DataSourceException { connect(); String ret = null; long startTime = System.currentTimeMillis(); - while (true) { - if (System.currentTimeMillis() - TIMEOUT > startTime) { + while(true) { + if(System.currentTimeMillis() - TIMEOUT > startTime) { throw new DataSourceException("Data source at " + uri + " could not connect for " + (TIMEOUT / 1000) + " seconds, so we're giving up on retrying."); } try { - try (PreparedStatement statement = getConnection().prepareStatement("SELECT `" + getValueColumn() + "` FROM `" + getEscapedTable() + try(PreparedStatement statement = getConnection().prepareStatement("SELECT `" + getValueColumn() + "` FROM `" + getEscapedTable() + "` WHERE `" + getKeyColumn() + "`=? LIMIT 1")) { statement.setString(1, StringUtils.Join(key, ".")); - try (ResultSet result = statement.executeQuery()) { - if(result.next()){ + try(ResultSet result = statement.executeQuery()) { + if(result.next()) { ret = result.getString(getValueColumn()); } } } break; - } catch(SQLException ex){ - if(ex.getMessage().startsWith("[SQLITE_BUSY]")){ + } catch (SQLException ex) { + if(ex.getMessage().startsWith("[SQLITE_BUSY]")) { try { - Thread.sleep(getRandomSleepTime()); + int sleepTime = getRandomSleepTime(); + MSLog.GetLogger().d(MSLog.Tags.PERSISTENCE, "Got recoverable error from SQLite DB," + + " sleeping for " + sleepTime + " then potentially retrying. (" + + ex.getMessage() + ")", + Target.UNKNOWN); + Thread.sleep(sleepTime); } catch (InterruptedException ex1) { // } @@ -258,15 +286,15 @@ public String get0(String[] key) throws DataSourceException { } updateLastConnected(); return ret; - } catch(SQLException | IOException ex){ + } catch (SQLException | IOException ex) { throw new DataSourceException(ex.getMessage(), ex); } finally { - if(DO_DISCONNECTS){ + if(DO_DISCONNECTS) { disconnect(); } } } - + @Override @SuppressWarnings("SleepWhileInLoop") protected Map getValues0(String[] leadKey) throws DataSourceException { @@ -274,24 +302,24 @@ protected Map getValues0(String[] leadKey) throws DataSourceEx connect(); Map map = new HashMap<>(); long startTime = System.currentTimeMillis(); - while (true) { - if (System.currentTimeMillis() - TIMEOUT > startTime) { + while(true) { + if(System.currentTimeMillis() - TIMEOUT > startTime) { throw new DataSourceException("Data source at " + uri + " could not connect for " + (TIMEOUT / 1000) + " seconds, so we're giving up on retrying."); } try { - try (PreparedStatement statement = getConnection().prepareStatement("SELECT `" + getKeyColumn() + "`, `" + getValueColumn() + "` FROM `" - + getEscapedTable() + "`" + " WHERE `" + getKeyColumn() + "` LIKE ?")){ + try(PreparedStatement statement = getConnection().prepareStatement("SELECT `" + getKeyColumn() + "`, `" + getValueColumn() + "` FROM `" + + getEscapedTable() + "`" + " WHERE `" + getKeyColumn() + "` LIKE ?")) { statement.setString(1, StringUtils.Join(leadKey, ".") + "%"); - try (ResultSet results = statement.executeQuery()){ - while(results.next()){ + try(ResultSet results = statement.executeQuery()) { + while(results.next()) { map.put(results.getString(getKeyColumn()).split("\\."), results.getString(getValueColumn())); } } } break; - } catch(SQLException ex){ - if(ex.getMessage().startsWith("[SQLITE_BUSY]")){ + } catch (SQLException ex) { + if(ex.getMessage().startsWith("[SQLITE_BUSY]")) { try { Thread.sleep(getRandomSleepTime()); } catch (InterruptedException ex1) { @@ -304,38 +332,39 @@ protected Map getValues0(String[] leadKey) throws DataSourceEx } updateLastConnected(); return map; - } catch(SQLException | IOException ex){ + } catch (SQLException | IOException ex) { throw new DataSourceException(ex.getMessage(), ex); } finally { - if(DO_DISCONNECTS){ + if(DO_DISCONNECTS) { disconnect(); } } } - + @Override @SuppressWarnings("SleepWhileInLoop") protected void clearKey0(DaemonManager dm, String[] key) throws ReadOnlyException, DataSourceException, IOException { - try{ + try { + dm.activateThread(Thread.currentThread()); connect(); long startTime = System.currentTimeMillis(); - while (true) { - if (System.currentTimeMillis() - TIMEOUT > startTime) { + while(true) { + if(System.currentTimeMillis() - TIMEOUT > startTime) { throw new DataSourceException("Data source at " + uri + " could not connect for " + (TIMEOUT / 1000) + " seconds, so we're giving up on retrying."); } try { - try (PreparedStatement statement = getConnection().prepareStatement("DELETE FROM `" + getEscapedTable() + try(PreparedStatement statement = getConnection().prepareStatement("DELETE FROM `" + getEscapedTable() + "` WHERE `" + getKeyColumn() + "`=?")) { statement.setString(1, StringUtils.Join(key, ".")); statement.executeUpdate(); updateLastConnected(); } break; - } catch(SQLException ex){ - if(ex.getMessage().startsWith("[SQLITE_BUSY]") + } catch (SQLException ex) { + if(ex.getMessage().startsWith("[SQLITE_BUSY]") // This one only happens with SETs - || ex.getMessage().equals("cannot commit transaction - SQL statements in progress")){ + || ex.getMessage().equals("cannot commit transaction - SQL statements in progress")) { try { Thread.sleep(getRandomSleepTime()); } catch (InterruptedException ex1) { @@ -346,42 +375,44 @@ protected void clearKey0(DaemonManager dm, String[] key) throws ReadOnlyExceptio } } } - } catch(IOException | SQLException e){ + } catch (IOException | SQLException e) { throw new DataSourceException(e.getMessage(), e); } finally { - if(DO_DISCONNECTS){ + if(DO_DISCONNECTS) { disconnect(); } + dm.deactivateThread(Thread.currentThread()); } } @Override public String docs() { return "SQLite {sqlite://path/to/db/file.db} This type store data in a SQLite database." - + " All the pros and cons of MySQL apply here. The database will contain a lone table," - + " and the table should be created with the query: " - + getTableCreationQuery() + ""; + + " All the pros and cons of MySQL apply here. The database will contain a lone table," + + " and the table should be created with the query: <%SYNTAX|sql|" + + getTableCreationQuery() + "%>"; } - + /** - * Returns the table creation query that should be used to create the table specified. - * This is public for documentation, but is used internally. - * @return + * Returns the table creation query that should be used to create the table specified. This is public for + * documentation, but is used internally. + * + * @return */ - public final String getTableCreationQuery(){ + public final String getTableCreationQuery() { return "CREATE TABLE IF NOT EXISTS `" + TABLE_NAME + "` (`" + getKeyColumn() + "` TEXT PRIMARY KEY," - + " `" + getValueColumn() + "` TEXT)"; + + " `" + getValueColumn() + "` TEXT)"; } @Override - public CHVersion since() { - return CHVersion.V3_3_1; + public MSVersion since() { + return MSVersion.V3_3_1; } @Override protected void startTransaction0(DaemonManager dm) { try { - try (PreparedStatement statement = getConnection().prepareStatement("BEGIN EXCLUSIVE TRANSACTION")) { + try(PreparedStatement statement = getConnection().prepareStatement("BEGIN EXCLUSIVE TRANSACTION")) { statement.execute(); } updateLastConnected(); @@ -393,12 +424,12 @@ protected void startTransaction0(DaemonManager dm) { @Override protected void stopTransaction0(DaemonManager dm, boolean rollback) throws DataSourceException, IOException { try { - if (rollback) { - try(PreparedStatement statement = getConnection().prepareStatement("ROLLBACK TRANSACTION")){ + if(rollback) { + try(PreparedStatement statement = getConnection().prepareStatement("ROLLBACK TRANSACTION")) { statement.execute(); } } else { - try(PreparedStatement statement = getConnection().prepareStatement("END TRANSACTION")){ + try(PreparedStatement statement = getConnection().prepareStatement("END TRANSACTION")) { statement.execute(); } } @@ -417,8 +448,8 @@ protected String getTable() { protected String getConnectionString() { return "jdbc:sqlite:" + path; } - - private int getRandomSleepTime(){ - return ((int)(Math.random() * 10) % 10); + + private int getRandomSleepTime() { + return ((int) (Math.random() * 10) % 10); } } diff --git a/src/main/java/com/laytonsmith/persistence/SerializedPersistence.java b/src/main/java/com/laytonsmith/persistence/SerializedPersistence.java index be57258734..f7f14be339 100644 --- a/src/main/java/com/laytonsmith/persistence/SerializedPersistence.java +++ b/src/main/java/com/laytonsmith/persistence/SerializedPersistence.java @@ -1,11 +1,13 @@ package com.laytonsmith.persistence; +import com.laytonsmith.PureUtilities.Common.ArrayUtils; import com.laytonsmith.PureUtilities.Common.StringUtils; import com.laytonsmith.PureUtilities.DaemonManager; import com.laytonsmith.PureUtilities.MemoryMapFileUtil; import com.laytonsmith.PureUtilities.RunnableQueue; import com.laytonsmith.annotations.datasource; -import com.laytonsmith.core.CHVersion; +import com.laytonsmith.core.MSVersion; +import com.laytonsmith.core.Static; import com.laytonsmith.persistence.io.ConnectionMixinFactory; import java.io.ByteArrayOutputStream; import java.io.File; @@ -15,6 +17,7 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.URI; +import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -24,24 +27,22 @@ import java.util.logging.Logger; /** - * This file allows for simple data storage across many different data sources. - * In general, the most common methods used are getValue and setValue. Note that - * getValue, setValue, save, and load are synchronized. + * This file allows for simple data storage across many different data sources. In general, the most common methods used + * are getValue and setValue. Note that getValue, setValue, save, and load are synchronized. + * * - * */ @datasource("ser") public class SerializedPersistence extends AbstractDataSource { - - private SerializedPersistence(){ - + + private SerializedPersistence() { + } /** - * This is the data structure that the registry is stored in. It is a - * HashMap, not a Map, since we are depending on the implementation to - * remain constant, since it is serialized. Do not change this ever, or it - * will break all current serialized databases. + * This is the data structure that the registry is stored in. It is a HashMap, not a Map, since we are depending on + * the implementation to remain constant, since it is serialized. Do not change this ever, or it will break all + * current serialized databases. */ private HashMap data = new HashMap(); private HashMap transactionData = new HashMap(); @@ -80,54 +81,53 @@ public Map rawData() { } /** - * Loads the database from disk. This is automatically called when setValue - * or getValue is called. + * Loads the database from disk. This is automatically called when setValue or getValue is called. * * @throws Exception */ private void load() throws Exception { - if (!isLoaded) { - queue.invokeAndWait(new Callable(){ + if(!isLoaded) { + queue.invokeAndWait(new Callable() { @Override - public Object call() throws Exception { + public Object call() throws Exception { + try { + FileInputStream fis = null; + ObjectInputStream in = null; try { - FileInputStream fis = null; - ObjectInputStream in = null; - try{ - if(!storageLocation.exists()){ - storageLocation.createNewFile(); - } - if(storageLocation.length() == 0){ - data = new HashMap(); - } else { - fis = new FileInputStream(storageLocation); - in = new ObjectInputStream(fis); - data = (HashMap) in.readObject(); - } - isLoaded = true; - } catch(Throwable t){ - t.printStackTrace(); - } finally { - if(fis != null){ - fis.close(); - } - if(in != null){ - in.close(); - } + if(!storageLocation.exists()) { + storageLocation.createNewFile(); + } + if(storageLocation.length() == 0) { + data = new HashMap(); + } else { + fis = new FileInputStream(storageLocation); + in = new ObjectInputStream(fis); + data = (HashMap) in.readObject(); + } + isLoaded = true; + } catch (Throwable t) { + t.printStackTrace(); + } finally { + if(fis != null) { + fis.close(); + } + if(in != null) { + in.close(); } - } catch (FileNotFoundException ex) { - //ignore this one - } catch (Exception ex) { - throw ex; } - return null; + } catch (FileNotFoundException ex) { + //ignore this one + } catch (Exception ex) { + throw ex; } - }); - } + return null; + } + }); + } } - private byte[] byteData = new byte[0]; + private byte[] byteData = ArrayUtils.EMPTY_BYTE_ARRAY; private MemoryMapFileUtil writer = null; private MemoryMapFileUtil.DataGrabber grabber = new MemoryMapFileUtil.DataGrabber() { @@ -136,14 +136,15 @@ public byte[] getData() { return byteData; } }; + /** * Causes the database to be saved to disk * * @throws IOException */ - private void save(final DaemonManager dm) throws IOException{ - if(!inTransaction()){ - if(writer == null){ + private void save(final DaemonManager dm) throws IOException { + if(!inTransaction()) { + if(writer == null) { writer = MemoryMapFileUtil.getInstance(storageLocation, grabber); } queue.invokeLater(dm, new Runnable() { @@ -152,13 +153,13 @@ public void run() { ObjectOutputStream out = null; ByteArrayOutputStream baos = null; try { - if (storageLocation.getParentFile() != null) { + if(storageLocation.getParentFile() != null) { storageLocation.getParentFile().mkdirs(); } - if (!storageLocation.exists()) { + if(!storageLocation.exists()) { storageLocation.createNewFile(); } - // fos = new FileOutputStream(storageLocation); + // fos = new FileOutputStream(storageLocation); baos = new ByteArrayOutputStream(); out = new ObjectOutputStream(baos); out.writeObject(new HashMap(data)); @@ -168,14 +169,14 @@ public void run() { Logger.getLogger(SerializedPersistence.class.getName()).log(Level.SEVERE, null, ex); } finally { try { - if (baos != null) { + if(baos != null) { baos.close(); } } catch (IOException ex) { Logger.getLogger(SerializedPersistence.class.getName()).log(Level.SEVERE, null, ex); } try { - if (out != null) { + if(out != null) { out.close(); } } catch (IOException ex) { @@ -188,20 +189,19 @@ public void run() { } /** - * You should not usually use this method. Please see - * setValue(String[] key, Serializable value) + * You should not usually use this method. Please see setValue(String[] key, Serializable value) */ private String setValue(DaemonManager dm, String key, String value) { //defer loading until we actually try and use the data structure - if (isLoaded == false) { + if(isLoaded == false) { try { load(); } catch (Exception ex) { - Logger.getLogger("Minecraft").log(Level.SEVERE, null, ex); + Static.getLogger().log(Level.SEVERE, null, ex); } } String oldVal = data.get(key); - if (value == null) { + if(value == null) { data.remove(key); } else { data.put(key, value); @@ -214,47 +214,43 @@ private String setValue(DaemonManager dm, String key, String value) { return oldVal; } - private String getValue(String key) { - //defer loading until we actually try and use the data structure - if (isLoaded == false) { - try { - load(); - } catch (Exception ex) { - Logger.getLogger(SerializedPersistence.class.getName()).log(Level.SEVERE, null, ex); - } - } - if (data == null) { - return null; - } - return data.get(key); - } - /** - * Adds or modifies the value of the key. Typically, this convention should - * be followed: + * Adds or modifies the value of the key. Typically, this convention should be followed: *
 	 * key1.key2.key3...
-	 * 
To make this usage easier, the function automatically namespaces - * the values for you. A sample usage might be: + * To make this usage easier, the function automatically namespaces the values for you. A sample usage might + * be: *
 	 * setValue(new String[]{"playerName", "value"}, value);
 	 * 
* - * When using namespaces in this way, the isNamespaceSet function becomes - * available to you. Since plugin values are global, you can use this to - * interact with other plugins. Caution should be used when interacting with - * other plugin's values though. + * When using namespaces in this way, the isNamespaceSet function becomes available to you. Since plugin values are + * global, you can use this to interact with other plugins. Caution should be used when interacting with other + * plugin's values though. * * @param key The key for this particular value - * @param value The value to store. If value is null, the key is simply - * removed. - * @return The object that was in this key, or null if the value did not - * exist. + * @param value The value to store. If value is null, the key is simply removed. + * @return The object that was in this key, or null if the value did not exist. */ private String setValue(DaemonManager dm, String[] key, String value) { return setValue(dm, getNamespace0(key), (String) value); } + private String getValue(String key) { + //defer loading until we actually try and use the data structure + if(isLoaded == false) { + try { + load(); + } catch (Exception ex) { + Logger.getLogger(SerializedPersistence.class.getName()).log(Level.SEVERE, null, ex); + } + } + if(data == null) { + return null; + } + return data.get(key); + } + /** * Combines the String array into a single string * @@ -263,8 +259,8 @@ private String setValue(DaemonManager dm, String[] key, String value) { */ private static String getNamespace0(String[] key) { StringBuilder b = new StringBuilder(); - for (int i = 0; i < key.length; i++) { - if (i > 0) { + for(int i = 0; i < key.length; i++) { + if(i > 0) { b.append(".").append(key[i]); } else { b.append(key[i]); @@ -277,8 +273,8 @@ private static String getNamespace0(String[] key) { public Set keySet(String[] keyBase) { Set list = new HashSet(); String kb = StringUtils.Join(keyBase, "."); - for (String key : data.keySet()) { - if(key.startsWith(kb)){ + for(String key : data.keySet()) { + if(key.startsWith(kb)) { list.add(key.split("\\.")); } } @@ -299,7 +295,7 @@ public boolean set0(DaemonManager dm, String[] key, String value) throws ReadOnl @Override public void populate() throws DataSourceException { - if (!finishedInitializing) { + if(!finishedInitializing) { return; } try { @@ -310,13 +306,13 @@ public void populate() throws DataSourceException { } @Override - public DataSourceModifier[] implicitModifiers() { + public EnumSet implicitModifiers() { return null; } @Override - public DataSourceModifier[] invalidModifiers() { - return new DataSourceModifier[]{DataSourceModifier.HTTP, DataSourceModifier.HTTPS, DataSourceModifier.PRETTYPRINT}; + public EnumSet invalidModifiers() { + return EnumSet.of(DataSourceModifier.HTTP, DataSourceModifier.HTTPS, DataSourceModifier.PRETTYPRINT); } @Override @@ -329,8 +325,8 @@ public String docs() { } @Override - public CHVersion since() { - return CHVersion.V3_0_2; + public MSVersion since() { + return MSVersion.V3_0_2; } @Override @@ -341,7 +337,7 @@ protected void startTransaction0(DaemonManager dm) { @Override protected void stopTransaction0(DaemonManager dm, boolean rollback) throws DataSourceException, IOException { - if(!rollback){ + if(!rollback) { save(dm); } else { data = transactionData; diff --git a/src/main/java/com/laytonsmith/persistence/StringSerializableDataSource.java b/src/main/java/com/laytonsmith/persistence/StringSerializableDataSource.java index fe48d62c51..ee677f4c56 100644 --- a/src/main/java/com/laytonsmith/persistence/StringSerializableDataSource.java +++ b/src/main/java/com/laytonsmith/persistence/StringSerializableDataSource.java @@ -11,32 +11,30 @@ import java.util.logging.Logger; /** - * For data sources that can input and output strings as the complete - * data model, this class should be extended. The data source may not - * be written to file, but it is for sure going to be stored (or at least retrievable) - * from a UTF-8 encoded string. + * For data sources that can input and output strings as the complete data model, this class should be extended. The + * data source may not be written to file, but it is for sure going to be stored (or at least retrievable) from a UTF-8 + * encoded string. * */ public abstract class StringSerializableDataSource extends AbstractDataSource { + /** * A reference to the DataSourceModel used by the set and get methods. */ protected DataSourceModel model; - + /** - * When a transaction starts or stops, this is set to true. If this - * is true, populate will run, then set this to false, and if this is false, - * populate will simply return. + * When a transaction starts or stops, this is set to true. If this is true, populate will run, then set this to + * false, and if this is false, populate will simply return. */ private boolean doPopulate = true; /** - * If in a transaction, and we made a change, we know we need to write it out - * when the transaction finishes. + * If in a transaction, and we made a change, we know we need to write it out when the transaction finishes. */ private boolean hasChanges = false; - - protected StringSerializableDataSource(){ - + + protected StringSerializableDataSource() { + } protected StringSerializableDataSource(URI uri, ConnectionMixinFactory.ConnectionMixinOptions options) throws DataSourceException { @@ -44,8 +42,7 @@ protected StringSerializableDataSource(URI uri, ConnectionMixinFactory.Connectio } /** - * Writes the stringified data to whatever output is associated with - * this data source. + * Writes the stringified data to whatever output is associated with this data source. * * @throws IOException */ @@ -66,25 +63,26 @@ protected final void startTransaction0(DaemonManager dm) { /** * {@inheritDoc} - * - * When we rollback, we simply re-populate the data, instead of tracking - * changes that happened since the transaction started. + * + * When we rollback, we simply re-populate the data, instead of tracking changes that happened since the transaction + * started. + * * @param rollback * @throws DataSourceException - * @throws IOException + * @throws IOException */ @Override protected final void stopTransaction0(DaemonManager dm, boolean rollback) throws DataSourceException, IOException { doPopulate = true; - if(hasChanges){ + if(hasChanges) { hasChanges = false; - if(rollback){ + if(rollback) { populate(); } else { try { writeData(dm, serializeModel()); } catch (ReadOnlyException ex) { - //This shouldn't happen, because we won't have been allowed to set any + //This shouldn't happen, because we won't have been allowed to set any Logger.getLogger(StringSerializableDataSource.class.getName()).log(Level.SEVERE, null, ex); } } @@ -93,8 +91,8 @@ protected final void stopTransaction0(DaemonManager dm, boolean rollback) throws @Override public void populate() throws DataSourceException { - if(inTransaction()){ - if(doPopulate){ + if(inTransaction()) { + if(doPopulate) { doPopulate = false; } else { return; @@ -113,8 +111,8 @@ public void populate() throws DataSourceException { public Set keySet(String[] keyBase) { Set keys = new HashSet<>(); String kb = StringUtils.Join(keyBase, "."); - for(String[] key : model.keySet()){ - if(StringUtils.Join(key, ".").startsWith(kb)){ + for(String[] key : model.keySet()) { + if(StringUtils.Join(key, ".").startsWith(kb)) { keys.add(key); } } @@ -128,15 +126,15 @@ protected final String get0(String[] key) throws DataSourceException { @Override protected final boolean set0(DaemonManager dm, String[] key, String value) throws ReadOnlyException, IOException, DataSourceException { - if (modifiers.contains(DataSourceModifier.READONLY)) { + if(modifiers.contains(DataSourceModifier.READONLY)) { throw new ReadOnlyException(); } String old = get(key); - if ((old == null && value == null) || (old != null && old.equals(value))) { + if((old == null && value == null) || (old != null && old.equals(value))) { return false; } model.set(key, value); - if(!inTransaction()){ + if(!inTransaction()) { //We need to output the model now writeData(dm, serializeModel()); } else { @@ -154,17 +152,15 @@ protected final boolean set0(DaemonManager dm, String[] key, String value) throw protected abstract void populateModel(String data) throws DataSourceException; /** - * Serializes the underlying model to a string, which can be written out - * to disk/network + * Serializes the underlying model to a string, which can be written out to disk/network * * @return */ protected abstract String serializeModel(); /** - * Subclasses that need a certain type of file to be the "blank" version - * of a data model can override this. By default, an empty string is - * returned. + * Subclasses that need a certain type of file to be the "blank" version of a data model can override this. By + * default, an empty string is returned. * * @return */ @@ -178,5 +174,5 @@ public void disconnect() { // By default, we assume that string based data sources don't need disconnecting. // If this assumption is bad, the subclass can override this method. } - + } diff --git a/src/main/java/com/laytonsmith/persistence/ThreadsafeDataSource.java b/src/main/java/com/laytonsmith/persistence/ThreadsafeDataSource.java index c9bdc567d6..c1d792e30f 100644 --- a/src/main/java/com/laytonsmith/persistence/ThreadsafeDataSource.java +++ b/src/main/java/com/laytonsmith/persistence/ThreadsafeDataSource.java @@ -1,4 +1,3 @@ - package com.laytonsmith.persistence; import com.laytonsmith.PureUtilities.DaemonManager; @@ -7,46 +6,48 @@ import com.laytonsmith.persistence.io.ConnectionMixinFactory; import java.io.IOException; import java.net.URI; +import java.util.EnumSet; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; /** - * Wraps a data source, and ensures that it is threadsafe. The static - * accessor method ensures that all methods that access a data source - * all operate on the one, threadsafe data source. This does not absolutely - * ensure that the data source itself is threadsafe, as incompatible code - * will be able to bypass restrictions, but all code that uses this class - * can ensure that amongst those classes, the accesses will be threadsafe. + * Wraps a data source, and ensures that it is threadsafe. The static accessor method ensures that all methods that + * access a data source all operate on the one, threadsafe data source. This does not absolutely ensure that the data + * source itself is threadsafe, as incompatible code will be able to bypass restrictions, but all code that uses this + * class can ensure that amongst those classes, the accesses will be threadsafe. */ -public class ThreadsafeDataSource implements DataSource { - - private static final WeakHashMap, ThreadsafeDataSource> sources = - new WeakHashMap<>(); - +public final class ThreadsafeDataSource implements DataSource { + + private static final WeakHashMap, ThreadsafeDataSource> + SOURCES = new WeakHashMap<>(); + /** - * Returns the threadsafe data source for the given uri and options. If an existing - * reference to a DataSource is currently cached, it is returned, otherwise a new one - * is constructed. + * Returns the threadsafe data source for the given uri and options. If an existing reference to a DataSource is + * currently cached, it is returned, otherwise a new one is constructed. + * * @param uri The URI to be passed to the DataSourceFactory. * @param options The options to be passed to the DataSourceFactory. * @return - * @throws DataSourceException If the underlying call to {@link DataSourceFactory#GetDataSource(java.net.URI, com.laytonsmith.persistence.io.ConnectionMixinFactory.ConnectionMixinOptions)} + * @throws DataSourceException If the underlying call to + * {@link DataSourceFactory#GetDataSource(java.net.URI, com.laytonsmith.persistence.io.ConnectionMixinFactory.ConnectionMixinOptions)} * throws a DataSourceException, it is re-thrown. */ - public static synchronized ThreadsafeDataSource GetDataSource(URI uri, ConnectionMixinFactory.ConnectionMixinOptions options) throws DataSourceException{ + public static synchronized ThreadsafeDataSource GetDataSource(URI uri, ConnectionMixinFactory.ConnectionMixinOptions options) throws DataSourceException { Pair pair = new Pair<>(uri, options); - if(sources.containsKey(pair)){ - return sources.get(pair); + ThreadsafeDataSource source = SOURCES.get(pair); + if(source != null) { + return source; } else { ThreadsafeDataSource ds = new ThreadsafeDataSource(DataSourceFactory.GetDataSource(uri, options)); - sources.put(pair, ds); + SOURCES.put(pair, ds); return ds; } } - + private final DataSource source; - private ThreadsafeDataSource(DataSource source){ + + private ThreadsafeDataSource(DataSource source) { this.source = source; } @@ -91,12 +92,12 @@ public synchronized void addModifier(DataSourceModifier modifier) { } @Override - public synchronized DataSourceModifier[] implicitModifiers() { + public synchronized EnumSet implicitModifiers() { return source.implicitModifiers(); } @Override - public synchronized DataSourceModifier[] invalidModifiers() { + public synchronized EnumSet invalidModifiers() { return source.invalidModifiers(); } @@ -165,5 +166,5 @@ public synchronized int hashCode() { public synchronized String toString() { return source.toString(); } - + } diff --git a/src/main/java/com/laytonsmith/persistence/XMLDataSource.java b/src/main/java/com/laytonsmith/persistence/XMLDataSource.java index 0674263832..019188a66c 100644 --- a/src/main/java/com/laytonsmith/persistence/XMLDataSource.java +++ b/src/main/java/com/laytonsmith/persistence/XMLDataSource.java @@ -1,51 +1,52 @@ - package com.laytonsmith.persistence; -import com.laytonsmith.core.CHVersion; +import com.laytonsmith.core.MSVersion; import com.laytonsmith.persistence.io.ConnectionMixinFactory; import java.net.URI; +import java.util.EnumSet; /** * - * + * */ //@datasource("xml") -public class XMLDataSource extends StringSerializableDataSource{ - private XMLDataSource(){ - +public class XMLDataSource extends StringSerializableDataSource { + + private XMLDataSource() { + } - - public XMLDataSource(URI uri, ConnectionMixinFactory.ConnectionMixinOptions options) throws DataSourceException{ - super(uri, options); - } - @Override - protected void populateModel(String data) throws DataSourceException { - throw new UnsupportedOperationException("Not supported yet."); - } + public XMLDataSource(URI uri, ConnectionMixinFactory.ConnectionMixinOptions options) throws DataSourceException { + super(uri, options); + } + + @Override + protected void populateModel(String data) throws DataSourceException { + throw new UnsupportedOperationException("Not supported yet."); + } - @Override - protected String serializeModel() { - throw new UnsupportedOperationException("Not supported yet."); - } + @Override + protected String serializeModel() { + throw new UnsupportedOperationException("Not supported yet."); + } @Override - public DataSourceModifier[] implicitModifiers() { - return null; - } + public EnumSet implicitModifiers() { + return null; + } @Override - public DataSourceModifier[] invalidModifiers() { - return null; - } + public EnumSet invalidModifiers() { + return null; + } @Override - public String docs() { - return "XML {xml://path/to/xml/file.xml} --"; - } + public String docs() { + return "XML {xml://path/to/xml/file.xml} --"; + } @Override - public CHVersion since() { - return CHVersion.V0_0_0; - } + public MSVersion since() { + return MSVersion.V0_0_0; + } } diff --git a/src/main/java/com/laytonsmith/persistence/YMLDataSource.java b/src/main/java/com/laytonsmith/persistence/YMLDataSource.java index 6b135e08cc..c59ec0610a 100644 --- a/src/main/java/com/laytonsmith/persistence/YMLDataSource.java +++ b/src/main/java/com/laytonsmith/persistence/YMLDataSource.java @@ -1,70 +1,71 @@ package com.laytonsmith.persistence; import com.laytonsmith.annotations.datasource; -import com.laytonsmith.core.CHVersion; +import com.laytonsmith.core.MSVersion; import com.laytonsmith.persistence.io.ConnectionMixinFactory; import java.net.URI; +import java.util.EnumSet; import java.util.Map; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.Yaml; /** * - * + * */ @datasource("yml") -public class YMLDataSource extends StringSerializableDataSource{ - - private YMLDataSource(){ - +public class YMLDataSource extends StringSerializableDataSource { + + private YMLDataSource() { + + } + + public YMLDataSource(URI uri, ConnectionMixinFactory.ConnectionMixinOptions options) throws DataSourceException { + super(uri, options); } - - public YMLDataSource(URI uri, ConnectionMixinFactory.ConnectionMixinOptions options) throws DataSourceException{ - super(uri, options); - } @Override - public DataSourceModifier[] implicitModifiers() { - return null; - } + public EnumSet implicitModifiers() { + return null; + } @Override - public DataSourceModifier[] invalidModifiers() { - return null; - } + public EnumSet invalidModifiers() { + return null; + } @Override - public String docs() { - return "YML {yml:///path/to/yml/file.yml} This type stores data in plain text," - + " in a yml file. Extremely simple to use, it is less scalable than" - + " database driven solutions, and even the Serialized Persistence will" - + " perform better. However, since it is stored in plain text, it is" - + " easy to edit locally, with a plain text editor, or using other tools. "; - } + public String docs() { + return "YML {yml:///path/to/yml/file.yml} This type stores data in plain text," + + " in a yml file. Extremely simple to use, it is less scalable than" + + " database driven solutions, and even the Serialized Persistence will" + + " perform better. However, since it is stored in plain text, it is" + + " easy to edit locally, with a plain text editor, or using other tools. "; + } @Override - public CHVersion since() { - return CHVersion.V3_3_1; - } + public MSVersion since() { + return MSVersion.V3_3_1; + } - @Override - protected void populateModel(String data) throws DataSourceException { - Yaml yaml = new Yaml(); + @Override + protected void populateModel(String data) throws DataSourceException { + Yaml yaml = new Yaml(); try { - model = new DataSourceModel((Map)yaml.load(data)); - } catch(Exception e){ + model = new DataSourceModel((Map) yaml.load(data)); + } catch (Exception e) { throw new DataSourceException("Could not load data source for " + uri + ": " + e.getMessage(), e); } - } + } + + @Override + protected String serializeModel() { + DumperOptions options = new DumperOptions(); + if(hasModifier(DataSourceModifier.PRETTYPRINT)) { + options.setPrettyFlow(true); + } + Yaml yaml = new Yaml(options); + return yaml.dump(model.toMap()); + } - @Override - protected String serializeModel() { - DumperOptions options = new DumperOptions(); - if(hasModifier(DataSourceModifier.PRETTYPRINT)){ - options.setPrettyFlow(true); - } - Yaml yaml = new Yaml(options); - return yaml.dump(model.toMap()); - } - } diff --git a/src/main/java/com/laytonsmith/persistence/io/ConnectionMixin.java b/src/main/java/com/laytonsmith/persistence/io/ConnectionMixin.java index 8afee54141..39a7723888 100644 --- a/src/main/java/com/laytonsmith/persistence/io/ConnectionMixin.java +++ b/src/main/java/com/laytonsmith/persistence/io/ConnectionMixin.java @@ -5,46 +5,45 @@ import java.io.IOException; /** - * A connection mixin is used for string based file type connections to read and write the data. - * Various subclasses will implement the actual connection to the data source. + * A connection mixin is used for string based file type connections to read and write the data. Various subclasses will + * implement the actual connection to the data source. */ public interface ConnectionMixin { + /** - * Gets the full data from this connection. This call is always blocking until the - * read finishes, and the data is returned. Read/write combinations are guaranteed - * to complete in the order they were registered, so if {@link #writeData} isn't - * blocking, any queued calls to writeData will first be resolved before this - * method returns. + * Gets the full data from this connection. This call is always blocking until the read finishes, and the data is + * returned. Read/write combinations are guaranteed to complete in the order they were registered, so if + * {@link #writeData} isn't blocking, any queued calls to writeData will first be resolved before this method + * returns. + * * @return The full data returned from the connection. * @throws IOException If some IOException occurs. */ public String getData() throws IOException; - + /** - * Writes out all the data to this connection. This might be a non-blocking - * call, in which case the operation is queued up, and - * @param dm If the call is non-blocking, this is used to ensure that the thread - * is not marked as daemon. + * Writes out all the data to this connection. This might be a non-blocking call, in which case the operation is + * queued up, and + * + * @param dm If the call is non-blocking, this is used to ensure that the thread is not marked as daemon. * @param data The full data to write out - * @throws ReadOnlyException If the data source is read only. This is not - * to say that the datasource is marked as read only, but that the datasource - * is truly read only, for instance, a zip archive, or a remote file, etc. If - * the datasource is intrinsically read only, not because the configuration for - * this particular datasource makes it read only, then UnsupportedOperationException - * will be thrown instead. In general, that should be taken to mean that this - * is read only anyways. + * @throws ReadOnlyException If the data source is read only. This is not to say that the datasource is marked as + * read only, but that the datasource is truly read only, for instance, a zip archive, or a remote file, etc. If the + * datasource is intrinsically read only, not because the configuration for this particular datasource makes it read + * only, then UnsupportedOperationException will be thrown instead. In general, that should be taken to mean that + * this is read only anyways. * @throws IOException If some IOException occurs. - * @throws UnsupportedOperationException If the datasource is intrinsically - * read only, this may also throw an UnsupportedOperationException in that case. + * @throws UnsupportedOperationException If the datasource is intrinsically read only, this may also throw an + * UnsupportedOperationException in that case. */ public void writeData(DaemonManager dm, String data) throws ReadOnlyException, IOException, UnsupportedOperationException; - + /** - * Some connections just need to get the path information, but don't want the mixin to - * get the data, so in that case, we just need to return our connection information. - * If it is completely unacceptable to return the connection info, an UnsupportedOperationException - * may be thrown. - * @return + * Some connections just need to get the path information, but don't want the mixin to get the data, so in that + * case, we just need to return our connection information. If it is completely unacceptable to return the + * connection info, an UnsupportedOperationException may be thrown. + * + * @return */ public String getPath() throws UnsupportedOperationException, IOException; } diff --git a/src/main/java/com/laytonsmith/persistence/io/ConnectionMixinFactory.java b/src/main/java/com/laytonsmith/persistence/io/ConnectionMixinFactory.java index 1a8ae3f109..386cbd6a86 100644 --- a/src/main/java/com/laytonsmith/persistence/io/ConnectionMixinFactory.java +++ b/src/main/java/com/laytonsmith/persistence/io/ConnectionMixinFactory.java @@ -10,36 +10,39 @@ import java.util.Set; /** - * A ConnectionMixin class dictates how a data source connects to its data. - * This can vary depending on the URI, and so this class grabs the appropriate - * mixin based on the original URI. - * + * A ConnectionMixin class dictates how a data source connects to its data. This can vary depending on the URI, and so + * this class grabs the appropriate mixin based on the original URI. + * */ -public class ConnectionMixinFactory { - private ConnectionMixinFactory(){} - - public static class ConnectionMixinOptions{ +public final class ConnectionMixinFactory { + + private ConnectionMixinFactory() { + } + + public static class ConnectionMixinOptions { + File workingDirectory = null; + /** - * In the case of file based connections, this is the working - * directory, that is, the "." directory used to resolve - * relative paths. - * @param workingDirectory + * In the case of file based connections, this is the working directory, that is, the "." directory used to + * resolve relative paths. + * + * @param workingDirectory */ - public void setWorkingDirectory(File workingDirectory){ + public void setWorkingDirectory(File workingDirectory) { this.workingDirectory = workingDirectory; } @Override public boolean equals(Object obj) { - if (obj == null) { + if(obj == null) { return false; } - if (getClass() != obj.getClass()) { + if(getClass() != obj.getClass()) { return false; } final ConnectionMixinOptions other = (ConnectionMixinOptions) obj; - if (!Objects.equals(this.workingDirectory, other.workingDirectory)) { + if(!Objects.equals(this.workingDirectory, other.workingDirectory)) { return false; } return true; @@ -51,40 +54,36 @@ public int hashCode() { hash = 41 * hash + Objects.hashCode(this.workingDirectory); return hash; } - - - + } - + /** - * A ConnectionMixin class dictates how a data source connects to its data. - * This can vary depending on the URI, and so this class grabs the appropriate - * mixin based on the original URI. It isn't always the case that a connection mixin - * will exist for this URI, if the data source implicitely provides it's own - * connection, it won't need this. This is generally the case for non-string - * based connections. If the data source does provide it's own connection information, - * this should be ignored, because it will probably not return the correct type - * anyways. + * A ConnectionMixin class dictates how a data source connects to its data. This can vary depending on the URI, and + * so this class grabs the appropriate mixin based on the original URI. It isn't always the case that a connection + * mixin will exist for this URI, if the data source implicitly provides it's own connection, it won't need this. + * This is generally the case for non-string based connections. If the data source does provide it's own connection + * information, this should be ignored, because it will probably not return the correct type anyways. + * * @param uri * @param modifiers - * @return + * @return */ - public static ConnectionMixin GetConnectionMixin(URI uri, Set modifiers, ConnectionMixinOptions options, String blankDataModel) throws DataSourceException{ - if(modifiers.contains(DataSource.DataSourceModifier.HTTP) || modifiers.contains(DataSource.DataSourceModifier.HTTPS)){ + public static ConnectionMixin GetConnectionMixin(URI uri, Set modifiers, ConnectionMixinOptions options, String blankDataModel) throws DataSourceException { + if(modifiers.contains(DataSource.DataSourceModifier.HTTP) || modifiers.contains(DataSource.DataSourceModifier.HTTPS)) { try { //This is a WebConnection - return new WebConnection(uri, modifiers.contains(DataSource.DataSourceModifier.HTTP)?false:true); + return new WebConnection(uri, modifiers.contains(DataSource.DataSourceModifier.HTTP) ? false : true); } catch (MalformedURLException ex) { throw new DataSourceException("Malformed URL.", ex); } - } else if(modifiers.contains(DataSource.DataSourceModifier.SSH)){ + } else if(modifiers.contains(DataSource.DataSourceModifier.SSH)) { //This is an SSHConnection return new SSHConnection(uri); } else { //Else it's a file connection, or null, but we will go ahead //and assume it's file. try { - if(modifiers.contains(DataSource.DataSourceModifier.READONLY)){ + if(modifiers.contains(DataSource.DataSourceModifier.READONLY)) { return new ReadOnlyFileConnection(uri, options.workingDirectory, blankDataModel); } else { return new ReadWriteFileConnection(uri, options.workingDirectory, blankDataModel); diff --git a/src/main/java/com/laytonsmith/persistence/io/ReadOnlyFileConnection.java b/src/main/java/com/laytonsmith/persistence/io/ReadOnlyFileConnection.java index 826efba2de..89b3086771 100644 --- a/src/main/java/com/laytonsmith/persistence/io/ReadOnlyFileConnection.java +++ b/src/main/java/com/laytonsmith/persistence/io/ReadOnlyFileConnection.java @@ -7,10 +7,11 @@ /** * - * + * */ -public class ReadOnlyFileConnection extends ReadWriteFileConnection{ - public ReadOnlyFileConnection(URI uri, File workingDirectory, String blankDataModel) throws IOException{ +public class ReadOnlyFileConnection extends ReadWriteFileConnection { + + public ReadOnlyFileConnection(URI uri, File workingDirectory, String blankDataModel) throws IOException { super(uri, workingDirectory, blankDataModel); } diff --git a/src/main/java/com/laytonsmith/persistence/io/ReadWriteFileConnection.java b/src/main/java/com/laytonsmith/persistence/io/ReadWriteFileConnection.java index f6de47a83d..db9921d242 100644 --- a/src/main/java/com/laytonsmith/persistence/io/ReadWriteFileConnection.java +++ b/src/main/java/com/laytonsmith/persistence/io/ReadWriteFileConnection.java @@ -1,6 +1,7 @@ package com.laytonsmith.persistence.io; import com.laytonsmith.PureUtilities.Common.FileUtil; +import com.laytonsmith.PureUtilities.Common.StreamUtils; import com.laytonsmith.PureUtilities.DaemonManager; import com.laytonsmith.PureUtilities.ZipReader; import com.laytonsmith.persistence.ReadOnlyException; @@ -18,7 +19,6 @@ import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; -import org.apache.log4j.lf5.util.StreamUtils; /** * @@ -28,8 +28,8 @@ public class ReadWriteFileConnection implements ConnectionMixin { //Do not change the name of this. It is read reflectively during testing protected final File file; /** - * The encoding that was determined to be the encoding for this file, if - * set, or UTF-8 by default, if the file doesn't exist. + * The encoding that was determined to be the encoding for this file, if set, or UTF-8 by default, if the file + * doesn't exist. */ protected String encoding = "UTF-8"; protected final ZipReader reader; @@ -37,13 +37,11 @@ public class ReadWriteFileConnection implements ConnectionMixin { protected final ExecutorService service; /** - * The executor service allows for reads and writes to be synchronized. - * Writes needn't be synchronous, just merely synchronized with reads and - * other writes. Reads of course need to be synchronous, at least as far as - * the thread that runs the getData function is concerned, but we still need - * to actually run the task on the executor service thread, so it will be - * synced with the writes. All file based persistence systems should use - * this executor to do the reads and writes. + * The executor service allows for reads and writes to be synchronized. Writes needn't be synchronous, just merely + * synchronized with reads and other writes. Reads of course need to be synchronous, at least as far as the thread + * that runs the getData function is concerned, but we still need to actually run the task on the executor service + * thread, so it will be synced with the writes. All file based persistence systems should use this executor to do + * the reads and writes. */ public ReadWriteFileConnection(URI uri, File workingDirectory, String blankDataModel) throws IOException { { @@ -54,49 +52,49 @@ public ReadWriteFileConnection(URI uri, File workingDirectory, String blankDataM //then the rest of the path is the actual file path. If it is absolute, the File constructor will handle //that for us. String path = uri.getSchemeSpecificPart(); - if (!path.startsWith("//")) { + if(!path.startsWith("//")) { throw new IOException("Could not read the URI: " + uri.toString() + ". Did you forget the \"//\"?"); } path = path.substring(2); File temp = new File(path); - if (temp.isAbsolute()) { + if(temp.isAbsolute()) { file = temp; } else { file = new File(workingDirectory, path); } } - if (file.exists()) { + if(file.exists()) { encoding = FileUtil.getFileCharset(file); } reader = new ZipReader(file); - if (!reader.isZipped()) { - if (reader.getTopLevelFile().getParentFile() != null) { + if(!reader.isZipped()) { + if(reader.getTopLevelFile().getParentFile() != null) { reader.getTopLevelFile().getParentFile().mkdirs(); } } - if (!reader.exists()) { + if(!reader.exists()) { reader.getTopLevelFile().createNewFile(); } this.blankDataModel = blankDataModel; - this.service = new ThreadPoolExecutor(1, 1, + this.service = new ThreadPoolExecutor(0, 1, 60L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - Thread t = new Thread(r, "ReadWriteFileConnection-" + file); - t.setPriority(Thread.MIN_PRIORITY); - t.setDaemon(true); - return t; - } - }); + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r, "ReadWriteFileConnection-" + file); + t.setPriority(Thread.MIN_PRIORITY); + t.setDaemon(true); + return t; + } + }); } @Override @SuppressWarnings("ThrowableResultIgnored") public String getData() throws IOException { - if (reader.isZipped()) { + if(reader.isZipped()) { //We have an entirely different method here: it is assumed that //a zip file is an archive; that is, there will be no write operations. //Making this assumption, it is then OK to simply read from it @@ -104,12 +102,12 @@ public String getData() throws IOException { return reader.getFileContents(); } final Future future; - synchronized (service) { + synchronized(service) { future = service.submit(new Callable() { @Override public byte[] call() throws Exception { - return StreamUtils.getBytes(FileUtil.readAsStream(file)); + return StreamUtils.GetBytes(FileUtil.readAsStream(file)); } }); } @@ -118,7 +116,7 @@ public byte[] call() throws Exception { } catch (InterruptedException ex) { throw new RuntimeException(ex); } catch (ExecutionException ex) { - if (ex.getCause() instanceof IOException) { + if(ex.getCause() instanceof IOException) { throw (IOException) ex.getCause(); } else { throw new RuntimeException(ex.getCause()); @@ -128,16 +126,16 @@ public byte[] call() throws Exception { @Override public void writeData(final DaemonManager dm, final String data) throws ReadOnlyException, IOException, UnsupportedOperationException { - if (reader.isZipped()) { + if(reader.isZipped()) { throw new ReadOnlyException("Cannot write to a zipped file."); } - if (file.getParentFile() != null) { + if(file.getParentFile() != null) { file.getParentFile().mkdirs(); } - if (!file.exists()) { + if(!file.exists()) { throw new FileNotFoundException(file.getAbsolutePath() + " does not exist!"); } - synchronized (service) { + synchronized(service) { dm.activateThread(null); service.submit(new Runnable() { diff --git a/src/main/java/com/laytonsmith/persistence/io/SSHConnection.java b/src/main/java/com/laytonsmith/persistence/io/SSHConnection.java index 011cb90f21..0b1ce5ee80 100644 --- a/src/main/java/com/laytonsmith/persistence/io/SSHConnection.java +++ b/src/main/java/com/laytonsmith/persistence/io/SSHConnection.java @@ -8,23 +8,27 @@ /** * - * + * */ -public class SSHConnection implements ConnectionMixin{ - +public class SSHConnection implements ConnectionMixin { + String connection; - public SSHConnection(URI uri){ + + public SSHConnection(URI uri) { connection = uri.getSchemeSpecificPart(); } @Override - public String getData() throws IOException { - return StreamUtils.GetString(SSHWrapper.SCPRead(connection)); + public String getData() throws IOException { + String data = StreamUtils.GetString(SSHWrapper.SCPRead(connection)); + SSHWrapper.closeSessions(); + return data; } @Override public void writeData(DaemonManager dm, String data) throws IOException, UnsupportedOperationException { SSHWrapper.SCPWrite(data, connection); + SSHWrapper.closeSessions(); } @Override diff --git a/src/main/java/com/laytonsmith/persistence/io/WebConnection.java b/src/main/java/com/laytonsmith/persistence/io/WebConnection.java index f9c3698cfc..0883aaeedc 100644 --- a/src/main/java/com/laytonsmith/persistence/io/WebConnection.java +++ b/src/main/java/com/laytonsmith/persistence/io/WebConnection.java @@ -10,16 +10,18 @@ /** * - * + * */ -public class WebConnection implements ConnectionMixin{ +public class WebConnection implements ConnectionMixin { + URL source; - public WebConnection(URI uri, boolean useHTTPS) throws MalformedURLException{ + + public WebConnection(URI uri, boolean useHTTPS) throws MalformedURLException { URI newURI; try { - newURI = new URI("http" + (useHTTPS?"s":"") + "://" + uri.getHost() + uri.getPath() - + (uri.getQuery()==null?"":"?" + uri.getQuery()) - + (uri.getFragment()==null?"":"#" + uri.getFragment())); + newURI = new URI("http" + (useHTTPS ? "s" : "") + "://" + uri.getHost() + uri.getPath() + + (uri.getQuery() == null ? "" : "?" + uri.getQuery()) + + (uri.getFragment() == null ? "" : "#" + uri.getFragment())); } catch (URISyntaxException ex) { //This shouldn't happen, because the URI we received should be correct. If this happens, it's my fault :x throw new Error("Bad URI?"); diff --git a/src/main/java/com/laytonsmith/tools/ExampleLocalPackageInstaller.java b/src/main/java/com/laytonsmith/tools/ExampleLocalPackageInstaller.java index 8a2785a214..2a1adbf34f 100644 --- a/src/main/java/com/laytonsmith/tools/ExampleLocalPackageInstaller.java +++ b/src/main/java/com/laytonsmith/tools/ExampleLocalPackageInstaller.java @@ -1,5 +1,6 @@ package com.laytonsmith.tools; +import com.laytonsmith.PureUtilities.Common.StreamUtils; import com.laytonsmith.PureUtilities.ZipReader; import java.io.File; import java.io.IOException; @@ -10,47 +11,49 @@ /** * - * + * */ public class ExampleLocalPackageInstaller { + private static File jarFolder; - public static void run(File jarFolder, String arg) throws IOException{ + + public static void run(File jarFolder, String arg) throws IOException { ExampleLocalPackageInstaller.jarFolder = jarFolder; - if(arg.isEmpty()){ + if(arg.isEmpty()) { listOptions(); } else { install(arg); } } - - private static void listOptions(){ + + private static void listOptions() { try { ZipReader reader = new ZipReader(new File(ExampleLocalPackageInstaller.class.getResource("/local_packages").getFile())); - for(ZipReader z : reader.zipListFiles()){ - System.out.println(z.getName()); + for(ZipReader z : reader.zipListFiles()) { + StreamUtils.GetSystemOut().println(z.getName()); } } catch (IOException ex) { Logger.getLogger(ExampleLocalPackageInstaller.class.getName()).log(Level.SEVERE, null, ex); } } - - private static void install(String pkg) throws IOException{ + + private static void install(String pkg) throws IOException { URL url = ExampleLocalPackageInstaller.class.getResource("/local_packages/" + pkg); - if(url == null){ - System.out.println("\"" + pkg + "\" is not a valid package name."); + if(url == null) { + StreamUtils.GetSystemOut().println("\"" + pkg + "\" is not a valid package name."); System.exit(1); } ZipReader reader = new ZipReader(url); - File localPackages = new File(jarFolder, "CommandHelper/LocalPackages/" + pkg); - if(localPackages.exists() && localPackages.list().length != 0){ - System.out.println("The LocalPackage " + pkg + " already exists on your system, and is not empty. Are you sure you wish to possibly overwrite files? (Y/N)"); - System.out.print(">"); + File localPackages = new File(jarFolder, "CommandHelper/LocalPackages/" + pkg); + if(localPackages.exists() && localPackages.list().length != 0) { + StreamUtils.GetSystemOut().println("The LocalPackage " + pkg + " already exists on your system, and is not empty. Are you sure you wish to possibly overwrite files? (Y/N)"); + StreamUtils.GetSystemOut().print(">"); String response = new Scanner(System.in).nextLine(); - if(!"Y".equalsIgnoreCase(response)){ + if(!"Y".equalsIgnoreCase(response)) { System.exit(0); } } reader.recursiveCopy(localPackages, true); - System.out.println("Local package installed at " + localPackages); + StreamUtils.GetSystemOut().println("Local package installed at " + localPackages); } } diff --git a/src/main/java/com/laytonsmith/tools/Interpreter.java b/src/main/java/com/laytonsmith/tools/Interpreter.java index bf001c9443..706ba3ef39 100644 --- a/src/main/java/com/laytonsmith/tools/Interpreter.java +++ b/src/main/java/com/laytonsmith/tools/Interpreter.java @@ -1,96 +1,139 @@ package com.laytonsmith.tools; +import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery; +import com.laytonsmith.PureUtilities.CommandExecutor; import com.laytonsmith.PureUtilities.Common.FileUtil; -import com.laytonsmith.PureUtilities.Common.MutableObject; -import com.laytonsmith.PureUtilities.DaemonManager; -import com.laytonsmith.PureUtilities.LimitedQueue; -import com.laytonsmith.PureUtilities.RunnableQueue; +import com.laytonsmith.PureUtilities.Common.FileWriteMode; +import com.laytonsmith.PureUtilities.Common.HTMLUtils; +import com.laytonsmith.PureUtilities.Common.OSUtils; +import com.laytonsmith.PureUtilities.Common.ReflectionUtils; +import com.laytonsmith.PureUtilities.Common.StreamUtils; +import com.laytonsmith.PureUtilities.Common.StringUtils; +import com.laytonsmith.PureUtilities.Common.WinRegistry; import com.laytonsmith.PureUtilities.SignalHandler; import com.laytonsmith.PureUtilities.SignalType; import com.laytonsmith.PureUtilities.Signals; import com.laytonsmith.PureUtilities.TermColors; -import static com.laytonsmith.PureUtilities.TermColors.BLUE; -import static com.laytonsmith.PureUtilities.TermColors.RED; -import static com.laytonsmith.PureUtilities.TermColors.YELLOW; -import static com.laytonsmith.PureUtilities.TermColors.p; -import static com.laytonsmith.PureUtilities.TermColors.pl; -import static com.laytonsmith.PureUtilities.TermColors.reset; import com.laytonsmith.abstraction.AbstractConvertor; import com.laytonsmith.abstraction.Implementation; +import com.laytonsmith.abstraction.MCAttributeModifier; import com.laytonsmith.abstraction.MCColor; -import com.laytonsmith.abstraction.MCEnchantment; import com.laytonsmith.abstraction.MCEntity; import com.laytonsmith.abstraction.MCFireworkBuilder; import com.laytonsmith.abstraction.MCInventory; +import com.laytonsmith.abstraction.MCInventoryHolder; import com.laytonsmith.abstraction.MCItemMeta; import com.laytonsmith.abstraction.MCItemStack; import com.laytonsmith.abstraction.MCLocation; import com.laytonsmith.abstraction.MCMetadataValue; +import com.laytonsmith.abstraction.MCNamespacedKey; import com.laytonsmith.abstraction.MCNote; +import com.laytonsmith.abstraction.MCPattern; import com.laytonsmith.abstraction.MCPlugin; import com.laytonsmith.abstraction.MCPluginMeta; +import com.laytonsmith.abstraction.MCPotionData; import com.laytonsmith.abstraction.MCRecipe; import com.laytonsmith.abstraction.MCServer; import com.laytonsmith.abstraction.MCWorld; import com.laytonsmith.abstraction.blocks.MCMaterial; +import com.laytonsmith.abstraction.enums.MCAttribute; +import com.laytonsmith.abstraction.enums.MCDyeColor; +import com.laytonsmith.abstraction.enums.MCEquipmentSlot; +import com.laytonsmith.abstraction.enums.MCEquipmentSlotGroup; +import com.laytonsmith.abstraction.enums.MCPatternShape; +import com.laytonsmith.abstraction.enums.MCPotionType; import com.laytonsmith.abstraction.enums.MCRecipeType; import com.laytonsmith.abstraction.enums.MCTone; import com.laytonsmith.annotations.api; import com.laytonsmith.annotations.convert; +import com.laytonsmith.annotations.seealso; +import com.laytonsmith.annotations.typeof; import com.laytonsmith.commandhelper.CommandHelperPlugin; +import com.laytonsmith.core.Documentation; import com.laytonsmith.core.Installer; import com.laytonsmith.core.LogLevel; -import com.laytonsmith.core.Main; import com.laytonsmith.core.MethodScriptCompiler; import com.laytonsmith.core.MethodScriptComplete; import com.laytonsmith.core.MethodScriptFileLocations; import com.laytonsmith.core.ParseTree; import com.laytonsmith.core.Prefs; +import com.laytonsmith.core.Profiles; import com.laytonsmith.core.Static; +import com.laytonsmith.core.compiler.TokenStream; +import com.laytonsmith.core.compiler.analysis.Declaration; +import com.laytonsmith.core.compiler.analysis.Namespace; +import com.laytonsmith.core.compiler.analysis.StaticAnalysis; import com.laytonsmith.core.constructs.CArray; +import com.laytonsmith.core.constructs.CBoolean; import com.laytonsmith.core.constructs.CClosure; +import com.laytonsmith.core.constructs.CInt; import com.laytonsmith.core.constructs.CString; +import com.laytonsmith.core.constructs.Construct; import com.laytonsmith.core.constructs.IVariable; import com.laytonsmith.core.constructs.Target; -import com.laytonsmith.core.constructs.Token; import com.laytonsmith.core.constructs.Variable; import com.laytonsmith.core.environments.Environment; import com.laytonsmith.core.environments.GlobalEnv; import com.laytonsmith.core.environments.InvalidEnvironmentException; +import com.laytonsmith.core.environments.RuntimeMode; +import com.laytonsmith.core.environments.StaticRuntimeEnv; import com.laytonsmith.core.events.Driver; import com.laytonsmith.core.events.EventUtils; import com.laytonsmith.core.events.drivers.CmdlineEvents; +import com.laytonsmith.core.exceptions.CRE.CREFormatException; +import com.laytonsmith.core.exceptions.CRE.CREIOException; +import com.laytonsmith.core.exceptions.CRE.CREThrowable; import com.laytonsmith.core.exceptions.CancelCommandException; import com.laytonsmith.core.exceptions.ConfigCompileException; +import com.laytonsmith.core.exceptions.ConfigCompileGroupException; import com.laytonsmith.core.exceptions.ConfigRuntimeException; -import com.laytonsmith.core.exceptions.FunctionReturnException; +import com.laytonsmith.core.functions.Cmdline; +import com.laytonsmith.core.functions.Echoes; +import com.laytonsmith.core.functions.ExampleScript; +import com.laytonsmith.core.functions.Function; import com.laytonsmith.core.functions.FunctionBase; import com.laytonsmith.core.functions.FunctionList; import com.laytonsmith.core.profiler.ProfilePoint; -import com.laytonsmith.core.Profiles; -import com.laytonsmith.core.functions.Exceptions; import com.laytonsmith.persistence.DataSourceException; +import com.laytonsmith.tools.docgen.DocGen; import com.laytonsmith.tools.docgen.DocGenTemplates; +import jline.console.ConsoleReader; +import jline.console.completer.ArgumentCompleter; +import jline.console.completer.StringsCompleter; + import java.io.File; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; +import java.util.EnumSet; +import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.NoSuchElementException; -import java.util.Queue; import java.util.Scanner; import java.util.Set; +import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; -import jline.console.ConsoleReader; -import jline.console.completer.ArgumentCompleter; -import jline.console.completer.StringsCompleter; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static com.laytonsmith.PureUtilities.TermColors.BLUE; +import static com.laytonsmith.PureUtilities.TermColors.RED; +import static com.laytonsmith.PureUtilities.TermColors.YELLOW; +import static com.laytonsmith.PureUtilities.TermColors.p; +import static com.laytonsmith.PureUtilities.TermColors.pl; +import static com.laytonsmith.PureUtilities.TermColors.reset; +import com.laytonsmith.abstraction.entities.MCTransformation; +import com.laytonsmith.core.natives.interfaces.Mixed; +import org.joml.Quaternionf; +import org.joml.Vector3f; /** - * This is a command line implementation of the in game interpreter mode. This - * should only be run while the server is stopped, as it has full access to - * filesystem resources. Many things won't work as intended, but pure abstract + * This is a command line implementation of the in game interpreter mode. This should only be run while the server is + * stopped, as it has full access to filesystem resources. Many things won't work as intended, but pure abstract * functions should still work fine. */ public final class Interpreter { @@ -100,50 +143,70 @@ public final class Interpreter { * * BAD THINGS WILL HAPPEN TO EVERYBODY YOU LOVE IF THIS IS CHANGED! */ - private static final String INTERPRETER_INSTALLATION_LOCATION = "/usr/local/bin/mscript"; + private static final String UNIX_INTERPRETER_INSTALLATION_LOC = "/usr/local/bin/"; - private boolean inTTYMode = false; private boolean multilineMode = false; + private boolean inShellMode = false; private String script = ""; private Environment env; + private StaticAnalysis staticAnalysis; private Thread scriptThread = null; private volatile boolean isExecuting = false; - private final Queue commandHistory = new LimitedQueue<>(MAX_COMMAND_HISTORY); - /** - * If they mash ctrlC a bunch, they probably really want to quit, so we'll - * keep track of this, and reset it only if they then run an actual command. + * If they mash ctrlC a bunch, they probably really want to quit, so we'll keep track of this, and reset it only if + * they then run an actual command. */ private volatile int ctrlCcount = 0; /** - * After this many mashes of Ctrl+C, clearly they want to exit, so we'll - * exit the shell. + * After this many mashes of Ctrl+C, clearly they want to exit, so we'll exit the shell. */ private static final int MAX_CTRL_C_MASHES = 5; - /** - * Max commands that are tracked. - */ - private static final int MAX_COMMAND_HISTORY = 100; + public static void startWithTTY(File file, List args, boolean systemExitOnFailure) throws IOException, DataSourceException, URISyntaxException, Profiles.InvalidProfileException { + startWithTTY(file.getCanonicalPath(), args, systemExitOnFailure); + } + + public static void startWithTTY(String file, List args) throws Profiles.InvalidProfileException, IOException, DataSourceException, URISyntaxException { + startWithTTY(file, args, true); + } - public static void startWithTTY(String file, List args) throws IOException, DataSourceException, URISyntaxException, Profiles.InvalidProfileException { + public static void startWithTTY(String file, List args, boolean systemExitOnFailure) throws IOException, DataSourceException, URISyntaxException, Profiles.InvalidProfileException { File fromFile = new File(file).getCanonicalFile(); Interpreter interpreter = new Interpreter(args, fromFile.getParentFile().getPath(), true); try { interpreter.execute(FileUtil.read(fromFile), args, fromFile); } catch (ConfigCompileException ex) { ConfigRuntimeException.HandleUncaughtException(ex, null, null); - System.out.println(TermColors.reset()); - System.exit(1); + StreamUtils.GetSystemOut().println(TermColors.reset()); + if(systemExitOnFailure) { + System.exit(1); + } + } catch (ConfigCompileGroupException ex) { + ConfigRuntimeException.HandleUncaughtException(ex, null); + StreamUtils.GetSystemOut().println(TermColors.reset()); + if(systemExitOnFailure) { + System.exit(1); + } } } - private String getHelpMsg(){ - String msg = YELLOW + "You are now in cmdline interpreter mode. Use exit() to exit, and >>> to enter" - + " multiline mode."; + private String getHelpMsg() { + String msg = YELLOW + "You are now in cmdline interpreter mode.\n" + + "- on a line by itself (outside of mulitline mode), or the exit() command exits the shell.\n" + + ">>> on a line by itself starts multiline mode, where multiple lines can be written, but not yet executed.\n" + + "<<< on a line by itself ends multiline mode, and executes the buffered script.\n" + + "- on a line by itself while in multiline mode cancels multiline mode, and clears the buffer, without executing the buffered script.\n" + + "If the line starts with $$, then the rest of the line is taken to be a shell command. The command is taken as a string, wrapped\n" + + "in shell_adv(), (where system out and system err are piped to the corresponding outputs).\n" + + "If $$ is on a line by itself, it puts the shell in shell_adv mode, and each line is taken as if it started\n" + + "with $$. Use - on a line by itself to exit this mode as well. Using ~ on a line by itself clears\n" + + "the environment (i.e. unsets all variables and procs, etc).\n\n" + + "For more information about a specific function, type \"help function\"\n" + + "and for documentation plus examples, type \"examples function\". See the api tool\n" + + "for more information about this feature."; try { msg += "\nYour current working directory is: " + env.getEnv(GlobalEnv.class).GetRootFolder().getCanonicalPath(); } catch (IOException ex) { @@ -153,14 +216,15 @@ private String getHelpMsg(){ } /** - * Creates a new Interpreter object. This object can then be manipulated via - * the cmdline interactively, or standalone, via the execute method. + * Creates a new Interpreter object. This object can then be manipulated via the cmdline interactively, or + * standalone, via the execute method. * * @param args Any arguments passed in to the script. They are set as $vars * @param cwd The initial working directory. * @throws IOException * @throws DataSourceException * @throws URISyntaxException + * @throws Profiles.InvalidProfileException */ public Interpreter(List args, String cwd) throws IOException, DataSourceException, URISyntaxException, Profiles.InvalidProfileException { this(args, cwd, false); @@ -169,40 +233,46 @@ public Interpreter(List args, String cwd) throws IOException, DataSource private Interpreter(List args, String cwd, boolean inTTYMode) throws IOException, DataSourceException, URISyntaxException, Profiles.InvalidProfileException { doStartup(); env.getEnv(GlobalEnv.class).SetRootFolder(new File(cwd)); - if(inTTYMode){ + if(inTTYMode) { //Ok, done. They'll have to execute from here. return; } //We have two modes here, piped input, or interactive console. - if (System.console() == null) { + if(System.console() == null) { Scanner scanner = new Scanner(System.in); //We need to read in everything, it's basically in multiline mode - StringBuilder script = new StringBuilder(); + StringBuilder s = new StringBuilder(); String line; try { - while ((line = scanner.nextLine()) != null) { - script.append(line).append("\n"); + while((line = scanner.nextLine()) != null) { + s.append(line).append("\n"); } } catch (NoSuchElementException e) { //Done } try { - execute(script.toString(), args); - System.out.print(TermColors.reset()); + execute(s.toString(), args); + StreamUtils.GetSystemOut().print(TermColors.reset()); System.exit(0); } catch (ConfigCompileException ex) { ConfigRuntimeException.HandleUncaughtException(ex, null, null); - System.out.print(TermColors.reset()); + StreamUtils.GetSystemOut().print(TermColors.reset()); + System.exit(1); + } catch (ConfigCompileGroupException ex) { + ConfigRuntimeException.HandleUncaughtException(ex, null); + StreamUtils.GetSystemOut().println(TermColors.reset()); System.exit(1); } } else { final ConsoleReader reader = new ConsoleReader(); + reader.setExpandEvents(false); //Get a list of all the function names. This will be provided to the auto completer. - Set functions = FunctionList.getFunctionList(api.Platforms.INTERPRETER_JAVA); + Set functions = FunctionList.getFunctionList(api.Platforms.INTERPRETER_JAVA, + env.getEnvClasses()); List names = new ArrayList<>(); - for(FunctionBase f : functions){ - if(f.appearInDocumentation()){ + for(FunctionBase f : functions) { + if(f.appearInDocumentation()) { names.add(f.getName()); } } @@ -213,202 +283,46 @@ public boolean isDelimiterChar(CharSequence buffer, int pos) { char c = buffer.charAt(pos); return !Character.isLetter(c) && c != '_'; } - }, new StringsCompleter(names){ + }, new StringsCompleter(names) { @Override public int complete(String buffer, int cursor, List candidates) { //The autocomplete can be improved a bit, instead of putting a space after it, //let's put a parenthesis. int ret = super.complete(buffer, cursor, candidates); - if(candidates.size() == 1){ + if(candidates.size() == 1) { String functionName = candidates.get(0).toString().trim(); - candidates.set(0, functionName + "()"); + candidates.set(0, functionName + "()"); } return ret; } })); - while(true){ + while(true) { String prompt; - if(multilineMode){ + if(multilineMode) { prompt = TermColors.WHITE + ">" + reset(); } else { prompt = getPrompt(); } String line = reader.readLine(prompt); - if(!textLine(line)){ + if(line == null || !textLine(line)) { break; } } - - //Perhaps this code will be revisited in the future, so that more things - //can be done, like syntax highlighting, function keys, etc, but in order + //TODO: Add syntax highlighting, function keys, etc, but in order //to do that, history, command completion, etc, will all have to be re-implemented, //and implemented around readCharacter, which is a lot of work. -// p(getPrompt()); -// boolean exit = false; -// while(true){ -// jline.console.ConsoleReader reader = new jline.console.ConsoleReader(); -// StringBuilder line = new StringBuilder(); -// while(true){ -// int c = reader.readCharacter(); -// if(c == 27){ -// //Escape sequence -// int c2 = reader.readCharacter(); -// if(c2 == 79){ -// //F1-F4 -// int c3 = reader.readCharacter(); -// if(c3 == 80){ -// //F1 -// System.out.println("F1"); -// continue; -// } else if(c3 == 81){ -// //F2 -// System.out.println("F2"); -// continue; -// } else if(c3 == 82){ -// //F3 -// System.out.println("F3"); -// continue; -// } else if(c3 == 83){ -// //F4 -// System.out.println("F4"); -// continue; -// } -// } else if(c2 == 91){ -// //At least 3 characters -// int c3 = reader.readCharacter(); -// if(c3 == 68){ -// //Left arrow -// System.out.println("Left Arrow"); -// continue; -// } else if(c3 == 65){ -// //Up Arrow -// System.out.println("Up Arrow"); -// continue; -// } else if(c3 == 66){ -// //Down Arrow -// System.out.println("Down Arrow"); -// continue; -// } else if(c3 == 67){ -// //Right Arrow -// System.out.println("Right Arrow"); -// continue; -// } else if(c3 == 72){ -// //Home -// System.out.println("Home"); -// continue; -// } else if(c3 == 70){ -// //End -// System.out.println("End"); -// continue; -// } else { -// //At least 4 characters -// int c4 = reader.readCharacter(); -// if(c4 == 126){ -// if(c3 == 50){ -// //Insert -// System.out.println("Insert"); -// continue; -// } else if(c3 == 51){ -// //Delete -// System.out.println("Delete"); -// continue; -// } else if(c3 == 53){ -// //Page Up -// System.out.println("Page Up"); -// continue; -// } else if(c3 == 54){ -// //Page Down -// System.out.println("Page Down"); -// continue; -// } -// } else { -// //At least 5 characters -// int c5 = reader.readCharacter(); -// if(c5 == 126){ -// if(c3 == 49){ -// if(c4 == 53){ -// //F5 -// System.out.println("F5"); -// continue; -// } else if(c4 == 55){ -// //F6 -// System.out.println("F6"); -// continue; -// } else if(c4 == 56){ -// //F7 -// System.out.println("F7"); -// continue; -// } else if(c4 == 57){ -// //F8 -// System.out.println("F8"); -// continue; -// } -// } else if(c3 == 50){ -// if(c4 == 48){ -// //F9 -// System.out.println("F9"); -// continue; -// } else if(c4 == 49){ -// //F10 -// System.out.println("F10"); -// continue; -// } else if(c4 == 51){ -// //F11 -// System.out.println("F11"); -// continue; -// } else if(c4 == 52){ -// //F12 -// System.out.println("F12"); -// continue; -// } -// } else { -// //Unknown -// continue; -// } -// } else { -// //Unknown. This hopefully won't ever happen. -// continue; -// } -// } -// } -// } else { -// continue; //Unrecognized. Hopefully this will be fine? -// } -// } -// if(c == 13){ //"Enter" character -// //done, send the line in for processing -// System.out.println(); -// break; -// } -// if(c == 127){ -// reader.moveCursor(-1); -// } -// line.append((char)c); -// reader.putString(Character.toString((char)c)); -// } -// if(!textLine(line.toString())){ -// exit = true; -// } -// if(multilineMode){ -// p(">"); -// } else { -// p(getPrompt()); -// } -// } } } - private String getPrompt(){ + private String getPrompt() { CClosure c = (CClosure) env.getEnv(GlobalEnv.class).GetCustom("cmdline_prompt"); - if(c != null){ + if(c != null) { try { - c.execute(); - } catch(FunctionReturnException ex){ - String val = ex.getReturn().val(); + String val = c.executeCallable(CBoolean.get(inShellMode)).val(); return Static.MCToANSIColors(val) + TermColors.RESET; - } catch(ConfigRuntimeException ex){ + } catch (ConfigRuntimeException ex) { ConfigRuntimeException.HandleUncaughtException(ex, env); } } @@ -420,39 +334,42 @@ private void doStartup() throws IOException, DataSourceException, URISyntaxExcep Installer.Install(MethodScriptFileLocations.getDefault().getConfigDirectory()); Installer.InstallCmdlineInterpreter(); - env = Static.GenerateStandaloneEnvironment(); - env.getEnv(GlobalEnv.class).SetCustom("cmdline", true); - if (Prefs.UseColors()) { + env = Static.GenerateStandaloneEnvironment(false, EnumSet.of(RuntimeMode.CMDLINE, RuntimeMode.INTERPRETER)); + staticAnalysis = new StaticAnalysis(true); + if(Prefs.UseColors()) { TermColors.EnableColors(); } else { TermColors.DisableColors(); } - String auto_include = FileUtil.read(MethodScriptFileLocations.getDefault().getCmdlineInterpreterAutoIncludeFile()); + String autoInclude = FileUtil.read(MethodScriptFileLocations.getDefault().getCmdlineInterpreterAutoIncludeFile()); try { - MethodScriptCompiler.execute(auto_include, MethodScriptFileLocations.getDefault().getCmdlineInterpreterAutoIncludeFile(), true, env, null, null, null); + MethodScriptCompiler.execute(autoInclude, MethodScriptFileLocations.getDefault() + .getCmdlineInterpreterAutoIncludeFile(), true, env, env.getEnvClasses(), null, null, null); } catch (ConfigCompileException ex) { ConfigRuntimeException.HandleUncaughtException(ex, "Interpreter will continue to run, however.", null); + } catch (ConfigCompileGroupException ex) { + ConfigRuntimeException.HandleUncaughtException(ex, null); } //Install our signal handlers. SignalHandler.SignalCallback signalHandler = new SignalHandler.SignalCallback() { @Override public boolean handle(SignalType type) { - if(isExecuting){ + if(isExecuting) { env.getEnv(GlobalEnv.class).SetInterrupt(true); - if(scriptThread != null){ + if(scriptThread != null) { scriptThread.interrupt(); } - for(Thread t : env.getEnv(GlobalEnv.class).GetDaemonManager().getActiveThreads()){ + for(Thread t : env.getEnv(StaticRuntimeEnv.class).GetDaemonManager().getActiveThreads()) { t.interrupt(); } } else { ctrlCcount++; - if(ctrlCcount > MAX_CTRL_C_MASHES){ + if(ctrlCcount > MAX_CTRL_C_MASHES) { //Ok, ok, we get the hint. - System.out.println(); - System.out.flush(); + StreamUtils.GetSystemOut().println(); + StreamUtils.GetSystemOut().flush(); System.exit(130); //Standard Ctrl+C exit code } pl(YELLOW + "\nUse exit() to exit the shell." + reset()); @@ -463,35 +380,44 @@ public boolean handle(SignalType type) { }; try { SignalHandler.addHandler(Signals.SIGTERM, signalHandler); - } catch(IllegalArgumentException ex){ + } catch (IllegalArgumentException ex) { // Oh well. } try { SignalHandler.addHandler(Signals.SIGINT, signalHandler); - } catch (IllegalArgumentException ex){ + } catch (IllegalArgumentException ex) { // Oh well again. } } /** * This evaluates each line of text + * * @param line * @return * @throws IOException */ private boolean textLine(String line) throws IOException { - switch (line) { + switch(line) { case "-": //Exit interpreter mode - return false; + if(multilineMode) { + script = ""; + } else if(inShellMode) { + inShellMode = false; + } else { + return false; + } + break; case ">>>": //Start multiline mode - if (multilineMode) { + if(multilineMode) { pl(RED + "You are already in multiline mode!"); } else { multilineMode = true; pl(YELLOW + "You are now in multiline mode. Type <<< on a line by itself to execute."); - } break; + } + break; case "<<<": //Execute multiline multilineMode = false; @@ -500,73 +426,375 @@ private boolean textLine(String line) throws IOException { script = ""; } catch (ConfigCompileException e) { ConfigRuntimeException.HandleUncaughtException(e, null, null); - } break; - default: - if (multilineMode) { - //Queue multiline - script = script + line + "\n"; - } else { - try { - //Execute single line - execute(line, null); - } catch (ConfigCompileException ex) { - ConfigRuntimeException.HandleUncaughtException(ex, null, null); + } catch (ConfigCompileGroupException e) { + ConfigRuntimeException.HandleUncaughtException(e, null); + } + break; + case "$$": + inShellMode = true; + break; + default: { + Pattern p = Pattern.compile("(help|examples) (.*)"); + Matcher m; + if((m = p.matcher(line)).find()) { + String helpCommand = m.group(2); + try { + List fl = new ArrayList<>(); + for(FunctionBase fb : FunctionList.getFunctionList(api.Platforms.INTERPRETER_JAVA, + env.getEnvClasses())) { + if(fb.getName().matches("^" + helpCommand + "$")) { + fl.add(fb); + } + } + if(fl.isEmpty()) { + StreamUtils.GetSystemErr().println("Could not find function of name " + helpCommand); + } else if(fl.size() == 1) { + StreamUtils.GetSystemOut().println(formatDocsForCmdline(helpCommand, + m.group(1).equals("examples"))); + } else { + StreamUtils.GetSystemOut().println("Multiple function matches found:"); + for(FunctionBase fb : fl) { + StreamUtils.GetSystemOut().println(fb.getName()); + } + } + } catch (IOException | DataSourceException | URISyntaxException + | DocGenTemplates.Generator.GenerateException | ConfigCompileException e) { + e.printStackTrace(StreamUtils.GetSystemErr()); + } + break; } - } break; + if(multilineMode) { + //Queue multiline + script = script + line + "\n"; + } else { + try { + //Execute single line + execute(line, null); + } catch (ConfigCompileException ex) { + ConfigRuntimeException.HandleUncaughtException(ex, null, null); + } catch (ConfigCompileGroupException ex) { + ConfigRuntimeException.HandleUncaughtException(ex, null); + } + } + break; + } } return true; } + /** + * Given a function name, returns a string that is suitable for printing to the command line. This mechanism + * is standardized, so that the display of this information is standardized across different methods. The returned + * string will contain usages of {@link TermColors}. + * @param function + * @param showExamples + * @return + * @throws ConfigCompileException + * @throws IOException + * @throws DataSourceException + * @throws URISyntaxException + * @throws DocGenTemplates.Generator.GenerateException + */ + public static String formatDocsForCmdline(String function, boolean showExamples) throws ConfigCompileException, + IOException, DataSourceException, URISyntaxException, DocGenTemplates.Generator.GenerateException { + StringBuilder b = new StringBuilder(); + FunctionBase f = FunctionList.getFunction(function, null, Target.UNKNOWN); + DocGen.DocInfo d = new DocGen.DocInfo(f.docs()); + b.append(TermColors.CYAN).append(d.ret).append(" "); + b.append(TermColors.RESET).append(f.getName()).append("(") + .append(TermColors.MAGENTA).append(d.originalArgs).append(TermColors.RESET).append(")\n"); + if(f instanceof Function function1) { + Class[] thrown = function1.thrown(); + if(thrown != null && thrown.length > 0) { + b.append("Throws: "); + Set th = new HashSet<>(); + for(Class c : thrown) { + if(ClassDiscovery.GetClassAnnotation(c, typeof.class) != null) { + typeof t = ClassDiscovery.GetClassAnnotation(c, typeof.class); + th.add(t.value()); + } + } + b.append(TermColors.RED).append(StringUtils.Join(th, ", ")).append(TermColors.RESET).append("\n"); + } + } + b.append("\n"); + { + String desc = reverseHTML(d.desc); + b.append(TermColors.WHITE).append(desc).append("\n"); + } + if(d.extendedDesc != null) { + String desc = reverseHTML(d.extendedDesc); + b.append(TermColors.WHITE).append(desc).append("\n"); + } + if(f instanceof Function function1) { + if(f.getClass().getAnnotation(seealso.class) != null) { + List seeAlso = new ArrayList<>(); + for(Class c : function1.seeAlso()) { + Object i = ReflectionUtils.newInstance(c); + if(i instanceof Documentation seeAlsoDocumentation) { + String color = TermColors.YELLOW; + if(i instanceof Function) { + if(function1.isRestricted()) { + color = TermColors.CYAN; + } else { + color = TermColors.GREEN; + } + } + seeAlso.add(color + seeAlsoDocumentation.getName() + TermColors.RESET); + } + // TODO: also support Templates at some point, though this method will have to also be able + // to support the display of them, which it currently is unable to do. + } + if(!seeAlso.isEmpty()) { + b.append("See also: "); + b.append(StringUtils.Join(seeAlso, ", ")).append("\n"); + } + } + } + if(f instanceof Function && showExamples) { + ExampleScript[] examples = ((Function) f).examples(); + if(examples != null && examples.length > 0) { + b.append(TermColors.BOLD).append("\nExamples").append(TermColors.RESET).append("\n"); + b.append("----------------------------------------------\n\n"); + for(int i = 0; i < examples.length; i++) { + b.append(TermColors.BRIGHT_WHITE).append(TermColors.BOLD).append(TermColors.UNDERLINE) + .append("Example ").append(i + 1).append(TermColors.RESET).append("\n"); + if(i > 0) { + b.append("\n\n"); + } + ExampleScript e = examples[i]; + b.append(e.getDescription()).append("\n\n"); + b.append(TermColors.UNDERLINE).append("Code").append(TermColors.RESET).append("\n") + .append(reverseHTML(DocGenTemplates.CODE.generate(e.getScript()))).append("\n\n"); + b.append(TermColors.UNDERLINE).append("Output").append(TermColors.RESET).append("\n") + .append(e.getOutput()).append("\n\n"); + } + } + } + b.append(TermColors.RESET).append("\n"); + return b.toString(); + } + + public static String reverseHTML(String input) { + input = input + .replaceAll("\\", "\n") + .replaceAll("", "\n") + .replaceAll("\\<.*?>", "") + .replaceAll("(?s)\\", ""); + input = HTMLUtils.unescapeHTML(input); + input = input.replaceAll("\\{\\{keyword\\|(.*?)\\}\\}", TermColors.BLUE + "$1" + TermColors.RESET); + input = input.replaceAll("\\{\\{object\\|(.*?)\\}\\}", TermColors.BRIGHT_BLUE + "$1" + TermColors.RESET); + input = input.replaceAll("\\\\\n", "\n"); + input = input.replaceAll("(?s)\\{\\{Warning\\|text=(.*?)\\}\\}", TermColors.RED + "$1" + TermColors.RESET); + while(true) { + Matcher functionMatcher = Pattern.compile("\\{\\{function\\|(.*?)\\}\\}").matcher(input); + if(functionMatcher.find()) { + String function = functionMatcher.group(1); + String color; + try { + FunctionBase f = FunctionList.getFunction(function, null, Target.UNKNOWN); + if(f instanceof Function function1) { + if(function1.isRestricted()) { + color = TermColors.CYAN; + } else { + color = TermColors.GREEN; + } + } else { + color = TermColors.YELLOW; + } + } catch (ConfigCompileException ex) { + color = TermColors.YELLOW; + } + input = input.replaceAll("\\{\\{function\\|" + function + "\\}\\}", color + function + + TermColors.RESET); + } else { + break; + } + } + { + StringBuilder b = new StringBuilder(); + StringBuilder headerLine = new StringBuilder(); + boolean inTable = false; + boolean inTableHeader = false; + boolean inTableHeaderField = false; + for(int i = 0; i < input.length(); i++) { + char c = input.charAt(i); + char c2 = '\0'; + char c3 = '\0'; + if(i < input.length() - 1) { + c2 = input.charAt(i + 1); + } + if(i < input.length() - 2) { + c3 = input.charAt(i + 2); + } + if(c == '{' && c2 == '|') { + inTable = true; + inTableHeader = true; + b.append("\n"); + i++; + continue; + } + if(c == '|' && c2 == '}') { + inTable = false; + i++; + b.append('\n'); + continue; + } + if(inTable) { + if(inTableHeader) { + if(c == '|' && c2 == '-') { + b.append("\n"); + i++; + continue; + } + if(c == '\n') { + inTableHeader = false; + } + continue; + } + if(inTableHeaderField) { + if(c == '|') { + inTableHeaderField = false; + b.append(TermColors.RESET).append("\n|").append(TermColors.MAGENTA); + } else if(c == '\n') { + b.append(TermColors.RESET).append("| ").append(TermColors.MAGENTA) + .append(headerLine.toString()).append(TermColors.RESET) + .append("\n"); + if(c2 != '!') { + inTableHeaderField = false; + } else { + headerLine = new StringBuilder(); + i++; + } + continue; + } + headerLine.append(c); + continue; + } + if(c == '\n' && c2 == '!') { + headerLine = new StringBuilder(); + inTableHeaderField = true; + i++; + continue; + } + if((c == '\n' && c2 == '|' && c3 == '-') || (c == '|' && c2 == '-')) { + b.append(TermColors.RESET).append("\n").append(StringUtils.stringMultiply(80, "-")); + if(c == '\n') { + i += 2; + } else { + i++; + b.append("\n"); + } + continue; + } + } + b.append(c); + } + input = b.toString() + "\n"; + } + return input; + } + /** * This executes a script + * * @param script * @param args * @throws ConfigCompileException * @throws IOException + * @throws ConfigCompileGroupException */ - public void execute(String script, List args) throws ConfigCompileException, IOException { + public void execute(String script, List args) throws ConfigCompileException, IOException, ConfigCompileGroupException { execute(script, args, null); } /** - * This executes an entire script. The cmdline_prompt_event is first triggered (if used) and - * if the event is cancelled, nothing happens. + * This executes an entire script. The cmdline_prompt_event is first triggered (if used) and if the event is + * cancelled, nothing happens. + * * @param script * @param args * @param fromFile * @throws ConfigCompileException * @throws IOException + * @throws ConfigCompileGroupException */ - public void execute(String script, List args, File fromFile) throws ConfigCompileException, IOException { - CmdlineEvents.cmdline_prompt_input.CmdlinePromptInput input = new CmdlineEvents.cmdline_prompt_input.CmdlinePromptInput(script); + public void execute(String script, List args, File fromFile) throws ConfigCompileException, IOException, ConfigCompileGroupException { + CmdlineEvents.cmdline_prompt_input.CmdlinePromptInput input = new CmdlineEvents.cmdline_prompt_input.CmdlinePromptInput(script, inShellMode); EventUtils.TriggerListener(Driver.CMDLINE_PROMPT_INPUT, "cmdline_prompt_input", input); - if(input.isCancelled()){ + if(input.isCancelled()) { return; } ctrlCcount = 0; - if("exit".equals(script)){ + if("exit".equals(script)) { + if(inShellMode) { + inShellMode = false; + return; + } pl(YELLOW + "Use exit() if you wish to exit."); return; } - if("help".equals(script)){ + if("help".equals(script)) { pl(getHelpMsg()); return; } - if (fromFile == null) { + if("~".equals(script)) { + pl("Clearing environment."); + env.getEnv(GlobalEnv.class).GetProcs().clear(); + env.getEnv(GlobalEnv.class).GetVarList().clear(); + for(Thread t : env.getEnv(StaticRuntimeEnv.class).GetDaemonManager().getActiveThreads()) { + t.interrupt(); + } + env.getEnv(StaticRuntimeEnv.class).getExecutionQueue().stopAll(); + env.getEnv(StaticRuntimeEnv.class).getIncludeCache().clear(); + staticAnalysis = new StaticAnalysis(true); + return; + } + if(fromFile == null) { fromFile = new File("Interpreter"); } + boolean localShellMode = false; + if(!inShellMode && script.startsWith("$$")) { + localShellMode = true; + script = script.substring(2); + } + + if(inShellMode || localShellMode) { + // Wrap this in shell_adv + if(doBuiltin(script)) { + return; + } + List shellArgs = StringUtils.ArgParser(script); + List escapedArgs = new ArrayList<>(); + for(String arg : shellArgs) { + escapedArgs.add(new CString(arg, Target.UNKNOWN).getQuote()); + } + script = "shell_adv(" + + "array(" + + StringUtils.Join(escapedArgs, ",") + + ")," + + "array(" + + "'stdout':closure(@l){sys_out(@l);}," + + "'stderr':closure(@l){sys_err(@l);})" + + ");"; + } isExecuting = true; - ProfilePoint compile = env.getEnv(GlobalEnv.class).GetProfiler().start("Compilation", LogLevel.VERBOSE); + if(args != null) { + staticAnalysis.getStartScope().addDeclaration( + new Declaration(Namespace.IVARIABLE, "@arguments", CArray.TYPE, null, Target.UNKNOWN)); + } + ProfilePoint compile = env.getEnv(StaticRuntimeEnv.class).GetProfiler().start("Compilation", LogLevel.VERBOSE); final ParseTree tree; try { - List stream = MethodScriptCompiler.lex(script, fromFile, true); - tree = MethodScriptCompiler.compile(stream); + TokenStream stream = MethodScriptCompiler.lex(script, env, fromFile, true); + tree = MethodScriptCompiler.compile(stream, env, env.getEnvClasses(), staticAnalysis); + staticAnalysis = new StaticAnalysis(staticAnalysis.getEndScope(), true); // Continue analysis in end scope. } finally { compile.stop(); } //Environment env = Environment.createEnvironment(this.env.getEnv(GlobalEnv.class)); final List vars = new ArrayList<>(); - if (args != null) { + if(args != null) { //Build the @arguments variable, the $ vars, and $ itself. Note that //we have special handling for $0, that is the script name, like bash. //However, it doesn't get added to either $ or @arguments, due to the @@ -580,9 +808,9 @@ public void execute(String script, List args, File fromFile) throws Conf v.setDefault(fromFile.toString()); vars.add(v); } - for (int i = 0; i < args.size(); i++) { + for(int i = 0; i < args.size(); i++) { String arg = args.get(i); - if (i > 1) { + if(i > 0) { finalArgument.append(" "); } Variable v = new Variable("$" + Integer.toString(i + 1), "", Target.UNKNOWN); @@ -590,58 +818,71 @@ public void execute(String script, List args, File fromFile) throws Conf v.setDefault(arg); vars.add(v); finalArgument.append(arg); - arguments.push(new CString(arg, Target.UNKNOWN)); + arguments.push(new CString(arg, Target.UNKNOWN), Target.UNKNOWN); } Variable v = new Variable("$", "", false, true, Target.UNKNOWN); v.setVal(new CString(finalArgument.toString(), Target.UNKNOWN)); v.setDefault(finalArgument.toString()); vars.add(v); - env.getEnv(GlobalEnv.class).GetVarList().set(new IVariable("@arguments", arguments, Target.UNKNOWN)); + env.getEnv(GlobalEnv.class).GetVarList().set(new IVariable(CArray.TYPE, "@arguments", arguments, + Target.UNKNOWN)); } try { - ProfilePoint p = this.env.getEnv(GlobalEnv.class).GetProfiler().start("Interpreter Script", LogLevel.ERROR); + ProfilePoint p = this.env.getEnv(StaticRuntimeEnv.class) + .GetProfiler().start("Interpreter Script", LogLevel.ERROR); try { - final MutableObject wasThrown = new MutableObject<>(); scriptThread = new Thread(new Runnable() { @Override public void run() { try { + if(tree != null && tree.getChildren().size() == 1 + && tree.getChildAt(0).getData() instanceof IVariable ivar) { + Mixed i = env.getEnv(GlobalEnv.class).GetVarList() + .get(ivar.getVariableName(), ivar.getTarget(), env).ival(); + StreamUtils.GetSystemOut().println(i.val()); + return; + } MethodScriptCompiler.execute(tree, env, new MethodScriptComplete() { @Override public void done(String output) { - if(System.console() != null && !"".equals(output.trim())){ - System.out.println(output); + if(System.console() != null && !"".equals(output.trim())) { + StreamUtils.GetSystemOut().println(output); } } }, null, vars); - } catch (CancelCommandException e) { - //Nothing, though we could have been Ctrl+C cancelled, so we need to reset - //the interrupt flag. But we do that unconditionally below, in the finally, - //in the other thread. + env.getEnv(StaticRuntimeEnv.class).GetDaemonManager().waitForThreads(); + } catch (CancelCommandException | InterruptedException e) { + // Nothing, though we could have been Ctrl+C cancelled, so we need to reset + // the interrupt flag. But we do that unconditionally below, in the finally, + // in the other thread. + // However, interrupt all the underlying threads + for(Thread t : env.getEnv(StaticRuntimeEnv.class).GetDaemonManager().getActiveThreads()) { + t.interrupt(); + } } catch (ConfigRuntimeException e) { ConfigRuntimeException.HandleUncaughtException(e, env); //No need for the full stack trace - if (System.console() == null) { + if(System.console() == null) { System.exit(1); } } catch (NoClassDefFoundError e) { - System.err.println(RED + Main.getNoClassDefFoundErrorMessage(e) + reset()); - System.err.println("Since you're running from standalone interpreter mode, this is not a fatal error, but one of the functions you just used required" + StreamUtils.GetSystemErr().println(RED + Static.getNoClassDefFoundErrorMessage(e) + reset()); + StreamUtils.GetSystemErr().println("Since you're running from standalone interpreter mode, this is not a fatal error, but one of the functions you just used required" + " an actual backing engine that isn't currently loaded. (It still might fail even if you load the engine though.) You simply won't be" + " able to use that function here."); - if (System.console() == null) { + if(System.console() == null) { System.exit(1); } } catch (InvalidEnvironmentException ex) { - System.err.println(RED + ex.getMessage() + " " + ex.getData() + "() cannot be used in this context."); - if (System.console() == null) { + StreamUtils.GetSystemErr().println(RED + ex.getMessage() + " " + ex.getData() + "() cannot be used in this context."); + if(System.console() == null) { System.exit(1); } } catch (RuntimeException e) { pl(RED + e.toString()); - e.printStackTrace(System.err); - if (System.console() == null) { + e.printStackTrace(StreamUtils.GetSystemErr()); + if(System.console() == null) { System.exit(1); } } @@ -653,11 +894,6 @@ public void done(String output) { } catch (InterruptedException ex) { // } - try { - env.getEnv(GlobalEnv.class).GetDaemonManager().waitForThreads(); - } catch (InterruptedException ex) { - // - } } finally { p.stop(); } @@ -667,69 +903,241 @@ public void done(String output) { } } - public static void install() { - if (TermColors.SYSTEM == TermColors.SYS.UNIX) { - try { - URL jar = Interpreter.class.getProtectionDomain().getCodeSource().getLocation(); - File exe = new File(INTERPRETER_INSTALLATION_LOCATION); - String bashScript = Static.GetStringResource("/interpreter-helpers/bash.sh"); + /** + * Works like {@link #execute(String, List, File)} but reads the file in for you. + * + * @param script Path the the file + * @param args Arguments to be passed to the script + * @throws ConfigCompileException If there is a compile error in the script + * @throws IOException + * @throws ConfigCompileGroupException + */ + public void execute(File script, List args) throws ConfigCompileException, IOException, ConfigCompileGroupException { + String scriptString = FileUtil.read(script); + execute(scriptString, args, script); + } + + public boolean doBuiltin(String script) { + List args = StringUtils.ArgParser(script); + if(!args.isEmpty()) { + String command = args.get(0); + args.remove(0); + command = command.toLowerCase(Locale.ENGLISH); + switch(command) { + case "help": + pl(getHelpMsg()); + pl("Shell builtins:"); + pl("cd - Runs cd() with the provided argument."); + pl("s - equivalent to cd('..')."); + pl("echo - Prints the arguments. If -e is set as the first argument, arguments are sent to colorize() first."); + pl("exit - Exits shellMode, and returns back to normal mscript mode."); + pl("logout - Exits the shell entirely with a return code of 0."); + pl("pwd - Runs pwd()"); + pl("help - Prints this message."); + return true; + case "cd": + case "s": + if("s".equals(command)) { + args.add(".."); + } + if(args.size() > 1) { + pl(RED + "Too many arguments passed to cd"); + return true; + } + Construct[] a = new Construct[0]; + if(args.size() == 1) { + a = new Construct[]{new CString(args.get(0), Target.UNKNOWN)}; + } + try { + new Cmdline.cd().exec(Target.UNKNOWN, env, a); + } catch (CREIOException ex) { + pl(RED + ex.getMessage()); + } + return true; + case "pwd": + pl(new Cmdline.pwd().exec(Target.UNKNOWN, env).val()); + return true; + case "exit": + // We need previous code to intercept, we cannot do this here. + throw new Error("I should not run"); + case "logout": + new Cmdline.exit().exec(Target.UNKNOWN, env, new CInt(0, Target.UNKNOWN)); + return true; // won't actually run + case "echo": + // TODO Probably need some variable interpolation maybe? Otherwise, I don't think this command + // is actually useful as is, because this is not supposed to be a scripting environment.. that's + // what the normal shell is for. + boolean colorize = false; + if(!args.isEmpty() && "-e".equals(args.get(0))) { + colorize = true; + args.remove(0); + } + String output = StringUtils.Join(args, " "); + if(colorize) { + output = new Echoes.colorize().exec(Target.UNKNOWN, env, new CString(output, Target.UNKNOWN)).val(); + } + pl(output); + return true; + } + } + return false; + } + + public static void install(String commandName) { + switch(OSUtils.GetOS()) { + case LINUX: + case MAC: { try { - bashScript = bashScript.replaceAll("%%LOCATION%%", jar.toURI().getPath()); - } catch (URISyntaxException ex) { - ex.printStackTrace(); - } - exe.createNewFile(); - if (!exe.canWrite()) { - throw new IOException(); + URL jar = Interpreter.class.getProtectionDomain().getCodeSource().getLocation(); + File exe = new File(UNIX_INTERPRETER_INSTALLATION_LOC + commandName); + String bashScript = Static.GetStringResource("/interpreter-helpers/bash.sh"); + try { + bashScript = bashScript.replaceAll("%%LOCATION%%", jar.toURI().getPath()); + } catch (URISyntaxException ex) { + ex.printStackTrace(System.err); + } + exe.createNewFile(); + if(!exe.canWrite()) { + throw new IOException(); + } + FileUtil.write(bashScript, exe); + exe.setExecutable(true, false); + File manDir = new File("/usr/local/man/man1"); + if(manDir.exists()) { + //Don't do this installation if the man pages aren't already there. + String manPage = Static.GetStringResource("/interpreter-helpers/manpage"); + try { + manPage = DocGenTemplates.DoTemplateReplacement(manPage, DocGenTemplates.GetGenerators()); + File manPageFile = new File(manDir, commandName + ".1"); + FileUtil.write(manPage, manPageFile); + } catch (DocGenTemplates.Generator.GenerateException ex) { + Logger.getLogger(Interpreter.class.getName()).log(Level.SEVERE, null, ex); + } + } + } catch (IOException e) { + StreamUtils.GetSystemErr().println("Cannot install. You must run the command with sudo for it to succeed, however, did you do that?"); + return; } - FileUtil.write(bashScript, exe); - exe.setExecutable(true, false); - File manDir = new File("/usr/local/man/man1"); - if (manDir.exists()) { - //Don't do this installation if the man pages aren't already there. - String manPage = Static.GetStringResource("/interpreter-helpers/manpage"); - manPage = DocGenTemplates.DoTemplateReplacement(manPage, DocGenTemplates.GetGenerators()); - File manPageFile = new File(manDir, "mscript.1"); - FileUtil.write(manPage, manPageFile); + break; + } + case WINDOWS: { + try { + // C# installer, not really uninstallable, so temporarily removing this, so the other installer + // can be used with no risk. +// // 1. Unpack the csharp installer program in a temporary directory +// File root = new File(Interpreter.class.getResource("/interpreter-helpers/csharp").toExternalForm()); +// ZipReader zReader = new ZipReader(root); +// tmp = Files.createTempDirectory("methodscript-installer", new FileAttribute[]{}); +// zReader.recursiveCopy(tmp.toFile(), false); + + // 2. Write the location of this jar to the registry + String me = ClassDiscovery.GetClassContainer(Interpreter.class).toExternalForm().substring(6); + String keyName = "Software\\MethodScript"; + WinRegistry.createKey(WinRegistry.HKEY_CURRENT_USER, keyName); + WinRegistry.writeStringValue(WinRegistry.HKEY_CURRENT_USER, keyName, "JarLocation", me); + +// // 3. Execute the setup.exe file +// File setup = new File(tmp.toFile(), "setup.exe"); +// int setupResult = new CommandExecutor(new String[]{setup.getAbsolutePath()}).start().waitFor(); +// if(setupResult != 0) { +// StreamUtils.GetSystemErr().println("Setup failed to complete successfully (exit code " + setupResult + ")"); +// System.exit(setupResult); +// } else { +// StreamUtils.GetSystemOut().println("Setup has begun. Finish the installation in the GUI."); +// } + + // 4. Write MethodScript.psm1 to the powershell module directory, + // C:\Program Files\WindowsPowerShell\Modules + // as well as MethodScript.psd1 + String powershellModule = Static.GetStringResource("/interpreter-helpers/windows/MethodScript.psm1"); + FileUtil.write(powershellModule, new File("C:/Program Files/WindowsPowerShell/Modules/" + + "MethodScript/MethodScript.psm1")); + String powershellManifest = Static.GetStringResource("/interpreter-helpers/windows/MethodScript.psd1"); + FileUtil.write(powershellManifest, new File("C:/Program Files/WindowsPowerShell/Modules/" + + "MethodScript/MethodScript.psd1")); + + // 5. Put the mscript.exe file in C:\Program Files\MethodScript + byte[] exe = StreamUtils.GetBytes(Interpreter.class + .getResource("/interpreter-helpers/windows/mscript.exe").openStream()); + + //= Static.GetStringResource("/interpreter-helpers/windows/mscript.cmd"); + FileUtil.write(exe, + new File(MethodScriptFileLocations.getDefault().getWindowsNativeDirectory(), "mscript.exe"), + FileWriteMode.OVERWRITE, true); + + // 6. Add C:\Program Files\MethodScript to the PATH, checking first if it's already + // there. + if(!System.getenv("PATH").contains("MethodScript")) { + String pathKey = "System\\CurrentControlSet\\Control\\Session Manager\\Environment"; + String path = System.getenv("Path"); + if(path != null) { + WinRegistry.writeStringValue(WinRegistry.HKEY_LOCAL_MACHINE, pathKey, "Path", path + ";" + + MethodScriptFileLocations.getDefault().getWindowsNativeDirectory()); + } + CommandExecutor.Execute("powershell -command \"& {$md=\\\"[DllImport(`\\\"user32.dll\\\"\\\",SetLastError=true,CharSet=CharSet.Auto)]public static extern IntPtr SendMessageTimeout(IntPtr hWnd,uint Msg,UIntPtr wParam,string lParam,uint fuFlags,uint uTimeout,out UIntPtr lpdwResult);\\\"; $sm=Add-Type -MemberDefinition $md -Name NativeMethods -Namespace Win32 -PassThru;$result=[uintptr]::zero;$sm::SendMessageTimeout(0xffff,0x001A,[uintptr]::Zero,\\\"Environment\\\",2,5000,[ref]$result)}\""); + } + + } catch (IOException | IllegalAccessException | InterruptedException | InvocationTargetException ex) { + StreamUtils.GetSystemErr().println("Could not install: " + ex + ". You need to run this in Administrator Mode however, did you do that?"); + System.exit(1); } - } catch (IOException e) { - System.err.println("Cannot install. You must run the command with sudo for it to succeed, however, did you do that?"); + break; + } + default: { + StreamUtils.GetSystemErr().println("Cmdline MethodScript is only supported on Unix and Windows"); return; } - } else { - System.err.println("Sorry, cmdline functionality is currently only supported on unix systems! Check back soon though!"); - return; } - System.out.println("MethodScript has successfully been installed on your system. Note that you may need to rerun the install command" - + " if you change locations of the jar, or rename it. Be sure to put \"#!" + INTERPRETER_INSTALLATION_LOCATION + "\" at the top of all your scripts," - + " if you wish them to be executable on unix systems, and set the execution bit with chmod +x ", ""); + { + // Some templates are immune to this, because it breaks the segments otherwise. So we need to first %s a few + // special templates. + inputString = inputString.replace("%%CURRENT_VERSION%%", "%s"); + inputString = inputString.replace("<%CURRENT_VERSION%>", "%s"); + inputString = inputString.replaceAll("(?s)%%.*?%%", ""); + // Template removal. We can't use regex here, because <% %> templates can be nested. Eventually, we want + // to use the whitelist, but for now, just remove all templates. + char c1; + char c2; + int count = 0; + StringBuilder b = new StringBuilder(); + for(int i = 0; i < inputString.length(); i++) { + c1 = inputString.charAt(i); + c2 = '\0'; + if(i + 1 < inputString.length()) { + c2 = inputString.charAt(i + 1); + } + if(c1 == '<' && c2 == '%') { + count++; + i++; + continue; + } + if(c1 == '%' && c2 == '>') { + count--; + i++; + continue; + } + if(count > 0) { + continue; + } + b.append(c1); + } + inputString = b.toString(); + } + inputString = inputString.replaceAll("(?s)", ""); + inputString = inputString.replaceAll("\\{\\{.*?\\}\\}", "%s"); + inputString = inputString.replaceAll("\\[\\[.*?\\|(.*?)\\]\\]", "[[%s|$1]]"); + inputString = inputString.replaceAll("\\[\\[File:.*?\\]\\]", ""); + inputString = inputString.replaceAll("\\[\\[Image:.*?\\]\\]", ""); + inputString = inputString.replaceAll("\\[" + URL_PATTERN + "( .*?)\\]", "[%s$1]"); + inputString = inputString.replaceAll(URL_PATTERN, "%s"); + inputString = inputString.replaceAll("(?s)<.*?>", "%s"); + + if(!forSearch) { + for(String uniqueSegment : UNIQUE_SEGMENTS) { + if(inputString.contains(uniqueSegment)) { + // We have to add it here so it ends up in the page file + segments.add(uniqueSegment); + inputString = inputString.replace(uniqueSegment, ""); + } + } + + for(String functionNames : FUNCTION_IDENTIFIERS) { + inputString = inputString.replaceAll(functionNames, "%s"); + } + } + + // Process tables + Matcher m = TABLE_PATTERN.matcher(inputString); + while(m.find()) { + String table = m.group(1); + table = table.replaceAll("\\{\\|.*\n", ""); + table = table.replaceAll("\\|\\}", ""); + table = table.replaceAll("\\|\\-\\s*\n", ""); + table = table.replaceAll("!.*\n", ""); + for(String cell : Arrays.asList(table.split("\\|\\||(?:(?:^|\n)\\|)"))) { + segments.addAll(Arrays.asList(SPLIT_PATTERN.split(cell))); + } + } + inputString = inputString.replaceAll(TABLE_PATTERN_STRING, ""); + + segments.addAll(Arrays.asList(SPLIT_PATTERN.split(inputString))); + segments.addAll(FRAME_SEGMENTS); + + return segments.stream() + .filter(string -> string != null) + .map(string -> { + string = string.trim(); + string = string.replace("\n", " "); + if(!forSearch) { + for(String className : CLASS_NAMES) { + // These are fully qualified, so they are certainly not meant to be translated. + string = string.replace(className, "%s"); + } + } + // Removing beginning and ending %s in the string has no impact on whether or not a string + // matches, but does increase the performance of the regex, and simplifies the segment for + // translators. We also want to remove symbols and whitespace where it's not necessary. + string = string.replaceAll("^(?:%s|\\s|[,#*])*(.*?)(?:%s|\\s)*$", "$1"); + // Collapse multiple %s into one + string = string.replaceAll("%s(?:%s)+", "%s"); + return string; + }) + .filter((string) -> { + if(string.isEmpty()) { + return false; + } else if(string.matches("(?:\\s*%s\\s*)+")) { + return false; + } else if(string.matches("\\s*")) { + return false; + } + return true; + }) + // Strings that are just symbols (or %s) in their entirety can be removed. + .filter((string) -> !string.matches("^(?:[^a-zA-Z]|%s)+$")) + // Segments that are entirely just a function name are removed. + .filter((string) -> !forSearch ? (!FUNCTION_NAMES.contains(string)) : (true)) + .filter((string) -> !USELESS_SEGMENTS.contains(string)) + .collect(Collectors.toSet()); + } + + /** + * Validates the database, returning a set of errors. If the set is empty, then this means there are no errors. + * @return + */ + public Set validate() { + Set errors = new HashSet<>(); + errors.addAll(translationSummary.validate()); + for(LocaleTranslationMaster ltm : allLocales.values()) { + for(TranslationMemory tm : ltm.master.values()) { + for(TranslationMemory tm2 : ltm.master.values()) { + if(tm == tm2) { + continue; + } + if(tm.getId() == tm2.getId()) { + errors.add("Two entries in the master.tmem.xml file for the " + ltm.locale + " locale" + + " have the same id: " + tm.getId()); + } + if(tm.getEnglishKey().equals(tm2.getEnglishKey())) { + errors.add("Two entries in the master.tmem.xml file for the " + ltm.locale + + " locale have the same key: " + + ltm.locale + "-" + tm.getId() + " and " + + ltm.locale + "-" + tm2.getId()); + } + } + int summaryId = this.translationSummary.getTranslationId(tm.getEnglishKey()); + if(summaryId != tm.getId()) { + errors.add("The id defined in master.tmem.xml for the " + ltm.locale + " locale for " + + tm.getId() + " does not exist in the summary file!"); + } + } +// for(PageTranslations pt : ltm.pages.values()) { +// for(TranslationMemory tm : pt.blocks.values()) { +// for(TranslationMemory tm2 : pt.blocks.values()) { +// if(tm == tm2) { +// continue; +// } +// if(tm.getId() == tm2.getId()) { +// errors.add("Two entries in the " + pt.file + " file for the " + ltm.locale + " locale" +// + " have the same id: " + tm.getId()); +// } +// if(tm.getEnglishKey().equals(tm2.getEnglishKey())) { +// errors.add("Two entries in the " + pt.file + " file for the " + ltm.locale + " locale" +// + " have the same key: " +// + ltm.locale + "-" + tm.getId() + " and " +// + ltm.locale + "-" + tm2.getId()); +// } +// } +// +// int summaryId = this.translationSummary.getTranslationId(tm.getEnglishKey()); +// if(summaryId != tm.getId()) { +// errors.add("The id defined in " + pt.file + " for the " + ltm.locale + " locale for " +// + tm.getId() + " does not exist in the summary file!"); +// } +// +// int masterId = allLocales.get(pt.locale).master.get(tm.getEnglishKey()).getId(); +// if(masterId != tm.getId()) { +// errors.add("The id defined in " + pt.file + " for the " + ltm.locale + " locale for " +// + tm.getId() + " does not exist in the master.tmem.xml file!"); +// } +// } +// } + } + return errors; + } + + /** + * Returns the number of unique segments in the summary. + * @return + */ + public int size() { + return translationSummary.size(); + } + +} diff --git a/src/main/java/com/laytonsmith/tools/docgen/localization/TranslationMemory.java b/src/main/java/com/laytonsmith/tools/docgen/localization/TranslationMemory.java new file mode 100644 index 0000000000..cb4efc7ab3 --- /dev/null +++ b/src/main/java/com/laytonsmith/tools/docgen/localization/TranslationMemory.java @@ -0,0 +1,215 @@ +package com.laytonsmith.tools.docgen.localization; + +import com.laytonsmith.PureUtilities.Common.MutableObject; +import com.laytonsmith.PureUtilities.SAXDocument; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.xml.sax.SAXException; + +/** + * A TranslationMemory is an language segment, that keys the english translation to the given translation. It optionally + * includes a comment which may assist the translator in determining the context + * of the translation. There is also an automatic translation field. If a manual translation is missing, then + * the automatic translation will be used by default, but may be overwritten manually. When a new translation memory + * appears, it will inherit the translation (if any) from the master translation file, and then may or may not + * attempt a machine translated version. If both the automatic and manual translation are missing, the original + * is kept. + * + * The serialization uses xml format. This was a difficult decision, because json seems more appropriate, and would + * be generally easier to deal with in the code, however, this increases complexity of the translators themselves, as + * things such as newlines, and quotes need escaping. With xml, a CDATA block can be added, and then very little + * needs to actually be escaped within the blocks. + */ +public class TranslationMemory implements Comparable { + + private static final String BEGIN_BLOCK = "\n\n"; + private static final String END_BLOCK = "\n"; + + private final int translationId; + private final String englishKey; + private final Locale locale; + private String comment; + private String translation; + private String automaticTranslation; + + public TranslationMemory(String englishKey, Locale locale, String comment, + String translation, + String automaticTranslation, int id) { + this.englishKey = englishKey; + this.locale = locale; + this.comment = comment; + this.translation = translation; + this.automaticTranslation = automaticTranslation; + if(id <= 0) { + throw new IllegalArgumentException("id must be above 0!"); + } + this.translationId = id; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public void setTranslation(String translation) { + this.translation = translation; + } + + public void setAutomaticTranslation(String automaticTranslation) { + this.automaticTranslation = automaticTranslation; + } + + /** + * The English Key is the "master" value for this translation. + * @return + */ + public String getEnglishKey() { + return englishKey; + } + + /** + * The locale that this {@code TranslationMemory} is associated with. + * @return + */ + public Locale getLocale() { + return locale; + } + + /** + * The local specific comment for this translation. + * @return + */ + public String getComment() { + return comment; + } + + /** + * The unique id for this translation. + * @return + */ + public int getId() { + return translationId; + } + + /** + * The machine translation, or null if not set. + * @return + */ + public String getMachineTranslation() { + return this.automaticTranslation; + } + + /** + * The human translation, or null if not set. + * @return + */ + public String getTranslation() { + return this.translation; + } + + @Override + public String toString() { + return "(" + locale + ") " + englishKey + " ------> " + translation; + } + + public String toTmemString() { + StringBuilder b = new StringBuilder(); + b.append("\n"); + b.append("\t").append(locale.getLocale()).append("-").append(translationId).append("\n"); + b.append("\t").append(escape(englishKey)).append("\n"); + b.append("\t").append(escape(comment)).append("\n"); + b.append("\t").append(escape(translation)).append("\n"); + b.append("\t").append(escape(automaticTranslation)).append("\n"); + b.append("\n"); + return b.toString(); + } + + private String escape(String input) { + if(input == null || "".equals(input)) { + return ""; + } + input = input.replace("]]>", "]]]]>"); + return ""; + } + + public static String generateTranslationFile(Map memories) { + StringBuilder b = new StringBuilder(); + List values = new ArrayList<>(memories.values()); + Collections.sort(values); + for(TranslationMemory tm : values) { + b.append(tm.toTmemString()); + } + return BEGIN_BLOCK + b.toString() + END_BLOCK; + } + + public static Map fromTmemFile(Locale locale, String fileContents) throws IOException { + Map memories = new HashMap<>(); + if("".equals(fileContents)) { + return memories; + } + SAXDocument sax = new SAXDocument(fileContents, "UTF-8"); + final MutableObject id = new MutableObject<>(); + final MutableObject key = new MutableObject<>(); + final MutableObject comment = new MutableObject<>(); + final MutableObject translation = new MutableObject<>(); + final MutableObject automaticTranslation = new MutableObject<>(); + final MutableObject overrideMaster = new MutableObject<>(); + + sax.addListener("/translations/translationBlock/id", (xpath, tag, attr, contents) -> { + id.setObject(contents); + }); + + sax.addListener("/translations/translationBlock/key", (xpath, tag, attr, contents) -> { + key.setObject(contents); + }); + + sax.addListener("/translations/translationBlock/comment", (xpath, tag, attr, contents) -> { + comment.setObject(contents); + }); + + sax.addListener("/translations/translationBlock/translation", (xpath, tag, attr, contents) -> { + translation.setObject(contents); + }); + + sax.addListener("/translations/translationBlock/auto", (xpath, tag, attr, contents) -> { + automaticTranslation.setObject(contents); + }); + + sax.addListener("/translations/translationBlock/overrideMaster", (xpath, tag, attr, contents) -> { + overrideMaster.setObject(Boolean.valueOf(contents)); + }); + + sax.addListener("/translations/translationBlock", (xpath, tag, attr, contents) -> { + int intId = Integer.parseInt(id.getObject().replaceAll(locale.getLocale() + "-(.*)", "$1")); + TranslationMemory tm = new TranslationMemory( + key.getObject(), + locale, + comment.getObject(), + translation.getObject(), + automaticTranslation.getObject(), + intId); + memories.put(key.getObject(), tm); + id.setObject(null); + key.setObject(null); + comment.setObject(null); + translation.setObject(null); + automaticTranslation.setObject(null); + overrideMaster.setObject(null); + }); + + try { + sax.parse(); + } catch (SAXException ex) { + throw new IOException(ex); + } + return memories; + } + + @Override + public int compareTo(TranslationMemory t) { + return Integer.compare(this.translationId, t.translationId); + } +} diff --git a/src/main/java/com/laytonsmith/tools/docgen/localization/TranslationSummary.java b/src/main/java/com/laytonsmith/tools/docgen/localization/TranslationSummary.java new file mode 100644 index 0000000000..0bc9b3eb1f --- /dev/null +++ b/src/main/java/com/laytonsmith/tools/docgen/localization/TranslationSummary.java @@ -0,0 +1,299 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.laytonsmith.tools.docgen.localization; + +import com.laytonsmith.PureUtilities.Common.FileUtil; +import com.laytonsmith.PureUtilities.Common.FileWriteMode; +import com.laytonsmith.PureUtilities.Common.MutableObject; +import com.laytonsmith.PureUtilities.SAXDocument; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.xml.sax.SAXException; + +/** + * A translation summary is a file that contains information about a translation that applies to all locales. + */ +public class TranslationSummary { + + public class TranslationSummaryEntry implements Comparable { + private final String englishKey; + private final int id; + private Boolean eligibleForMachineTranslation = null; + private boolean untranslatable = false; + private boolean suspectSegment = false; + private String comment; + + public TranslationSummaryEntry(String englishKey, int id) { + this.englishKey = englishKey; + this.id = id; + } + + @Override + public int compareTo(TranslationSummaryEntry t) { + return Integer.compare(this.id, t.id); + } + + public String getEnglishKey() { + return englishKey; + } + + public int getId() { + return id; + } + + public Boolean getEligibleForMachineTranslation() { + return eligibleForMachineTranslation; + } + + public String getComment() { + return comment; + } + + public boolean isUntranslatable() { + return untranslatable; + } + + public boolean isSuspectSegment() { + return suspectSegment; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public void setEligibleForMachineTranslation(Boolean eligibleForMachineTranslation) { + this.eligibleForMachineTranslation = eligibleForMachineTranslation; + } + + public void setSuspectSegment(boolean suspectSegment) { + this.suspectSegment = suspectSegment; + } + + public void setUntranslatable(boolean untranslatable) { + this.untranslatable = untranslatable; + } + } + + private final Map entries = new HashMap<>(); + private final File translationDb; + private int nextId = 0; + + public TranslationSummary(File translationDb) throws IOException { + this.translationDb = translationDb; + initialize(); + } + + private String toSummaryString() { + StringBuilder b = new StringBuilder(); + b.append(""); + List values = new ArrayList<>(entries.values()); + Collections.sort(values); + for(TranslationSummaryEntry tse : values) { + b.append("\n"); + b.append("\t").append(tse.id).append("\n"); + b.append("\t").append(escape(tse.englishKey)).append("\n"); + b.append("\t").append(tse.eligibleForMachineTranslation) + .append("\n"); + b.append("\t").append(escape(tse.comment)).append("\n"); + b.append("\t").append(tse.untranslatable).append("\n"); + b.append("\t").append(tse.suspectSegment).append("\n"); + b.append("\n"); + } + b.append("\n"); + return b.toString(); + } + + private String escape(String input) { + if(input == null || "".equals(input)) { + return ""; + } + input = input.replace("]]>", "]]]]>"); + return ""; + } + + public int getNextId() { + return ++nextId; + } + + /** + * Checks if the summary file contains this translation already. + * @param key + * @return + */ + public boolean containsTranslation(String key) { + return entries.containsKey(key); + } + + /** + * Returns the translation id + * @param key + * @return + */ + public int getTranslationId(String key) { + return entries.get(key).id; + } + + /** + * Returns a list of all the summary translation memories. + * @return + */ + public List getAllMemories() { + return new ArrayList<>(entries.values()); + } + + /** + * Returns a translation memory summary based on the string key + * @param key + * @return + */ + public TranslationSummaryEntry getMemory(String key) { + return entries.get(key); + } + + /** + * Adds a new translation summary to the summary database. + * @param key + * @param id + */ + public void addTranslation(String key, int id) { + if(containsTranslation(key)) { + throw new Error("The summary already contains an entry with key! " + key); + } + TranslationSummaryEntry v = new TranslationSummaryEntry(key, id); + entries.put(key, v); + } + + /** + * Writes out the summary xml. + * @throws IOException + */ + public void save() throws IOException { + FileUtil.write(toSummaryString(), new File(translationDb, "summary.xml"), FileWriteMode.OVERWRITE, true); + } + + /** + * Returns whether or not the given translation is eligible for machine translation. False and null should have + * the same effect, that is, it should not be machine translated. + * @param key + * @return + */ + public Boolean isMachineTranslatable(String key) { + return entries.get(key).eligibleForMachineTranslation; + } + + private void initialize() throws IOException { + File f = new File(translationDb, "summary.xml"); + if(!f.exists()) { + FileUtil.write("", f); + } + SAXDocument sd = new SAXDocument(new FileInputStream(f)); + + MutableObject id = new MutableObject<>(); + MutableObject key = new MutableObject<>(); + MutableObject eligibleForMachineTranslation = new MutableObject<>(); + MutableObject comment = new MutableObject<>(); + MutableObject untranslatable = new MutableObject<>(); + MutableObject suspectSegment = new MutableObject<>(false); + + sd.addListener("/summary/translationEntry/id", + (String xpath, String tag, Map attr, String contents) -> { + id.setObject(Integer.parseInt(contents)); + }); + + sd.addListener("/summary/translationEntry/key", + (String xpath, String tag, Map attr, String contents) -> { + key.setObject(contents); + }); + + sd.addListener("/summary/translationEntry/eligibleForMachineTranslation", + (String xpath, String tag, Map attr, String contents) -> { + Boolean b; + if("true".equals(contents)) { + b = true; + } else if("false".equals(contents)) { + b = false; + } else { + b = null; + } + eligibleForMachineTranslation.setObject(b); + }); + + sd.addListener("/summary/translationEntry/comment", + (String xpath, String tag, Map attr, String contents) -> { + comment.setObject(contents); + }); + + sd.addListener("/summary/translationEntry/untranslatable", + (xpath, tag, attr, contents) -> { + untranslatable.setObject(Boolean.valueOf(contents)); + }); + + sd.addListener("/summary/translationEntry/suspectSegment", (xpath, tag, attr, contents) -> { + suspectSegment.setObject(Boolean.valueOf(contents)); + }); + + sd.addListener("/summary/translationEntry", + (String xpath, String tag, Map attr, String contents) -> { + TranslationSummaryEntry tse = new TranslationSummaryEntry(key.getObject(), id.getObject()); + tse.eligibleForMachineTranslation = eligibleForMachineTranslation.getObject(); + tse.comment = comment.getObject(); + tse.untranslatable = untranslatable.getObject(); + tse.suspectSegment = suspectSegment.getObject(); + TranslationSummary.this.entries.put(key.getObject(), tse); + + nextId = java.lang.Math.max(nextId, tse.id); + + id.setObject(null); + key.setObject(null); + eligibleForMachineTranslation.setObject(null); + comment.setObject(null); + untranslatable.setObject(false); + suspectSegment.setObject(false); + }); + + try { + sd.parse(); + } catch (SAXException ex) { + throw new IOException(ex); + } + + } + + public Set validate() { + Set errors = new HashSet<>(); + for(TranslationSummaryEntry tse : entries.values()) { + // Ensure unique ids + for(TranslationSummaryEntry tse2 : entries.values()) { + if(tse == tse2) { + continue; + } + if(tse.id == tse2.id) { + errors.add("Two entries in the summary.xml file have the same id: " + tse.id); + } + if(tse.englishKey.equals(tse2.englishKey)) { + errors.add("Two entries in the summary.xml file have the same key: " + tse.id + " and " + tse2.id); + } + } + } + return errors; + } + + /** + * Returns the number of translation segments in total. + * @return + */ + public int size() { + return entries.size(); + } +} diff --git a/src/main/java/com/laytonsmith/tools/docgen/sitedeploy/APIBuilder.java b/src/main/java/com/laytonsmith/tools/docgen/sitedeploy/APIBuilder.java new file mode 100644 index 0000000000..debd5681b1 --- /dev/null +++ b/src/main/java/com/laytonsmith/tools/docgen/sitedeploy/APIBuilder.java @@ -0,0 +1,249 @@ +package com.laytonsmith.tools.docgen.sitedeploy; + +import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery; +import com.laytonsmith.PureUtilities.Common.StreamUtils; +import com.laytonsmith.PureUtilities.Common.StringUtils; +import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.abstraction.Implementation; +import com.laytonsmith.annotations.api.Platforms; +import com.laytonsmith.annotations.hide; +import com.laytonsmith.annotations.typeof; +import com.laytonsmith.core.FullyQualifiedClassName; +import com.laytonsmith.core.MethodScriptFileLocations; +import com.laytonsmith.core.Optimizable; +import com.laytonsmith.core.compiler.KeywordDocumentation; +import com.laytonsmith.core.compiler.KeywordList; +import com.laytonsmith.core.constructs.CClassType; +import com.laytonsmith.core.constructs.NativeTypeList; +import com.laytonsmith.core.events.Event; +import com.laytonsmith.core.events.EventList; +import com.laytonsmith.core.exceptions.CRE.CREThrowable; +import com.laytonsmith.core.extensions.ExtensionManager; +import com.laytonsmith.core.extensions.ExtensionTracker; +import com.laytonsmith.core.functions.Function; +import com.laytonsmith.core.functions.FunctionBase; +import com.laytonsmith.core.functions.FunctionList; +import com.laytonsmith.core.natives.interfaces.Mixed; +import com.laytonsmith.tools.docgen.DocGen; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.json.simple.JSONValue; + +/** + * This class builds the JSON API file + * + * @author cailin + */ +public class APIBuilder { + + /** + * Can be run independently, or by the programmer, but this will always print out to System.out the json. Extensions + * are loaded first. + * @param args Ignored. + */ + public static void main(String[] args) { + try { + Implementation.setServerType(Implementation.Type.SHELL); + } catch (RuntimeException e) { + // Eh.. in most cases this is wrong, but this will happen when json-api is called, and that's ok. + } + ClassDiscovery.getDefaultInstance().addDiscoveryLocation(ClassDiscovery.GetClassContainer(APIBuilder.class)); + ExtensionManager.AddDiscoveryLocation(MethodScriptFileLocations.getDefault().getExtensionsDirectory()); + ExtensionManager.Cache(MethodScriptFileLocations.getDefault().getExtensionCacheDirectory()); + ExtensionManager.Initialize(ClassDiscovery.getDefaultInstance()); + StreamUtils.GetSystemOut().println(JSONValue.toJSONString(new APIBuilder().build())); + } + + /** + * Returns a Map that can be converted into a json (or used for other purposes), which provides various information + * about the entire API, including known extensions. + * + * @return + */ + public Map build() { + Map json = new TreeMap<>(); + { + // functions + Map> api = new TreeMap<>(); + for(FunctionBase f : FunctionList.getFunctionList(Platforms.INTERPRETER_JAVA, null)) { + if(f instanceof Function) { + Function ff = (Function) f; + DocGen.DocInfo di; + try { + di = new DocGen.DocInfo(ff.docs()); + } catch (IllegalArgumentException ex) { + continue; + } + Map function = new TreeMap<>(); + function.put("name", ff.getName()); + function.put("ret", di.ret); + function.put("args", di.originalArgs); + List thrown = new ArrayList<>(); + try { + if(ff.thrown() != null) { + for(Class c : ff.thrown()) { + thrown.add(ClassDiscovery.GetClassAnnotation(c, typeof.class).value()); + } + } + } catch (Throwable t) { + Logger.getLogger("default").log(Level.SEVERE, null, t); + } + function.put("thrown", thrown); + function.put("desc", di.desc); + function.put("extdesc", di.extendedDesc); + function.put("shortdesc", di.topDesc); + function.put("since", ff.since().toString()); + function.put("restricted", ff.isRestricted()); + function.put("coreFunction", ff.isCore()); + List optimizations = new ArrayList<>(); + if(ff instanceof Optimizable) { + for(Optimizable.OptimizationOption o : ((Optimizable) ff).optimizationOptions()) { + optimizations.add(o.name()); + } + } + function.put("optimizations", optimizations); + hide athide = ff.getClass().getAnnotation(hide.class); + String hidden = athide == null ? null : athide.value(); + function.put("hidden", hidden); + ExtensionTracker et = ExtensionManager.getTrackers().get(ff.getSourceJar()); + String extId; + if(et != null) { + extId = et.getIdentifier(); + } else { + // Legacy extensions don't have an ExtensionTracker, so we need to come up with a name + // for them. Just use the jar name, but remove the oldstyle- that is prepended by the + // extension manager. + extId = StringUtils.replaceLast(new java.io.File(ff.getSourceJar().getPath().replaceFirst("/", "")) + .getName().replaceFirst("oldstyle-", ""), ".jar", ""); + } + function.put("source", extId); + api.put(ff.getName(), function); + } + } + json.put("functions", api); + } + { + // events + Map> events = new TreeMap<>(); + for(Event e : EventList.GetEvents()) { + try { + DocGen.EventDocInfo edi; + try { + edi = new DocGen.EventDocInfo(e, e.docs(), e.getName(), DocGen.MarkupType.HTML); + } catch (IllegalArgumentException ex) { + continue; + } + Map event = new TreeMap<>(); + event.put("name", e.getName()); + event.put("desc", edi.description); + Map ed = new TreeMap<>(); + for(DocGen.EventDocInfo.EventData edd : edi.eventData) { + ed.put(edd.name, edd.description); + } + event.put("eventData", ed); + Map md = new TreeMap<>(); + for(DocGen.EventDocInfo.MutabilityData mdd : edi.mutability) { + md.put(mdd.name, mdd.description); + } + event.put("mutability", md); + Map pd = new TreeMap<>(); + for(DocGen.EventDocInfo.PrefilterData pdd : edi.prefilter) { + pd.put(pdd.name, pdd.formatDescription(DocGen.MarkupType.HTML)); + } + event.put("prefilters", pd); + event.put("since", e.since().toString()); + String extId = ExtensionManager.getTrackers().get(e.getSourceJar()).getIdentifier(); + event.put("source", extId); + events.put(e.getName(), event); + } catch (Exception ex) { + Logger.getLogger("default").log(Level.SEVERE, e.getName(), ex); + } + } + json.put("events", events); + } + { + Map> extensions = new TreeMap<>(); + // extensions + for(ExtensionTracker t : ExtensionManager.getTrackers().values()) { + Map ext = new TreeMap<>(); + ext.put("id", t.getIdentifier()); + ext.put("version", t.getVersion().toString()); + extensions.put(t.getIdentifier(), ext); + } + json.put("extensions", extensions); + } + { + // keywords + Map> keywords = new TreeMap<>(); + for(KeywordDocumentation keyword : KeywordList.getKeywordList()) { + Map keyw = new TreeMap<>(); + keyw.put("name", keyword.getKeywordName()); + keyw.put("docs", keyword.docs()); + keyw.put("since", keyword.since().toString()); + String extId = ExtensionManager.getTrackers().get(keyword.getSourceJar()).getIdentifier(); + keyw.put("source", extId); + keywords.put(keyword.getKeywordName(), keyw); + } + json.put("keywords", keywords); + } + { + //objects + Map> objects = new TreeMap<>(); + for(FullyQualifiedClassName t : NativeTypeList.getNativeTypeList()) { + try { + if("void".equals(t.getFQCN()) || "null".equals(t.getFQCN())) { + // These are super special, and aren't really + // "real" datatypes, so shouldn't be included. + continue; + } + Map obj = new TreeMap<>(); + String name; + String docs; + Version since; + URL source; + CClassType[] interfaces; + CClassType[] supers; + + Mixed m = NativeTypeList.getInvalidInstanceForUse(t); + name = m.getName(); + docs = m.docs(); + since = m.since(); + source = m.getSourceJar(); + interfaces = m.getInterfaces(); + supers = m.getSuperclasses(); + obj.put("type", name); + obj.put("docs", docs); + obj.put("since", since.toString()); + String extId = ExtensionManager.getTrackers().get(source).getIdentifier(); + obj.put("source", extId); + List i = new ArrayList<>(); + List s = new ArrayList<>(); + for(CClassType c : interfaces) { + i.add(c.val()); + } + for(CClassType c : supers) { + s.add(c.val()); + } + obj.put("interfaces", i); + obj.put("superclasses", s); + //obj.put("objectType", m.getObjectType().toString()); + //obj.put("containingClass", m.getContainingClass().getName()); + //obj.put("objectModifiers", m.getObjectModifiers()); + objects.put(name, obj); + } catch (ClassNotFoundException ex) { + // Pretty sure this isn't possible? + Logger.getLogger(SiteDeploy.class.getName()).log(Level.SEVERE, null, ex); + } catch (Exception ex) { + Logger.getLogger(SiteDeploy.class.getName()).log(Level.SEVERE, "Could not instantiate " + t, ex); + } + } + json.put("objects", objects); + } + return json; + } +} diff --git a/src/main/java/com/laytonsmith/tools/docgen/sitedeploy/DeploymentMethod.java b/src/main/java/com/laytonsmith/tools/docgen/sitedeploy/DeploymentMethod.java new file mode 100644 index 0000000000..160e6adb00 --- /dev/null +++ b/src/main/java/com/laytonsmith/tools/docgen/sitedeploy/DeploymentMethod.java @@ -0,0 +1,38 @@ +package com.laytonsmith.tools.docgen.sitedeploy; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Provides a mechanism to deploy to a particular system using a particular method. + * + * @author cailin + */ +interface DeploymentMethod { + + /** + * Writes the file to the appropriate location. + * + * @param data The data to write + * @param toLocation The general location to write the file to. + * @param overrideIdRsa The overridden id_rsa location, or null. + * @return true, if the file was actually changed, false otherwise + * @throws IOException + */ + boolean deploy(InputStream data, String toLocation, String overrideIdRsa) throws IOException; + + /** + * After the deployment is finished, this will be called. This can optionally clean up i.e. network connections if + * they were being cached. + */ + void finish(); + + /** + * Should return a string that, given the input parameter, will be a unique (but human readable) identifier for that + * particular resource location. + * + * @return + */ + String getID(); + +} diff --git a/src/main/java/com/laytonsmith/tools/docgen/sitedeploy/LocalDeploymentMethod.java b/src/main/java/com/laytonsmith/tools/docgen/sitedeploy/LocalDeploymentMethod.java new file mode 100644 index 0000000000..75077babf9 --- /dev/null +++ b/src/main/java/com/laytonsmith/tools/docgen/sitedeploy/LocalDeploymentMethod.java @@ -0,0 +1,59 @@ +package com.laytonsmith.tools.docgen.sitedeploy; + +import com.laytonsmith.PureUtilities.Common.FileUtil; +import com.laytonsmith.PureUtilities.Common.StreamUtils; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author cailin + */ +class LocalDeploymentMethod implements DeploymentMethod { + + String rootDirectory; + + public LocalDeploymentMethod(String rootDirectory) { + this.rootDirectory = rootDirectory; + } + + @Override + public boolean deploy(InputStream data, String toLocation, String ignored) { + File outLoc = new File(rootDirectory, toLocation); + try { + byte[] d = StreamUtils.GetBytes(data); + String currentFile; + try { + currentFile = SiteDeploy.getLocalMD5(new FileInputStream(outLoc)); + } catch (FileNotFoundException ex) { + // Doesn't exist, so just set the currentFile to INVALID + currentFile = "INVALID"; + } + String newFile = SiteDeploy.getLocalMD5(new ByteArrayInputStream(d)); + if(currentFile.equals(newFile)) { + return false; + } + FileUtil.write(d, outLoc, FileUtil.OVERWRITE, true); + } catch (IOException ex) { + Logger.getLogger(SiteDeploy.class.getName()).log(Level.SEVERE, null, ex); + return false; + } + return true; + } + + @Override + public void finish() { + } + + @Override + public String getID() { + return rootDirectory; + } + +} diff --git a/src/main/java/com/laytonsmith/tools/docgen/sitedeploy/RemoteDeploymentMethod.java b/src/main/java/com/laytonsmith/tools/docgen/sitedeploy/RemoteDeploymentMethod.java new file mode 100644 index 0000000000..6b3ccc28fe --- /dev/null +++ b/src/main/java/com/laytonsmith/tools/docgen/sitedeploy/RemoteDeploymentMethod.java @@ -0,0 +1,41 @@ +package com.laytonsmith.tools.docgen.sitedeploy; + +import com.laytonsmith.PureUtilities.SSHWrapper; +import java.io.IOException; +import java.io.InputStream; + +/** + * + * @author cailin + */ +class RemoteDeploymentMethod implements DeploymentMethod { + + String remote; + + public RemoteDeploymentMethod(String remote) { + this.remote = remote; + } + + @Override + public boolean deploy(InputStream data, String toLocation, String overrideIdRsa) throws IOException { + // TODO We actually are assuming the system we're connecting to is linux, however, that may not + // be true. We should have two RemoteDeploymentMethods for each OS so that we can support + // either, and add an option to be able to override that if the remote system is not Linux. + // There's no real way for us to guess the OS though, so we can't do that automatically. But + // linux is a super good guess if we're using ssh. However, swapping to an rsync method may + // make this moot anyways. + toLocation = toLocation.replace('\\', '/'); + return SSHWrapper.SCPWrite(data, remote + toLocation, overrideIdRsa); + } + + @Override + public void finish() { + SSHWrapper.closeSessions(); + } + + @Override + public String getID() { + return remote; + } + +} diff --git a/src/main/java/com/laytonsmith/tools/docgen/sitedeploy/SiteDeploy.java b/src/main/java/com/laytonsmith/tools/docgen/sitedeploy/SiteDeploy.java new file mode 100644 index 0000000000..99b00b3dbc --- /dev/null +++ b/src/main/java/com/laytonsmith/tools/docgen/sitedeploy/SiteDeploy.java @@ -0,0 +1,1708 @@ +package com.laytonsmith.tools.docgen.sitedeploy; + +import com.laytonsmith.tools.docgen.localization.TranslationMaster; +import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery; +import com.laytonsmith.PureUtilities.ClassLoading.ClassMirror.MethodMirror; +import com.laytonsmith.PureUtilities.CommandExecutor; +import com.laytonsmith.PureUtilities.Common.GNUErrorMessageFormat; +import com.laytonsmith.PureUtilities.Common.HTMLUtils; +import com.laytonsmith.PureUtilities.Common.ReflectionUtils; +import com.laytonsmith.PureUtilities.Common.StreamUtils; +import com.laytonsmith.PureUtilities.Common.StringUtils; +import com.laytonsmith.PureUtilities.DaemonManager; +import com.laytonsmith.PureUtilities.Preferences; +import com.laytonsmith.PureUtilities.Web.HTTPMethod; +import com.laytonsmith.PureUtilities.Web.HTTPResponse; +import com.laytonsmith.PureUtilities.Web.RequestSettings; +import com.laytonsmith.PureUtilities.Web.WebUtility; +import com.laytonsmith.PureUtilities.ZipReader; +import com.laytonsmith.abstraction.Implementation; +import com.laytonsmith.abstraction.enums.MCChatColor; +import com.laytonsmith.annotations.api; +import com.laytonsmith.annotations.hide; +import com.laytonsmith.annotations.typeof; +import com.laytonsmith.core.MSVersion; +import com.laytonsmith.core.MethodScriptFileLocations; +import com.laytonsmith.core.Optimizable; +import com.laytonsmith.core.Profiles; +import com.laytonsmith.core.Static; +import com.laytonsmith.core.asm.LLVMFunction; +import com.laytonsmith.core.events.Event; +import com.laytonsmith.core.exceptions.CRE.CREThrowable; +import com.laytonsmith.core.exceptions.ConfigCompileException; +import com.laytonsmith.core.functions.ExampleScript; +import com.laytonsmith.core.functions.Function; +import com.laytonsmith.persistence.DataSourceException; +import com.laytonsmith.persistence.PersistenceNetwork; +import com.laytonsmith.persistence.PersistenceNetworkImpl; +import com.laytonsmith.persistence.ReadOnlyException; +import com.laytonsmith.persistence.io.ConnectionMixinFactory; +import com.laytonsmith.tools.Interpreter; +import com.laytonsmith.tools.docgen.DocGen; +import com.laytonsmith.tools.docgen.DocGenTemplates; +import com.laytonsmith.tools.docgen.DocGenTemplates.Generator; +import com.laytonsmith.tools.docgen.DocGenTemplates.Generator.GenerateException; +import com.laytonsmith.tools.docgen.localization.MasterSearchIndex; +import com.laytonsmith.tools.docgen.localization.ResultType; +import com.laytonsmith.tools.docgen.templates.Template; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Logger; +import java.util.regex.Pattern; +import java.util.zip.GZIPOutputStream; +import org.json.simple.JSONValue; + +/** + * This class is responsible for deploying documentation to an html webserver. It converts wiki markup to html, and uses + * scp to transfer the complete website structure to the specified location. + * + * @author cailin + */ +@SuppressWarnings("UseSpecificCatch") +public final class SiteDeploy { + + private static final String USERNAME = "username"; + private static final String HOSTNAME = "hostname"; + private static final String PORT = "port"; + private static final String DIRECTORY = "directory"; + private static final String PASSWORD = "use-password"; + private static final String DOCSBASE = "docs-base"; + private static final String SITEBASE = "site-base"; + private static final String SHOW_TEMPLATE_CREDIT = "show-template-credit"; + private static final String GITHUB_BASE_URL = "github-base-url"; + private static final String VALIDATOR_URL = "validator-url"; + private static final String POST_SCRIPT = "post-script"; + + private static final String TRANSLATION_MEMORY_DB = "translation-memory-db"; + private static final String PRODUCTION_TRANSLATIONS = "production-translations"; + + private static final String INSTALL_URL = "install-url"; + private static final String INSTALL_PEM_FILE = "install-pem-file"; + private static final String INSTALL_PUB_KEYS = "install-pub-keys"; + + private static final String PRODUCTION_TRANSLATIONS_URL + = "https://raw.githubusercontent.com/LadyCailin/MethodScriptTranslationDB/master/"; + + @SuppressWarnings("ResultOfObjectAllocationIgnored") + public static void run(boolean generatePrefs, boolean useLocalCache, File sitedeploy, String password, + boolean doValidation, boolean clearProgressBar, String overridePostScript, + String overrideIdRsa) throws Exception { + List defaults = new ArrayList<>(); + // SCP Options + defaults.add(new Preferences.Preference(USERNAME, "", Preferences.Type.STRING, "The username to scp with")); + defaults.add(new Preferences.Preference(HOSTNAME, "", Preferences.Type.STRING, "The hostname to scp to (not the" + + " hostname of the site). If" + + " the hostname is \"localhost\", or \"127.0.0.1\" this triggers special handling," + + " which skips the upload, and simply" + + " saves to the specified location on local disk. This should work with all OSes, otherwise the host" + + " that this connects to must support ssh, though it does not necessarily have to be a unix" + + " based system." + + " If the value is localhost/127.0.0.1, the values " + USERNAME + ", " + PORT + ", and " + PASSWORD + + " are" + + " irrelevant, and not used. This should NOT begin with a protocol, i.e. http://")); + defaults.add(new Preferences.Preference(PORT, "22", Preferences.Type.INT, "The port to use for SCP")); + defaults.add(new Preferences.Preference(DIRECTORY, "/var/www/docs", Preferences.Type.STRING, + "The root location of" + + " the remote web server. This must be an absolute path. Note that this is the location of" + + " the *docs* folder. Files may be created one directory above this folder, in this folder, and in" + + " lower folders that are created by the site deploy tool. So if /var/www is your web" + + " root, then you should put /var/www/docs here. It will create an index file in /var/www, as" + + " well as in /var/www/docs, but the majority of files will be put in /var/www/docs/.")); + defaults.add(new Preferences.Preference(PASSWORD, "false", Preferences.Type.BOOLEAN, "Whether or not to use" + + " password authentication. If false, public key authentication will be used instead, and your" + + " system must be pre-configured for that. The password is interactively" + + " prompted for. If you wish to use non-interactive mode, you must use public key authentication.")); + + // General Options + defaults.add(new Preferences.Preference(DOCSBASE, "", Preferences.Type.STRING, "The base url of the docs." + + " This should begin with http:// or https://")); + defaults.add(new Preferences.Preference(SITEBASE, "", Preferences.Type.STRING, "The base url of the" + + " site (where \"home\" should be). This should begin with http:// or https://")); + + // Other options + defaults.add(new Preferences.Preference(SHOW_TEMPLATE_CREDIT, "true", Preferences.Type.BOOLEAN, "Whether or not" + + " to show the template credit. (Design by TEMPLATED logo in bottom.) If you set this to false, you" + + " agree that you have purchased a license for your deployment, and are legally allowed to suppress" + + " this from the templates.")); + defaults.add(new Preferences.Preference(GITHUB_BASE_URL, "", Preferences.Type.STRING, "The base url for" + + " the github project. If empty string, then the value " + DEFAULT_GITHUB_BASE_URL + " is used.")); + defaults.add(new Preferences.Preference(VALIDATOR_URL, "", Preferences.Type.STRING, "The validator url." + + " This service must be based on the https://validator.github.io/validator/ service. If the url" + + " is left blank, then using the --do-validation flag is an error. Generally, you will need" + + " to host your own validator for this solution to work, as running against a public service" + + " will undoubtably result in being blacklisted from the service. This should be something" + + " like http://localhost:8888/")); + defaults.add(new Preferences.Preference(POST_SCRIPT, "", Preferences.Type.FILE, + "The path to a shell script, or an mscript. This will be executed after" + + " the upload (and validation, if specified) is complete, and can be used" + + " to run custom procedures afterwards, for instance clearing any systems'" + + " caches as might be necessary. The script will be sent a list of all the" + + " the changed files as arguments. If the file name ends with .ms, it will" + + " be executed with the MethodScript engine. Otherwise, the file will be" + + " executed using the system shell. Leave this option empty to skip this" + + " step. If the file is specified, it must exist, and if it does not end" + + " in .ms, it must be executable.")); + defaults.add(new Preferences.Preference(INSTALL_URL, "", Preferences.Type.STRING, "The ec2 instance public url." + + " NOTE: The security group of the instance must be configured to allow access to port 22." + + " Ports 80 and" + + " 443 are also used, and should be opened, but that will not affect the installation process.")); + defaults.add(new Preferences.Preference(INSTALL_PEM_FILE, "", Preferences.Type.STRING, + "The path to the PEM file" + + " used for initial login.")); + defaults.add(new Preferences.Preference(INSTALL_PUB_KEYS, "", Preferences.Type.STRING, "A list of public keys" + + " to upload to, and add to the authorized_keys file on the server. These keys will not" + + " be used by this script, but can allow easier login in the future. If blank, no additional keys" + + " will be uploaded.")); + defaults.add(new Preferences.Preference(TRANSLATION_MEMORY_DB, "", Preferences.Type.FILE, "The path to a" + + " local checkout of a translation memory database. This may be empty, in which case" + + " internationalization options will not be available on the deployed site.")); + defaults.add(new Preferences.Preference(PRODUCTION_TRANSLATIONS, "", Preferences.Type.STRING, "The base url" + + " for the production version of the localization database. If blank, the official url is used, but" + + " this should be set to your fork of the official repository, or a local server that serves the" + + " translations if you are testing localizations.")); + + Preferences prefs = new Preferences("Site-Deploy", Logger.getLogger(SiteDeploy.class.getName()), defaults); + if(generatePrefs) { + prefs.init(sitedeploy); + System.out.println("Preferences file is now located at " + sitedeploy.getAbsolutePath() + + ". Please fill in the" + + " values, then re-run this command without the --generate-prefs option."); + System.exit(0); + } + prefs.init(sitedeploy); + + String username = prefs.getStringPreference(USERNAME); + String hostname = prefs.getStringPreference(HOSTNAME); + Integer port = prefs.getIntegerPreference(PORT); + String directory = prefs.getStringPreference(DIRECTORY); + Boolean usePassword = prefs.getBooleanPreference(PASSWORD); + String docsBase = prefs.getStringPreference(DOCSBASE); + String siteBase = prefs.getStringPreference(SITEBASE); + Boolean showTemplateCredit = prefs.getBooleanPreference(SHOW_TEMPLATE_CREDIT); + String githubBaseUrl = prefs.getStringPreference(GITHUB_BASE_URL); + String validatorUrl = prefs.getStringPreference(VALIDATOR_URL); + File finalizerScript = prefs.getFilePreference(POST_SCRIPT); + File translationMemoryDb = prefs.getFilePreference(TRANSLATION_MEMORY_DB); + String productionTranslations = prefs.getStringPreference(PRODUCTION_TRANSLATIONS); + + + if(!overridePostScript.equals("")) { + finalizerScript = new File(overridePostScript); + } + + { + // Check for config errors + List configErrors = new ArrayList<>(); + if("".equals(directory)) { + configErrors.add("Directory cannot be empty."); + } + if("".equals(docsBase)) { + configErrors.add("DocsBase cannot be empty."); + } + if("".equals(hostname)) { + configErrors.add("Hostname cannot be empty."); + } + if(!docsBase.startsWith("https://") && !docsBase.startsWith("http://")) { + configErrors.add("DocsBase must begin with either http:// or https://"); + } + if(!siteBase.startsWith("https://") && !siteBase.startsWith("http://")) { + configErrors.add("SiteBase must begin with either http:// or https://"); + } + if(!"localhost".equalsIgnoreCase(hostname) && !"127.0.0.1".equals(hostname)) { + if(port < 0 || port > 65535) { + configErrors.add("Port must be a number between 0 and 65535."); + } + if("".equals(username)) { + configErrors.add("Username cannot be empty."); + } + } + if(doValidation && "".equals(validatorUrl)) { + configErrors.add("Validation cannot occur while an empty validation url is specified in the config." + + " Either set a validator url, or re-run without the --do-validation flag."); + } + if(finalizerScript != null) { + if(!finalizerScript.exists()) { + configErrors.add("post-script file specified does not exist (" + finalizerScript.getCanonicalPath() + + ")"); + } else if(!finalizerScript.getPath().endsWith(".ms") && !finalizerScript.canExecute()) { + configErrors.add("post-script does not end in .ms, and is not executable"); + } + } + if(overrideIdRsa != null) { + if(!new File(overrideIdRsa).exists()) { + configErrors.add("override-id-rsa parameter points to a non-existent file."); + } + } + if(translationMemoryDb != null && !translationMemoryDb.exists()) { + configErrors.add("Translation memory db must point to an existing database. (" + translationMemoryDb + + ")"); + } + if("".equals(productionTranslations)) { + productionTranslations = PRODUCTION_TRANSLATIONS_URL; + } + + try { + new URL(productionTranslations); + } catch (MalformedURLException e) { + configErrors.add("Invalid URL for " + PRODUCTION_TRANSLATIONS + " value: " + + productionTranslations); + } + + if(!configErrors.isEmpty()) { + System.err.println("Invalid input. Check preferences in " + sitedeploy.getAbsolutePath() + + " and re-run"); + System.err.println(StringUtils.PluralTemplateHelper(configErrors.size(), + "Here is the %d error:", "Here are the %d errors:")); + System.err.println(" - " + StringUtils.Join(configErrors, "\n - ")); + System.exit(1); + } + } + + if(!directory.endsWith("/")) { + directory += "/"; + } + if(!docsBase.endsWith("/")) { + docsBase += "/"; + } + directory += MSVersion.LATEST; + docsBase += MSVersion.LATEST + "/"; + System.out.println("Using the following settings, loaded from " + sitedeploy.getCanonicalPath()); + System.out.println("username: " + username); + System.out.println("hostname: " + hostname); + System.out.println("port: " + port); + System.out.println("directory: " + directory); + System.out.println("docs-base: " + docsBase); + System.out.println("site-base: " + siteBase); + System.out.println("github-base-url: " + githubBaseUrl); + if(translationMemoryDb != null) { + System.out.println("Translation memory database: " + translationMemoryDb); + } + if(productionTranslations != null) { + System.out.println("Production translations url: " + productionTranslations); + } + if(finalizerScript != null) { + System.out.println("post-script: " + finalizerScript.getCanonicalPath()); + } + if(doValidation) { + System.out.println("validator-url: " + validatorUrl); + } + + if(usePassword && password != null) { + jline.console.ConsoleReader reader = null; + try { + Character cha = (char) 0; + reader = new jline.console.ConsoleReader(); + reader.setExpandEvents(false); + password = reader.readLine("Please enter your password: ", cha); + } finally { + if(reader != null) { + reader.close(); + } + } + } + DeploymentMethod deploymentMethod; + if("localhost".equalsIgnoreCase(hostname) || "127.0.0.1".equals(hostname)) { + deploymentMethod = new LocalDeploymentMethod(directory + "/"); + } else { + /** + * Remote will be in the format user@remote:port[:password]:/directory/ using the inputs from the user. + */ + String remote = username + "@" + hostname + ":" + port + + (password == null || "".equals(password) ? "" : (":" + password)) + ":" + directory + "/"; + deploymentMethod = new RemoteDeploymentMethod(remote); + } + + // Ok, all the configuration details are input and correct, so lets deploy now. + deploy(useLocalCache, siteBase, docsBase, deploymentMethod, doValidation, + showTemplateCredit, githubBaseUrl, validatorUrl, finalizerScript, clearProgressBar, overrideIdRsa, + translationMemoryDb, productionTranslations); + } + + private static void deploy(boolean useLocalCache, String siteBase, String docsBase, + DeploymentMethod deploymentMethod, boolean doValidation, boolean showTemplateCredit, + String githubBaseUrl, String validatorUrl, File finalizerScript, boolean clearProgressBar, + String overrideIdRsa, File translationMemoryDb, String productionTranslations) + throws IOException, InterruptedException { + new SiteDeploy(siteBase, docsBase, useLocalCache, deploymentMethod, doValidation, + showTemplateCredit, githubBaseUrl, validatorUrl, finalizerScript, clearProgressBar, + overrideIdRsa, translationMemoryDb, productionTranslations).deploy(); + } + + String apiJson; + String apiJsonVersion; + + @SuppressWarnings({"StringEquality", "ImplicitArrayToString"}) + private void deploy() throws InterruptedException, IOException { + apiJson = JSONValue.toJSONString(new APIBuilder().build()); + apiJsonVersion = getLocalMD5(StreamUtils.GetInputStream(apiJson)); + deployAPI(); + deployEventAPI(); + deployAPIJSON(); + deployFrontPages(); + deployLearningTrail(); + deployEvents(); + deployObjects(); + deployResources(); + deployJar(); + Runnable generateFinalizer = new Runnable() { + @Override + public void run() { + // Just us left, shut us down + if(generateQueue.getQueue().isEmpty()) { + generateQueue.shutdown(); + } else { + // Oops, we're a bit premature. Schedule us to run again. + generateQueue.submit(this); + } + } + }; + generateQueue.submit(generateFinalizer); + generateQueue.awaitTermination(1, TimeUnit.DAYS); + Runnable uploadFinalizer = new Runnable() { + @Override + public void run() { + if(uploadQueue.getQueue().isEmpty()) { + try { + writeMasterTranslations(); + } catch (Throwable ex) { + writeLog("Could not write out translations!", ex); + } + try { + writeSearchIndex(); + } catch (Throwable ex) { + writeLog("Could not write out search index!", ex); + } + uploadQueue.shutdown(); + } else { + uploadQueue.submit(this); + } + } + }; + uploadQueue.submit(uploadFinalizer); + uploadQueue.awaitTermination(1, TimeUnit.DAYS); + dm.waitForThreads(); + deploymentMethod.finish(); + // Next, we need to validate the pages + System.out.println(); + if(doValidation) { + System.out.println("Upload complete, running html5 validation"); + int filesValidated = 0; + int specifiedErrors = 0; + try { + for(Map.Entry e : uploadedPages.entrySet()) { + Map> headers = new HashMap<>(); + RequestSettings settings = new RequestSettings(); + //settings.setLogger(Logger.getLogger(SiteDeploy.class.getName())); + settings.setFollowRedirects(true); + headers.put("Content-Type", Arrays.asList(new String[]{"text/html; charset=utf-8"})); + headers.put("Content-Encoding", Arrays.asList(new String[]{"gzip"})); + headers.put("Accept-Encoding", Arrays.asList(new String[]{"gzip"})); + settings.setHeaders(headers); + + byte[] outStream = e.getValue().getBytes("UTF-8"); + ByteArrayOutputStream out = new ByteArrayOutputStream(outStream.length); + try(GZIPOutputStream gz = new GZIPOutputStream(out)) { + gz.write(outStream); + } + byte[] param = out.toByteArray(); + settings.setRawParameter(param); + settings.setTimeout(10000); + settings.setMethod(HTTPMethod.POST); + HTTPResponse response = WebUtility.GetPage(new URL(validatorUrl + "?out=gnu"), settings); + + if(response.getResponseCode() != 200) { + System.out.println(Static.MCToANSIColors("Response for " + + MCChatColor.AQUA + e.getKey() + MCChatColor.PLAIN_WHITE + ":")); + System.out.println(response.getContent()); + throw new IOException("Response was non-200, refusing to continue with validation"); + } + + String[] errors = response.getContentAsString().split("\n"); + int errorsDisplayed = 0; + for(String error : errors) { + GNUErrorMessageFormat gnuError = new GNUErrorMessageFormat(error); + String supressWarning = "info warning: Section lacks heading. Consider using “h2”-“h6”" + + " elements to add identifying headings to all sections."; + if(supressWarning.equals(gnuError.message())) { + continue; + } + // == on String, yes this is what I want + if(error == errors[0]) { + System.out.println(Static.MCToANSIColors("Response for " + + MCChatColor.AQUA + e.getKey() + MCChatColor.PLAIN_WHITE + ":")); + } + StringBuilder output = new StringBuilder(); + switch(gnuError.messageType()) { + case ERROR: + output.append(MCChatColor.RED); + break; + case WARNING: + output.append(MCChatColor.GOLD); + break; + } + output.append("line ").append(gnuError.fromLine()).append(" ") + .append(gnuError.message()).append(MCChatColor.PLAIN_WHITE); + String[] page = e.getValue().split("\n"); + for(int i = gnuError.fromLine(); i < gnuError.toLine() + 1; i++) { + output.append("\n").append(page[i - 1]); + } + output.append("\n"); + for(int i = 0; i < gnuError.fromColumn() - 1; i++) { + output.append(" "); + } + output.append(MCChatColor.RED).append("^").append(MCChatColor.PLAIN_WHITE); + System.out.println(Static.MCToANSIColors(output.toString())); + specifiedErrors++; + errorsDisplayed++; + } + filesValidated++; + } + } catch (IOException ex) { + System.err.println("Validation could not occur due to the following exception: " + ex.getMessage()); + ex.printStackTrace(System.err); + } + System.out.println("Files validated: " + filesValidated); + System.out.println("Errors found: " + specifiedErrors); + } + if(finalizerScript != null) { + System.out.println("Running post-script"); + if(finalizerScript.getPath().endsWith(".ms")) { + try { + Interpreter.startWithTTY(finalizerScript, filesChanged, false); + } catch (DataSourceException | URISyntaxException | Profiles.InvalidProfileException ex) { + ex.printStackTrace(System.err); + } + } else { + List args = new ArrayList<>(); + args.add(finalizerScript.getCanonicalPath()); + args.addAll(filesChanged); + CommandExecutor exec = new CommandExecutor(args.toArray(new String[args.size()])); + exec.setSystemInputsAndOutputs(); + exec.start(); + exec.waitFor(); + } + } + System.out.println("Done!"); + System.out.println("Summary of changed files (" + filesChanged.size() + ")"); + System.out.println(StringUtils.Join(filesChanged, "\n")); + if(masterMemories != null) { + System.out.println(masterMemories.size() + " translation segments exist."); + } + } + + private final String siteBase; + private final String docsBase; + private final String resourceBase; + private final String productionTranslations; + private final jline.console.ConsoleReader reader; + private final ThreadPoolExecutor generateQueue; + private final ThreadPoolExecutor uploadQueue; + private final AtomicInteger currentUploadTask = new AtomicInteger(0); + private final AtomicInteger totalUploadTasks = new AtomicInteger(0); + private final AtomicInteger currentGenerateTask = new AtomicInteger(0); + private final AtomicInteger totalGenerateTasks = new AtomicInteger(0); + private final List filesChanged = new ArrayList<>(); + private final PersistenceNetwork pn; + private final boolean useLocalCache; + private final DaemonManager dm = new DaemonManager(); + private Map lc = null; + private DeploymentMethod deploymentMethod; + private final boolean doValidation; + private final Map uploadedPages = new HashMap<>(); + private final boolean showTemplateCredit; + private final String githubBaseUrl; + private final String validatorUrl; + private final File finalizerScript; + private final boolean clearProgressBar; + private final String overrideIdRsa; + private final File translationMemoryDb; + + /** + * The master memories object exists purely to prevent duplicate translations being requested from the + * translation server for new translations. However, identical keys may be translated differently + * on different pages, and that's ok. The TM that gets set in here is non deterministic, but only the + * first time, because after that, each page should have it's own TM, and if the one that was picked + * was wrong, then once it's manually corrected, it will stay correct forever. + */ + private final TranslationMaster masterMemories; + + private static final String EDIT_THIS_PAGE_PREAMBLE + = "Find a bug in this page? Edit this page yourself, then submit a pull request."; + private static final String DEFAULT_GITHUB_BASE_URL + = "https://github.com/EngineHub/CommandHelper/edit/master/src/main/%s"; + + @SuppressWarnings({"unchecked", "checkstyle:regexpsingleline"}) + private SiteDeploy(String siteBase, String docsBase, boolean useLocalCache, + DeploymentMethod deploymentMethod, boolean doValidation, boolean showTemplateCredit, + String githubBaseUrl, String validatorUrl, File finalizerScript, boolean clearProgressBar, + String overrideIdRsa, File translationMemoryDb, String productionTranslations) + throws IOException { + this.siteBase = siteBase; + this.docsBase = docsBase; + this.resourceBase = docsBase + "resources/"; + this.finalizerScript = finalizerScript; + this.reader = new jline.console.ConsoleReader(); + this.generateQueue = new ThreadPoolExecutor(1, 1, + 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue<>(), + new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + return new Thread(r, "generateQueue"); + } + }); + this.uploadQueue = new ThreadPoolExecutor(1, 1, + 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue<>(), + new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + return new Thread(r, "uploadQueue"); + } + }); + this.useLocalCache = useLocalCache; + this.deploymentMethod = deploymentMethod; + this.doValidation = doValidation; + this.showTemplateCredit = showTemplateCredit; + this.validatorUrl = validatorUrl; + if(githubBaseUrl.isEmpty()) { + githubBaseUrl = DEFAULT_GITHUB_BASE_URL; + } + this.githubBaseUrl = githubBaseUrl; + this.clearProgressBar = clearProgressBar; + pn = getPersistenceNetwork(); + if(pn != null) { + try { + String localCache = pn.get(new String[]{"site_deploy", "local_cache"}); + if(localCache == null) { + localCache = "{}"; + } + lc = (Map) JSONValue.parse(localCache); + } catch (DataSourceException | IllegalArgumentException ex) { + writeLog("Could not read in local cache", ex); + notificationAboutLocalCache = false; + } + } + this.overrideIdRsa = overrideIdRsa; + this.translationMemoryDb = translationMemoryDb; + this.productionTranslations = productionTranslations; + if(translationMemoryDb != null) { + writeStatus("Loading translation memories, this may take a while."); + this.masterMemories = new TranslationMaster(translationMemoryDb, (current, total) -> { + if(!clearProgressBar) { + return; + } + writeStatus("Loading translation memories, this may take a while (" + ((int) current) + "/" + + ((int) total) + ")"); + }); + writeStatus("Done loading translation memories."); + } else { + this.masterMemories = null; + } + } + + /** + * Returns the persistence network, or null if it couldn't be generated for whatever reason. + * + * @return + */ + public static PersistenceNetwork getPersistenceNetwork() { + PersistenceNetwork p; + try { + p = new PersistenceNetworkImpl(MethodScriptFileLocations.getDefault().getPersistenceConfig(), + new URI("sqlite://" + MethodScriptFileLocations.getDefault().getDefaultPersistenceDBFile() + .getCanonicalFile().toURI().getRawSchemeSpecificPart().replace('\\', '/')), + new ConnectionMixinFactory.ConnectionMixinOptions()); + } catch (DataSourceException | URISyntaxException | IOException ex) { + p = null; + } + return p; + } + + private void resetLine() throws IOException { + if(clearProgressBar) { + reader.getOutput().write("\u001b[1G\u001b[K"); + reader.flush(); + } else { + reader.getOutput().write("\n"); + reader.flush(); + } + } + + private synchronized void writeLog(String log, Throwable e) { + try { + reader.getOutput().write("\n" + log + "\n"); + e.printStackTrace(new PrintWriter(reader.getOutput())); + reader.getOutput().write("\n"); + reader.flush(); + } catch (IOException ex) { + System.err.println("Failure while logging exception!"); + System.err.println("Original exception:"); + e.printStackTrace(System.err); + System.err.println("Logging exception:"); + ex.printStackTrace(System.err); + } + } + + private synchronized void writeStatus(String additionalInfo) { + int generatePercent = 0; + if(totalGenerateTasks.get() != 0) { + generatePercent = (int) (currentGenerateTask.get() / ((double) totalGenerateTasks.get()) * 100.0); + } + int uploadPercent = 0; + if(totalUploadTasks.get() != 0) { + uploadPercent = (int) (currentUploadTask.get() / ((double) totalUploadTasks.get()) * 100.0); + } + String message = "Generate progress: " + currentGenerateTask.get() + "/" + totalGenerateTasks.get() + + " (" + generatePercent + "%)" + + "; Upload progress: " + currentUploadTask.get() + "/" + totalUploadTasks.get() + + " (" + uploadPercent + "%)" + + "; " + additionalInfo; + try { + resetLine(); + reader.getOutput().write(message); + reader.flush(); + } catch (IOException ex) { + System.out.println(message); + } + } + + private Map getStandardGenerators() { + Map g = new HashMap<>(); + g.put("resourceBase", (Generator) (String... args) -> SiteDeploy.this.resourceBase); + g.put("branding", (Generator) (String... args) -> Implementation.GetServerType().getBranding()); + g.put("siteRoot", (Generator) (String... args) -> SiteDeploy.this.siteBase); + g.put("productionTranslations", (args) -> SiteDeploy.this.productionTranslations); + g.put("docsBase", (Generator) (String... args) -> SiteDeploy.this.docsBase); + g.put("apiJsonVersion", (Generator) (String... args) -> apiJsonVersion); + /** + * The cacheBuster template is meant to make it easier to deal with caching of resources. The template allows + * you to specify the resource, and it creates a path to the resource using resourceBase, but it also appends a + * hash of the file, so that as the file changes, so does the hash (using a ?v=hash query string). Most + * resources live in /siteDeploy/resources/*, and so the shorthand is to use + * %%cacheBuster|path/to/resource.css%%. However, this isn't always correct, because resources can live all over + * the place. In that case, you should use the following format: + * %%cacheBuster|/absolute/path/to/resource.css|path/to/resource/in/html.css%% + */ + g.put("cacheBuster", (Generator) (String... args) -> { + String resourceLoc = SiteDeploy.this.resourceBase + args[0]; + String loc = args[0]; + if(!loc.startsWith("/")) { + loc = "/siteDeploy/resources/" + loc; + } else { + resourceLoc = SiteDeploy.this.resourceBase + args[1]; + } + String hash = "0"; + try { + InputStream in = SiteDeploy.class.getResourceAsStream(loc); + if(in == null) { + throw new RuntimeException("Could not find " + loc + + " in resources folder for cacheBuster template"); + } + hash = getLocalMD5(in); + } catch (IOException ex) { + writeLog(null, ex); + } + return resourceLoc + "?v=" + hash; + }); + final Generator learningTrailGen = (String... args) -> { + String learningTrail = + StreamUtils.GetString(SiteDeploy.class.getResourceAsStream("/siteDeploy/LearningTrail.json")); + List>>> ret = new ArrayList<>(); + @SuppressWarnings("unchecked") + List>> lt = (List>>) JSONValue.parse(learningTrail); + for(Map> l : lt) { + for(Map.Entry> e : l.entrySet()) { + String category = e.getKey(); + List> catInfo = new ArrayList<>(); + for(Object ll : e.getValue()) { + Map pageInfo = new LinkedHashMap<>(); + String page = null; + String name = null; + if(ll instanceof String) { + name = page = (String) ll; + } else if(ll instanceof Map) { + @SuppressWarnings("unchecked") + Map p = (Map) ll; + if(p.entrySet().size() != 1) { + throw new RuntimeException("Invalid JSON for learning trail"); + } + for(Map.Entry ee : p.entrySet()) { + page = ee.getKey(); + name = ee.getValue(); + } + } else { + throw new RuntimeException("Invalid JSON for learning trail"); + } + assert page != null && name != null; + boolean exists; + if(page.contains(".")) { + // We can't really check this, because it might be a synthetic page, like + // api.json. So we just have to set it to true. + exists = true; + } else { + exists = SiteDeploy.class.getResourceAsStream("/docs/" + page) != null; + } + pageInfo.put("name", name); + pageInfo.put("page", page); + pageInfo.put("category", category); + pageInfo.put("exists", Boolean.toString(exists)); + catInfo.add(pageInfo); + } + Map>> m = new HashMap<>(); + m.put(category, catInfo); + ret.add(m); + } + } + return JSONValue.toJSONString(ret); + }; + g.put("js_string_learning_trail", (Generator) (String... args) -> { + String g1 = learningTrailGen.generate(args); + g1 = g1.replaceAll("\\\\", "\\\\"); + g1 = g1.replaceAll("\"", "\\\\\""); + return g1; + }); + g.put("learning_trail", learningTrailGen); + /** + * If showTemplateCredit is false, then this will return "display: none;" otherwise, it will return an empty + * string. + */ + g.put("showTemplateCredit", (Generator) (String... args) -> showTemplateCredit ? "" : "display: none;"); + return g; + } + + /** + * Writes an arbitrary string to a file on the remote. + * + * @param contents The string to write + * @param toLocation The location of the remote file + */ + private void writeFromString(final String contents, final String toLocation) { + writeFromStream(StreamUtils.GetInputStream(contents), toLocation); + } + + private boolean notificationAboutLocalCache = true; + + /** + * Writes an arbitrary stream to a file on the remote. + * + * @param contents The input stream to write + * @param toLocation The location to write to + */ + private void writeFromStream(final InputStream contents, final String toLocation) { + uploadQueue.submit(() -> { + try { + writeStatus("Currently uploading " + toLocation); + // Read the contents only once + byte[] c = StreamUtils.GetBytes(contents); + contents.close(); + boolean skipUpload = false; + String hash = null; + if(pn != null) { + if(notificationAboutLocalCache) { + hash = getLocalMD5(new ByteArrayInputStream(c)); + try { + if(lc.containsKey(deploymentMethod.getID() + toLocation)) { + if(useLocalCache) { + String cacheHash = lc.get(deploymentMethod.getID() + toLocation); + if(cacheHash.equals(hash)) { + skipUpload = true; + } + } + } + } catch (IllegalArgumentException ex) { + writeLog("Could not use local cache", ex); + notificationAboutLocalCache = false; + } + } + } + if(!skipUpload && deploymentMethod.deploy(new ByteArrayInputStream(c), toLocation, overrideIdRsa)) { + filesChanged.add(toLocation); + } + if(pn != null && notificationAboutLocalCache && hash != null) { + try { + lc.put(deploymentMethod.getID() + toLocation, hash); + pn.set(dm, new String[]{"site_deploy", "local_cache"}, JSONValue.toJSONString(lc)); + } catch (DataSourceException | ReadOnlyException | IllegalArgumentException ex) { + writeLog(null, ex); + notificationAboutLocalCache = false; + } + } + currentUploadTask.addAndGet(1); + writeStatus(""); + } catch (Throwable ex) { + writeLog("Failed while uploading " + toLocation, ex); + generateQueue.shutdownNow(); + uploadQueue.shutdownNow(); + } + }); + totalUploadTasks.addAndGet(1); + } + + static synchronized String getLocalMD5(InputStream localFile) throws IOException { + try { + byte[] f = StreamUtils.GetBytes(localFile); + MessageDigest digest = java.security.MessageDigest.getInstance("MD5"); + digest.update(f); + String hash = StringUtils.toHex(digest.digest()).toLowerCase(); + return hash; + } catch (NoSuchAlgorithmException ex) { + throw new RuntimeException(ex); + } finally { + localFile.close(); + } + } + + /** + * Writes a resource as is (no template replacements) to + * + * @param resource + * @param toLocation + */ + private void writeResource(final String resource, final String toLocation) { + writeFromStream(SiteDeploy.class.getResourceAsStream(resource), toLocation); + } + + /** + * Most pages should use this method instead of the other methods. This takes care of all the steps, including + * substituting the body into the frame, and handling all the other connections. + * + * @param title The title of the page + * @param resource The absolute path (or path relative to SiteDeploy.java) of the resource to use + * @param toLocation The location on the remote server + * @param keywords A list of keywords to be added to the meta tag on the page + * @param description A description of the page + * @return + */ + private void writePageFromResource(String title, String resource, String toLocation, List keywords, + String description) { + String s = StreamUtils.GetString(SiteDeploy.class.getResourceAsStream(resource.replace('\\', '/'))); + s += "

" + + EDIT_THIS_PAGE_PREAMBLE + + String.format(githubBaseUrl, "resources" + resource) + + EDIT_THIS_PAGE_POSTAMBLE + + "

"; + writePage(title.replace("_", " "), s, ResultType.ARTICLE, toLocation, keywords, description); + } + + /** + * Most pages should use this method instead of the other methods. This takes care of all the steps, including + * substituting the body into the frame, and handling all the other connections. + * + * @param title The title of the page + * @param resource The absolute path (or path relative to SiteDeploy.java) of the resource to use + * @param toLocation The location on the remote server + * @return + */ + private void writePageFromResource(String title, String resource, String toLocation) { + writePageFromResource(title, resource, toLocation, null, ""); + } + + /** + * Most pages should use this method instead of the other methods. This takes care of all the steps, including + * substituting the body into the frame, and handling all the other connections. + * + * @param title The title of the page + * @param body The content body + * @param type The page type + * @param toLocation the location on the remote server + */ + private void writePage(String title, String body, ResultType type, String toLocation) { + writePage(title, body, type, toLocation, null, ""); + } + + /** + * Most pages should use this method instead of the other methods. This takes care of all the steps, including + * substituting the body into the frame, and handling all the other connections. + * + * @param body The content body + * @param type The type of page this is + * @param title The title of the page + * @param toLocation the location on the remote server + * @param keywords A list of keywords to be added to the meta tag on the page + * @param description A description of the page's content + */ + private void writePage(final String title, final String body, final ResultType type, + final String toLocation, + List keywords, final String description) { + if(keywords == null) { + keywords = new ArrayList<>(); + } + final List kw = keywords; + generateQueue.submit(() -> { + try { + String bW = body; + if(!bW.contains(EDIT_THIS_PAGE_PREAMBLE)) { + bW += "

" + + EDIT_THIS_PAGE_PREAMBLE + + String.format(githubBaseUrl, "java/" + SiteDeploy.class.getName().replace('.', '/')) + + ".java" + + EDIT_THIS_PAGE_POSTAMBLE + + "

"; + } + try { + writeStatus("Currently generating " + toLocation); + if(translationMemoryDb != null) { + generateQueue.submit(() -> { + try { + createTranslationMemory(title, toLocation, type, body); + } catch (Throwable t) { + writeLog("While generating translation memory for " + toLocation + "an error occurred: ", + t); + } + currentGenerateTask.addAndGet(1); + }); + totalGenerateTasks.addAndGet(1); + } + // First, substitute the templates in the body + final String b; + try { + Map standard = getStandardGenerators(); + standard.putAll(DocGenTemplates.GetGenerators()); + b = DocGenTemplates.DoTemplateReplacement(bW, standard); + } catch (Exception ex) { + if(ex instanceof GenerateException) { + writeLog("Failed to substitute template" + + " while trying to upload resource to " + toLocation, ex); + } else { + writeLog(null, ex); + } + reader.flush(); + generateQueue.shutdownNow(); + uploadQueue.shutdownNow(); + return; + } + // Second, add the template %%body%% and replace that in the frame + final Map g = new HashMap<>(); + g.put("body", (Generator) (String... args) -> b); + g.put("bodyEscaped", (Generator) (String... args) -> { + String s = b.replaceAll("\\\\", "\\\\\\\\").replaceAll("'", "\\\\'") + .replaceAll("\r", "").replaceAll("\n", "\\\\n"); + s = s.replaceAll("", ""); + return s; + }); + g.put("title", (Generator) (String... args) -> title); + g.put("useHttps", (Generator) (String... args) + -> SiteDeploy.this.siteBase.startsWith("https") ? "true" : "false"); + g.put("keywords", (Generator) (String... args) -> { + List k = new ArrayList<>(kw); + k.add(Implementation.GetServerType().getBranding()); + return StringUtils.Join(k, ", "); + }); + g.put("description", (Generator) (String... args) -> description); + g.putAll(getStandardGenerators()); + g.putAll(DocGenTemplates.GetGenerators()); + String frame = StreamUtils.GetString(SiteDeploy.class + .getResourceAsStream("/siteDeploy/frame.html")); + final String bb = DocGenTemplates.DoTemplateReplacement(frame, g); + // Write out using writeFromString + uploadedPages.put(toLocation, bb); + writeFromString(bb, toLocation); + currentGenerateTask.addAndGet(1); + writeStatus(""); + } catch (Exception ex) { + writeLog("While writing " + toLocation + " the following error occurred:", ex); + } + } catch (Throwable t) { + writeLog("Failure!", t); + } + }); + totalGenerateTasks.addAndGet(1); + } + + /** + * Translation memories allow strings within the page to be localized in to different languages. The translations + * themselves are by default created by machine translation, but it's important to be able to override these when + * necessary, so the translation memories (tmem) files are created and committed to a repository, so PRs can + * be created. This function is charged with orchestrating the process, which is comprised of several smaller tasks. + * @param toLocation + * @param resultType + * @param inputString + */ + private void createTranslationMemory(String title, String toLocation, ResultType type, + String inputString) throws IOException { + toLocation = StringUtils.replaceLast(toLocation, "\\.html", ".tmem.xml"); + String location = "%s/docs/" + MSVersion.LATEST + "/" + toLocation; + writeStatus("Creating memory file for " + location); + masterMemories.createTranslationMemory(title, location, type, inputString); + } + + private void writeMasterTranslations() throws IOException { + if(masterMemories != null) { + writeStatus("Writing out translation database"); + masterMemories.save((current, total) -> { + if(!clearProgressBar) { + return; + } + writeStatus("Writing out translation database (" + ((int) current) + "/" + + ((int) total) + ")"); + }); + } + } + + /** + * If the masterMemories is not null, this writes out the search index file. In any case, writes out a small page + * (which can be loaded on each page load) + * called "searchIndexExists.json" which states whether or not the index exists, so the search bar can be + * shown only if the index is present. + * @throws IOException + */ + private void writeSearchIndex() throws IOException { + if(masterMemories != null) { + writeStatus("Generating and uploading search index"); + MasterSearchIndex index = masterMemories.getSearchIndex(); + writeFromString(index.getIndex(), "searchIndex.json"); + writeFromString("{\"searchIndexExists\":true}", "searchIndexExists.json"); + } else { + writeFromString("{\"searchIndexExists\":false}", "searchIndexExists.json"); + } + } + + /** + * Pages deployed: All pages under the siteDeploy/resources folder. They are transferred as is, including + * recursively looking through the folder structure. Binary files are also supported. index.js (after template + * replacement) + */ + private void deployResources() { + generateQueue.submit(() -> { + try { + writeStatus("Generating resources"); + File root = new File(SiteDeploy.class.getResource("/siteDeploy/resources").toExternalForm()); + ZipReader reader1 = new ZipReader(root); + Queue q = new LinkedList<>(); + q.addAll(Arrays.asList(reader1.listFiles())); + while(q.peek() != null) { + ZipReader r = new ZipReader(q.poll()); + if(r.isDirectory()) { + q.addAll(Arrays.asList(r.listFiles())); + } else { + String fileName = r.getFile().getAbsolutePath().replaceFirst(Pattern.quote(reader1.getFile() + .getAbsolutePath()), ""); + writeStatus("Generating " + fileName); + writeFromStream(r.getInputStream(), "resources" + fileName); + } + } + } catch (IOException ex) { + writeLog(null, ex); + } + String indexJs = StreamUtils.GetString(SiteDeploy.class.getResourceAsStream("/siteDeploy/index.js")); + try { + writeFromString(DocGenTemplates.DoTemplateReplacement(indexJs, getStandardGenerators()), + "resources/js/index.js"); + } catch (Generator.GenerateException ex) { + writeLog("GenerateException in /siteDeploy/index.js", ex); + } + currentGenerateTask.addAndGet(1); + }); + totalGenerateTasks.addAndGet(1); + } + + /** + * Pages deployed: index.html privacy_policy.html sponsors.html + */ + private void deployFrontPages() { + generateQueue.submit(() -> { + try { + writePageFromResource(MSVersion.LATEST.toString() + " - Docs", "/siteDeploy/VersionFrontPage", + "index.html", + Arrays.asList(new String[]{MSVersion.LATEST.toString(), "better than skript"}), + "Front page for " + + MSVersion.LATEST.toString()); + currentGenerateTask.addAndGet(1); + } catch (Throwable t) { + writeLog("Failure!", t); + } + }); + totalGenerateTasks.addAndGet(1); + generateQueue.submit(() -> { + try { + writePageFromResource("Privacy Policy", "/siteDeploy/privacy_policy.html", "privacy_policy.html", + Arrays.asList(new String[]{"privacy policy"}), "Privacy policy for the site"); + currentGenerateTask.addAndGet(1); + } catch (Throwable t) { + writeLog("Failure!", t); + } + }); + totalGenerateTasks.addAndGet(1); + generateQueue.submit(() -> { + try { + writePageFromResource(Implementation.GetServerType().getBranding(), "/siteDeploy/FrontPage", + "../../index.html", + Arrays.asList(new String[]{"index", "front page"}), "The front page for " + + Implementation.GetServerType().getBranding()); + currentGenerateTask.addAndGet(1); + } catch (Throwable t) { + writeLog("Failure!", t); + } + }); + totalGenerateTasks.addAndGet(1); + generateQueue.submit(() -> { + try { + writePageFromResource(Implementation.GetServerType().getBranding(), "/siteDeploy/Sponsors", + "../../sponsors.html", + Arrays.asList(new String[]{"index", "front page"}), "Sponsors of MethodScript"); + currentGenerateTask.addAndGet(1); + } catch (Throwable t) { + writeLog("Failure!", t); + } + }); + totalGenerateTasks.addAndGet(1); + generateQueue.submit(() -> { + try { + writePageFromResource("Doc Directory", "/siteDeploy/DocDirectory", "../index.html", + Arrays.asList(new String[]{"directory"}), "The directory for all documented versions"); + currentGenerateTask.addAndGet(1); + } catch (Throwable t) { + writeLog("Failure!", t); + } + }); + totalGenerateTasks.addAndGet(1); + generateQueue.submit(() -> { + try { + writePageFromResource("404 Not Found", "/siteDeploy/404", "../../404.html", + Arrays.asList(new String[]{"404"}), "Page not found"); + currentGenerateTask.addAndGet(1); + } catch (Throwable t) { + writeLog("Failure!", t); + } + }); + totalGenerateTasks.addAndGet(1); + generateQueue.submit(() -> { + try { + writeFromString("{\"currentVersion\":\"" + MSVersion.LATEST.getVersionString() + "\"}", + "../../currentVersion.json"); + currentGenerateTask.addAndGet(1); + } catch (Throwable t) { + writeLog("Failure!", t); + } + }); + totalGenerateTasks.addAndGet(1); + } + + /** + * Pages deployed: All files from /docs/* + */ + private void deployLearningTrail() { + generateQueue.submit(() -> { + try { + File root = new File(SiteDeploy.class.getResource("/docs").toExternalForm()); + ZipReader zReader = new ZipReader(root); + String path = Pattern.quote(zReader.getFile().getAbsolutePath()); + for(File r : zReader.listFiles()) { + String filename = r.getAbsolutePath().replaceFirst(path, ""); + writePageFromResource(r.getName(), "/docs" + filename, r.getName() + ".html", + Arrays.asList(new String[]{r.getName().replace('_', ' ')}), "Learning trail page for " + + r.getName().replace('_', ' ')); + } + } catch (IOException ex) { + writeLog(null, ex); + } + currentGenerateTask.addAndGet(1); + }); + totalGenerateTasks.addAndGet(1); + } + + private void deployAPI() { + generateQueue.submit(() -> { + try { + writeStatus("Generating API"); + Set> functionClasses = new TreeSet<>( + (Class o1, Class o2) -> { + Function f1 = ReflectionUtils.instantiateUnsafe(o1); + Function f2 = ReflectionUtils.instantiateUnsafe(o2); + int result = f1.compareTo(f2); + if(result == 0) { + // Functions with the same name + return o1.getPackageName().compareTo(o2.getPackageName()); + } + return result; + }); + functionClasses.addAll(ClassDiscovery.getDefaultInstance() + .loadClassesWithAnnotationThatExtend(api.class, Function.class)); + // A map of where it maps the enclosing class to the list of function rows, which contains a list of + // table cells. + Map, List>> data = new TreeMap<>((Class o1, Class o2) + -> o1.getCanonicalName().compareTo(o2.getCanonicalName())); + List hiddenFunctions = new ArrayList<>(); + for(Class functionClass : functionClasses) { + if(LLVMFunction.class.isAssignableFrom(functionClass)) { + // Skip these for now. + continue; + } + try { + if(!data.containsKey(functionClass.getEnclosingClass())) { + data.put(functionClass.getEnclosingClass(), new ArrayList<>()); + } + List> d = data.get(functionClass.getEnclosingClass()); + List c = new ArrayList<>(); + // function name, returns, arguments, throws, description, restricted + final Function f; + try { + f = ReflectionUtils.instantiateUnsafe(functionClass); + } catch (ReflectionUtils.ReflectionException ex) { + throw new RuntimeException("While trying to construct " + functionClass + + ", got the following", ex); + } + final DocGen.DocInfo di = new DocGen.DocInfo(f.docs()); + // If the function is hidden, we don't want to put it on the main page by default. Regardless, + // we do want to generate the function page, it will just remain unlinked. + generateQueue.submit(() -> { + try { + generateFunctionDocs(f, di); + } catch (Throwable ex) { + ex.printStackTrace(System.err); + } + }); + if(f.since().equals(MSVersion.V0_0_0)) { + // Don't add these + continue; + } + if(f.getClass().getAnnotation(hide.class) != null) { + hiddenFunctions.add(f.getName()); + } + c.add("[[API/functions/" + f.getName() + "|" + f.getName() + "]]()"); + c.add(di.ret); + c.add(di.args); + List exc = new ArrayList<>(); + if(f.thrown() != null) { + for(Class e : f.thrown()) { + // for the API reference page we want the simple name + String[] splitType = ReflectionUtils.instantiateUnsafe(e).getName().split("\\."); + exc.add("{{object|" + (splitType[splitType.length - 1]) + "}}"); + } + } + c.add(StringUtils.Join(exc, "
")); + StringBuilder desc = new StringBuilder(); + desc.append(HTMLUtils.escapeHTML(di.desc)); + if(di.extendedDesc != null) { + desc.append(" [[API/functions/").append(f.getName()).append("|See more...]]
\n"); + } + try { + if(f.examples() != null && f.examples().length > 0) { + desc.append("
([[API/functions/").append(f.getName()) + .append("#Examples|Examples...]])\n"); + } + } catch (ConfigCompileException | NoClassDefFoundError ex) { + writeLog(null, ex); + } + c.add(desc.toString()); + c.add("" + + (f.isRestricted() ? "Yes" : "No") + + ""); + d.add(c); + } catch (Throwable t) { + writeLog("Failure while generating " + functionClass, t); + } + } + // System.out.println("Functions to be deployed: " + data + "\n\n"); + // data is now constructed. + StringBuilder b = new StringBuilder(); + b.append("\n"); + for(Map.Entry, List>> e : data.entrySet()) { + Class clazz = e.getKey(); + List> clazzData = e.getValue(); + if(clazzData.isEmpty()) { + // If there are no functions in the class, don't display it. This is most likely to happen + // if all the class's functions are hidden with @hide. + continue; + } + try { + b.append("== ").append(clazz.getSimpleName()).append(" ==\n"); + String docs = (String) ReflectionUtils.invokeMethod(clazz, null, "docs"); + b.append("
").append(docs).append("
\n\n"); + b.append("{| class=\"sticky\"\n|-\n"); + b.append("! scope=\"col\" width=\"8%\" | Function Name\n" + + "! scope=\"col\" width=\"4%\" | Returns\n" + + "! scope=\"col\" width=\"16%\" | Arguments\n" + + "! scope=\"col\" width=\"8%\" | Throws\n" + + "! scope=\"col\" width=\"62%\" | Description\n" + + "! scope=\"col\" width=\"2%\" |" + + " Res\n"); + for(List row : clazzData) { + b.append("|-"); + String fName = row.get(0); + fName = fName.replaceAll(".*\\[\\[.*?\\|(.*?)\\]\\].*", "$1"); + if(hiddenFunctions.contains(fName)) { + b.append(" class=\"hiddenFunction\""); + } + b.append("\n"); + for(String cell : row) { + b.append("| ").append(cell).append("\n"); + } + + } + b.append("|}\n"); + b.append("

Back to top

\n"); + } catch (Error ex) { + writeLog("While processing " + clazz + " got:", ex); + } + } + b.append(""); + b.append(""); + writePage("API", b.toString(), ResultType.API, "API.html", + Arrays.asList(new String[]{"API", "functions"}), + "A list of all " + Implementation.GetServerType().getBranding() + " functions"); + currentGenerateTask.addAndGet(1); + } catch (Throwable ex) { + ex.printStackTrace(System.err); + } + }); + totalGenerateTasks.addAndGet(1); + } + + private void generateFunctionDocs(Function f, DocGen.DocInfo docs) { + writeStatus("Generating function docs for " + f.getName()); + StringBuilder page = new StringBuilder(); + page.append("== ").append(f.getName()).append(" ==\n"); + page.append("
").append(docs.desc).append("
\n"); + + page.append("=== Vital Info ===\n"); + page.append("{| style=\"width: 40%;\" cellspacing=\"1\" cellpadding=\"1\" border=\"1\" class=\"wikitable\"\n"); + page.append("|-\n" + + "! scope=\"col\" width=\"20%\" | \n" + + "! scope=\"col\" width=\"80%\" | \n" + + "|-\n" + + "! scope=\"row\" | Name\n" + + "| ").append(f.getName()).append("\n" + + "|-\n" + + "! scope=\"row\" | Returns\n" + + "| ").append(docs.ret).append("\n" + + "|-\n" + + "! scope=\"row\" | Usages\n" + + "| ").append(docs.args).append("\n" + + "|-\n" + + "! scope=\"row\" | Throws\n" + + "| "); + List exceptions = new ArrayList<>(); + if(f.thrown() != null) { + for(Class c : f.thrown()) { + String t = ClassDiscovery.GetClassAnnotation(c, typeof.class).value(); + exceptions.add("[[../objects/" + t + "|" + t + "]]"); + } + } + page.append(StringUtils.Join(exceptions, "
")); + page.append("\n" + + "|-\n" + + "! scope=\"row\" | Since\n" + + "| ").append(f.since()).append("\n" + + "|-\n" + + "! scope=\"row\" | Restricted\n"); + page.append("|
" + ).append(f.isRestricted() ? "Yes" : "No").append("
\n" + + "|-\n" + + "! scope=\"row\" | Optimizations\n" + + "| "); + String optimizationMessage = "None"; + if(f instanceof Optimizable) { + Set options = ((Optimizable) f).optimizationOptions(); + List list = new ArrayList<>(); + for(Optimizable.OptimizationOption option : options) { + list.add("[[../../Optimizer#" + option.name() + "|" + option.name() + "]]"); + } + optimizationMessage = StringUtils.Join(list, "
"); + } + page.append(optimizationMessage); + page.append("\n|}"); + if(docs.extendedDesc != null) { + page.append("
").append(docs.extendedDesc).append("
"); + } + + String[] usages = docs.originalArgs.split("\\|"); + StringBuilder usageBuilder = new StringBuilder(); + for(String usage : usages) { + usageBuilder.append("
\n").append(f.getName()).append("(").append(usage.trim()).append(")\n
"); + } + page.append("\n=== Usages ===\n"); + page.append(usageBuilder.toString()); + + StringBuilder exampleBuilder = new StringBuilder(); + try { + if(f.examples() != null && f.examples().length > 0) { + int count = 1; + //If the output was automatically generated, change the color of the pre + for(ExampleScript es : f.examples()) { + exampleBuilder.append("====Example ").append(count).append("====\n") + .append(HTMLUtils.escapeHTML(es.getDescription())).append("\n\n" + + "Given the following code:\n"); +// exampleBuilder.append(SimpleSyntaxHighlighter.Highlight(es.getScript(), true)).append("\n"); + exampleBuilder.append("<%CODE|").append(es.getScript()).append("%>\n"); + String style = ""; + exampleBuilder.append("\n\nThe output "); + if(es.isAutomatic()) { + style = " background-color: #BDC7E9;"; + exampleBuilder.append("would"); + } else { + exampleBuilder.append("might"); + } + exampleBuilder.append(" be:\n
<%NOWIKI|").append(es.getOutput())
+							.append("%>").append("
\n"); + count++; + } + } else { + exampleBuilder.append("Sorry, there are no examples for this function! :(\n"); + } + } catch (Exception ex) { + exampleBuilder.append("Error while compiling the examples for ").append(f.getName()); + } + + page.append("\n=== Examples ===\n"); + page.append(exampleBuilder.toString()); + + Class[] seeAlso = f.seeAlso(); + String seeAlsoText = ""; + if(seeAlso != null && seeAlso.length > 0) { + seeAlsoText += "===See Also===\n"; + boolean first = true; + for(Class c : seeAlso) { + if(!first) { + seeAlsoText += ", "; + } + first = false; + if(Function.class.isAssignableFrom(c)) { + Function f2 = (Function) ReflectionUtils.newInstance(c); + seeAlsoText += "[[API/functions/" + f2.getName() + ".html|" + f2.getName() + "]]"; + } else if(Template.class.isAssignableFrom(c)) { + Template t = (Template) ReflectionUtils.newInstance(c); + seeAlsoText += "[[" + t.getPath() + t.getName() + "|Learning Trail: " + t.getDisplayName() + "]]"; + } else { + throw new Error("Unsupported class found in @seealso annotation: " + c.getName()); + } + } + } + page.append(seeAlsoText); + + Class container = f.getClass(); + while(container.getEnclosingClass() != null) { + container = container.getEnclosingClass(); + } + int lineNum = 0; + try { + MethodMirror m = ClassDiscovery.getDefaultInstance().forName(f.getClass().getName()) + .getMethod("docs", new Class[0]); + lineNum = m.getLineNumber(); + } catch (NoSuchMethodException | ClassNotFoundException ex) { + // Oh well. + } + String bW = "

" + + EDIT_THIS_PAGE_PREAMBLE + + String.format(githubBaseUrl, "java/" + container.getName().replace('.', '/')) + ".java#L" + + (lineNum < 10 ? lineNum : lineNum + 10) // Add 10, so we scroll a bit more in view + + EDIT_THIS_PAGE_POSTAMBLE + + " (Note this page is automatically generated from the documentation in the source code.)

"; + page.append(bW); + String description = f.getName() + "() api page"; + writePage(f.getName(), page.toString(), ResultType.FUNCTION, "API/functions/" + f.getName() + ".html", + Arrays.asList( + new String[]{f.getName(), f.getName() + " api", f.getName() + " example", f.getName() + + " description"}), description); + } + + private void deployEventAPI() { + generateQueue.submit(() -> { + try { + Set> eventClasses = new TreeSet<>( + (Class o1, Class o2) -> { + Event f1 = ReflectionUtils.instantiateUnsafe(o1); + Event f2 = ReflectionUtils.instantiateUnsafe(o2); + return f1.getName().compareTo(f2.getName()); + }); + eventClasses.addAll(ClassDiscovery.getDefaultInstance() + .loadClassesWithAnnotationThatExtend(api.class, Event.class)); + // A map of where it maps the enclosing class to the list of event rows, + // which contains a list of table cells. + Map, List>> data = new TreeMap<>((Class o1, Class o2) + -> o1.getCanonicalName().compareTo(o2.getCanonicalName())); + for(Class eventClass : eventClasses) { + if(!data.containsKey(eventClass.getEnclosingClass())) { + data.put(eventClass.getEnclosingClass(), new ArrayList<>()); + } + List> d = data.get(eventClass.getEnclosingClass()); + List c = new ArrayList<>(); + // event name, description, prefilters, data, mutable + final Event e; + try { + e = ReflectionUtils.instantiateUnsafe(eventClass); + } catch (ReflectionUtils.ReflectionException ex) { + throw new RuntimeException("While trying to construct " + eventClass + + ", got the following", ex); + } + final DocGen.EventDocInfo edi = new DocGen.EventDocInfo(e, e.docs(), e.getName(), DocGen.MarkupType.WIKI); + if(e.since().equals(MSVersion.V0_0_0)) { + // Don't add these + continue; + } + c.add(e.getName()); + c.add(edi.description); + List pre = new ArrayList<>(); + if(!edi.prefilter.isEmpty()) { + for(DocGen.EventDocInfo.PrefilterData pdata : edi.prefilter) { + pre.add("

" + pdata.name + ": " + + pdata.formatDescription(DocGen.MarkupType.HTML) + "

"); + } + } + c.add(StringUtils.Join(pre, "")); + List ed = new ArrayList<>(); + if(!edi.eventData.isEmpty()) { + for(DocGen.EventDocInfo.EventData edata : edi.eventData) { + ed.add("

" + edata.name + "" + + (!edata.description.isEmpty() ? ": " + edata.description : "") + "

"); + } + } + c.add(StringUtils.Join(ed, "")); + List mut = new ArrayList<>(); + if(!edi.mutability.isEmpty()) { + for(DocGen.EventDocInfo.MutabilityData mdata : edi.mutability) { + mut.add("

" + mdata.name + "" + + (!mdata.description.isEmpty() ? ": " + mdata.description : "") + "

"); + } + } + c.add(StringUtils.Join(mut, "")); + d.add(c); + } + // data is now constructed. + StringBuilder b = new StringBuilder(); + b.append("\n"); + for(Map.Entry, List>> e : data.entrySet()) { + Class clazz = e.getKey(); + List> clazzData = e.getValue(); + if(clazzData.isEmpty()) { + // If there are no events in the class, don't display it. + continue; + } + try { + b.append("== ").append(clazz.getSimpleName()).append(" ==\n"); + String docs = (String) ReflectionUtils.invokeMethod(clazz, null, "docs"); + b.append("
").append(docs).append("
\n\n"); + b.append("{| class=\"sticky\"\n|-\n"); + b.append("! scope=\"col\" width=\"7%\" | Event Name\n" + + "! scope=\"col\" width=\"30%\" | Description\n" + + "! scope=\"col\" width=\"20%\" | Prefilters\n" + + "! scope=\"col\" width=\"25%\" | Event Data\n" + + "! scope=\"col\" width=\"18%\" | Mutable Fields\n"); + for(List row : clazzData) { + b.append("|-"); + b.append("\n"); + for(String cell : row) { + b.append("| ").append(cell).append("\n"); + } + } + b.append("|}\n"); + b.append("

Back to top

\n"); + } catch (Error ex) { + writeLog("While processing " + clazz + " got:", ex); + } + } + writePage("Event API", b.toString(), ResultType.API, "Event_API.html", + Arrays.asList(new String[]{"API", "events"}), + "A list of all " + Implementation.GetServerType().getBranding() + " events"); + currentGenerateTask.addAndGet(1); + } catch (Error ex) { + ex.printStackTrace(System.err); + } + }); + totalGenerateTasks.addAndGet(1); + } + + private void deployEvents() { +// generateQueue.submit(new Runnable() { +// @Override +// public void run() { +// currentGenerateTask.addAndGet(1); +// } +// }); +// totalGenerateTasks.addAndGet(1); + } + + private void deployObjects() { +// generateQueue.submit(new Runnable() { +// @Override +// public void run() { +// currentGenerateTask.addAndGet(1); +// } +// }); +// totalGenerateTasks.addAndGet(1); + } + + /** + * Pages deployed: api.json - This page is the json version of the api + */ + private void deployAPIJSON() { + generateQueue.submit(() -> { + try { + writeStatus("Generating api.json"); + writeFromString(apiJson, "api.json"); + currentGenerateTask.addAndGet(1); + } catch (Throwable t) { + writeLog("Failure!", t); + } + }); + totalGenerateTasks.addAndGet(1); + } + + private void deployJar() { + uploadQueue.submit(() -> { + try { + writeFromStream(ClassDiscovery.GetClassContainer(SiteDeploy.class).openStream(), + "MethodScript.jar"); + // It goes in two places, so the latest release is always available no matter the last + // build this version was built with. + writeFromStream(ClassDiscovery.GetClassContainer(SiteDeploy.class).openStream(), + "../../MethodScript.jar"); + + } catch (Throwable e) { + e.printStackTrace(System.err); + } + }); + } +} diff --git a/src/main/java/com/laytonsmith/tools/docgen/templates/ArrayIteration.java b/src/main/java/com/laytonsmith/tools/docgen/templates/ArrayIteration.java new file mode 100644 index 0000000000..05fca61e44 --- /dev/null +++ b/src/main/java/com/laytonsmith/tools/docgen/templates/ArrayIteration.java @@ -0,0 +1,18 @@ +package com.laytonsmith.tools.docgen.templates; + +/** + * + */ +public class ArrayIteration extends Template { + + @Override + public String getName() { + return "Array_Iteration"; + } + + @Override + public String getDisplayName() { + return "Array Iteration"; + } + +} diff --git a/src/main/java/com/laytonsmith/tools/docgen/templates/Arrays.java b/src/main/java/com/laytonsmith/tools/docgen/templates/Arrays.java new file mode 100644 index 0000000000..e2a87aa469 --- /dev/null +++ b/src/main/java/com/laytonsmith/tools/docgen/templates/Arrays.java @@ -0,0 +1,13 @@ +package com.laytonsmith.tools.docgen.templates; + +/** + * + */ +public class Arrays extends Template { + + @Override + public String getName() { + return "Arrays"; + } + +} diff --git a/src/main/java/com/laytonsmith/tools/docgen/templates/Closures.java b/src/main/java/com/laytonsmith/tools/docgen/templates/Closures.java new file mode 100644 index 0000000000..c74756eba4 --- /dev/null +++ b/src/main/java/com/laytonsmith/tools/docgen/templates/Closures.java @@ -0,0 +1,13 @@ +package com.laytonsmith.tools.docgen.templates; + +/** + * + */ +public class Closures extends Template { + + @Override + public String getName() { + return "Closures"; + } + +} diff --git a/src/main/java/com/laytonsmith/tools/docgen/templates/Exceptions.java b/src/main/java/com/laytonsmith/tools/docgen/templates/Exceptions.java new file mode 100644 index 0000000000..d18ca67eab --- /dev/null +++ b/src/main/java/com/laytonsmith/tools/docgen/templates/Exceptions.java @@ -0,0 +1,13 @@ +package com.laytonsmith.tools.docgen.templates; + +/** + * + */ +public class Exceptions extends Template { + + @Override + public String getName() { + return "Exceptions"; + } + +} diff --git a/src/main/java/com/laytonsmith/tools/docgen/templates/Logic.java b/src/main/java/com/laytonsmith/tools/docgen/templates/Logic.java new file mode 100644 index 0000000000..c37b430c71 --- /dev/null +++ b/src/main/java/com/laytonsmith/tools/docgen/templates/Logic.java @@ -0,0 +1,14 @@ +package com.laytonsmith.tools.docgen.templates; + +/** + * + * @author cailin + */ +public class Logic extends Template { + + @Override + public String getName() { + return "Logic"; + } + +} diff --git a/src/main/java/com/laytonsmith/tools/docgen/templates/Loops.java b/src/main/java/com/laytonsmith/tools/docgen/templates/Loops.java new file mode 100644 index 0000000000..dd983f4a01 --- /dev/null +++ b/src/main/java/com/laytonsmith/tools/docgen/templates/Loops.java @@ -0,0 +1,13 @@ +package com.laytonsmith.tools.docgen.templates; + +/** + * + */ +public class Loops extends Template { + + @Override + public String getName() { + return "Loops"; + } + +} diff --git a/src/main/java/com/laytonsmith/tools/docgen/templates/PersistenceNetwork.java b/src/main/java/com/laytonsmith/tools/docgen/templates/PersistenceNetwork.java new file mode 100644 index 0000000000..a6fbbdd0c2 --- /dev/null +++ b/src/main/java/com/laytonsmith/tools/docgen/templates/PersistenceNetwork.java @@ -0,0 +1,18 @@ +package com.laytonsmith.tools.docgen.templates; + +/** + * + */ +public class PersistenceNetwork extends Template { + + @Override + public String getName() { + return "Persistence_Network"; + } + + @Override + public String getDisplayName() { + return "Persistence Network"; + } + +} diff --git a/src/main/java/com/laytonsmith/tools/docgen/templates/Profiles.java b/src/main/java/com/laytonsmith/tools/docgen/templates/Profiles.java new file mode 100644 index 0000000000..cbd1067879 --- /dev/null +++ b/src/main/java/com/laytonsmith/tools/docgen/templates/Profiles.java @@ -0,0 +1,13 @@ +package com.laytonsmith.tools.docgen.templates; + +/** + * + */ +public class Profiles extends Template { + + @Override + public String getName() { + return "Profiles"; + } + +} diff --git a/src/main/java/com/laytonsmith/tools/docgen/templates/SQL.java b/src/main/java/com/laytonsmith/tools/docgen/templates/SQL.java new file mode 100644 index 0000000000..4c0d2a90c7 --- /dev/null +++ b/src/main/java/com/laytonsmith/tools/docgen/templates/SQL.java @@ -0,0 +1,13 @@ +package com.laytonsmith.tools.docgen.templates; + +/** + * + */ +public class SQL extends Template { + + @Override + public String getName() { + return "SQL"; + } + +} diff --git a/src/main/java/com/laytonsmith/tools/docgen/templates/Template.java b/src/main/java/com/laytonsmith/tools/docgen/templates/Template.java new file mode 100644 index 0000000000..c4144afc02 --- /dev/null +++ b/src/main/java/com/laytonsmith/tools/docgen/templates/Template.java @@ -0,0 +1,35 @@ +package com.laytonsmith.tools.docgen.templates; + +/** + * A Template subclass represents a template in the docs, that can be referenced in code. There is no check to ensure + * that the template file in fact exists, so ensure that if a subclass is created, there is a corresponding file for it. + */ +public abstract class Template { + + /** + * Returns the name of this Template. + * + * @return + */ + public abstract String getName(); + + /** + * Returns the display name of this Template. By default, this is just the value of getName(). + * + * @return + */ + public String getDisplayName() { + return getName(); + } + + /** + * If the template should be located at a different path on the website, then this should be overridden + * to provide the path. By default, empty string is returned, which means the root path. If this is not + * the empty string, the path returned should end with {@code /} + * @return + */ + public String getPath() { + return ""; + } + +} diff --git a/src/main/java/com/laytonsmith/tools/docgen/templates/Variables.java b/src/main/java/com/laytonsmith/tools/docgen/templates/Variables.java new file mode 100644 index 0000000000..bb1d099e13 --- /dev/null +++ b/src/main/java/com/laytonsmith/tools/docgen/templates/Variables.java @@ -0,0 +1,13 @@ +package com.laytonsmith.tools.docgen.templates; + +/** + * + */ +public class Variables extends Template { + + @Override + public String getName() { + return "Variables"; + } + +} diff --git a/src/main/java/com/laytonsmith/tools/langserv/LangServ.java b/src/main/java/com/laytonsmith/tools/langserv/LangServ.java new file mode 100644 index 0000000000..4bfbaca67b --- /dev/null +++ b/src/main/java/com/laytonsmith/tools/langserv/LangServ.java @@ -0,0 +1,901 @@ +package com.laytonsmith.tools.langserv; + +import com.laytonsmith.PureUtilities.ArgumentParser; +import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery; +import com.laytonsmith.PureUtilities.Common.StackTraceUtils; +import com.laytonsmith.PureUtilities.MapBuilder; +import com.laytonsmith.PureUtilities.SmartComment; +import com.laytonsmith.core.AbstractCommandLineTool; +import com.laytonsmith.core.LogLevel; +import com.laytonsmith.core.MSLog; +import com.laytonsmith.core.ParseTree; +import com.laytonsmith.core.Profiles; +import com.laytonsmith.core.Script; +import com.laytonsmith.core.Security; +import com.laytonsmith.core.Static; +import com.laytonsmith.core.compiler.CompilerWarning; +import com.laytonsmith.core.compiler.analysis.Declaration; +import com.laytonsmith.core.compiler.analysis.Namespace; +import com.laytonsmith.core.compiler.analysis.ParamDeclaration; +import com.laytonsmith.core.compiler.analysis.ProcDeclaration; +import com.laytonsmith.core.compiler.analysis.Scope; +import com.laytonsmith.core.compiler.analysis.StaticAnalysis; +import com.laytonsmith.core.constructs.CFunction; +import com.laytonsmith.core.constructs.CNull; +import com.laytonsmith.core.constructs.CString; +import com.laytonsmith.core.constructs.Target; +import com.laytonsmith.core.environments.Environment; +import com.laytonsmith.core.exceptions.ConfigCompileException; +import com.laytonsmith.core.functions.DocumentLinkProvider; +import com.laytonsmith.core.functions.DocumentSymbolProvider; +import com.laytonsmith.core.functions.Function; +import com.laytonsmith.core.natives.interfaces.Mixed; +import com.laytonsmith.core.tool; +import com.laytonsmith.persistence.DataSourceException; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.management.ManagementFactory; +import java.lang.management.RuntimeMXBean; +import java.net.Socket; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.eclipse.lsp4j.CompletionItem; +import org.eclipse.lsp4j.CompletionList; +import org.eclipse.lsp4j.CompletionOptions; +import org.eclipse.lsp4j.CompletionParams; +import org.eclipse.lsp4j.DeclarationParams; +import org.eclipse.lsp4j.DiagnosticSeverity; +import org.eclipse.lsp4j.DidChangeConfigurationParams; +import org.eclipse.lsp4j.DidChangeTextDocumentParams; +import org.eclipse.lsp4j.DidChangeWatchedFilesParams; +import org.eclipse.lsp4j.DidChangeWorkspaceFoldersParams; +import org.eclipse.lsp4j.DidCloseTextDocumentParams; +import org.eclipse.lsp4j.DidOpenTextDocumentParams; +import org.eclipse.lsp4j.DidSaveTextDocumentParams; +import org.eclipse.lsp4j.DocumentLink; +import org.eclipse.lsp4j.DocumentLinkOptions; +import org.eclipse.lsp4j.DocumentLinkParams; +import org.eclipse.lsp4j.DocumentSymbol; +import org.eclipse.lsp4j.DocumentSymbolParams; +import org.eclipse.lsp4j.ExecuteCommandOptions; +import org.eclipse.lsp4j.ExecuteCommandParams; +import org.eclipse.lsp4j.Hover; +import org.eclipse.lsp4j.HoverParams; +import org.eclipse.lsp4j.InitializeParams; +import org.eclipse.lsp4j.InitializeResult; +import org.eclipse.lsp4j.InitializedParams; +import org.eclipse.lsp4j.Location; +import org.eclipse.lsp4j.LocationLink; +import org.eclipse.lsp4j.MarkupContent; +import org.eclipse.lsp4j.MessageParams; +import org.eclipse.lsp4j.MessageType; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.ServerCapabilities; +import org.eclipse.lsp4j.SymbolInformation; +import org.eclipse.lsp4j.SymbolKind; +import org.eclipse.lsp4j.TextDocumentSyncKind; +import org.eclipse.lsp4j.TypeDefinitionParams; +import org.eclipse.lsp4j.WorkspaceFolder; +import org.eclipse.lsp4j.WorkspaceFoldersOptions; +import org.eclipse.lsp4j.WorkspaceServerCapabilities; +import org.eclipse.lsp4j.jsonrpc.Launcher; +import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.eclipse.lsp4j.launch.LSPLauncher; +import org.eclipse.lsp4j.services.LanguageClient; +import org.eclipse.lsp4j.services.LanguageClientAware; +import org.eclipse.lsp4j.services.LanguageServer; +import org.eclipse.lsp4j.services.TextDocumentService; +import org.eclipse.lsp4j.services.WorkspaceService; + +/** + * + */ +public class LangServ implements LanguageServer, LanguageClientAware, TextDocumentService, WorkspaceService { + + @MSLog.LogTag + public static final MSLog.Tag LANGSERVLOGTAG = new MSLog.Tag() { + @Override + public String getName() { + return "langserv"; + } + + @Override + public String getDescription() { + return "Logs events related to the Language Server"; + } + + @Override + public LogLevel getLevel() { + return LogLevel.WARNING; + } + }; + + @tool("lang-serv") + public static class LangServMode extends AbstractCommandLineTool { + + @Override + public ArgumentParser getArgumentParser() { + return ArgumentParser.GetParser() + .addDescription("Starts up the language server, which implements the Language Server Protocol") + .addArgument(new ArgumentParser.ArgumentBuilder() + .setDescription("The host location that the client is running on.") + .setUsageName("host") + .setOptional() + .setName("host") + .setArgType(ArgumentParser.ArgumentBuilder.BuilderTypeNonFlag.STRING)) + .addArgument(new ArgumentParser.ArgumentBuilder() + .setDescription("The port the client is running on.") + .setUsageName("port") + .setOptional() + .setName("port") + .setArgType(ArgumentParser.ArgumentBuilder.BuilderTypeNonFlag.NUMBER)) + .addArgument(new ArgumentParser.ArgumentBuilder() + .setDescription("If set, stdio is used instead of socket connections.") + .asFlag() + .setName("stdio")) + .setErrorOnUnknownArgs(false) + .addArgument(new ArgumentParser.ArgumentBuilder() + .setDescription("For future compatibility reasons, unrecognized arguments are not an error," + + " but they are not supported unless otherwise noted.") + .setUsageName("unrecognizedArgs") + .setOptionalAndDefault() + .setArgType(ArgumentParser.ArgumentBuilder.BuilderTypeNonFlag.ARRAY_OF_STRINGS)); + } + + @Override + public boolean startupExtensionManager() { + return false; + } + + @Override + @SuppressWarnings("UseSpecificCatch") + public void execute(ArgumentParser.ArgumentParserResults parsedArgs) throws Exception { + boolean useStdio = parsedArgs.isFlagSet("stdio"); + String hostname = null; + int port = 0; + if(!useStdio) { + hostname = parsedArgs.getStringArgument("host"); + port = parsedArgs.getNumberArgument("port").intValue(); + } + + LangServ langserv = new LangServ(useStdio); + langserv.log("Starting up Language Server: " + parsedArgs.getRawArguments(), LogLevel.INFO); + try { + InputStream is; + OutputStream os; + if(!useStdio) { + Socket socket = new Socket(hostname, port); + socket.setKeepAlive(true); + is = socket.getInputStream(); + os = socket.getOutputStream(); + } else { + is = System.in; + os = System.out; + } + Launcher launcher = LSPLauncher.createServerLauncher(langserv, is, os); + LanguageClient client = launcher.getRemoteProxy(); + langserv.connect(client); + RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean(); + List arguments = runtimeMxBean.getInputArguments(); + langserv.log("Java started with args: " + arguments.toString(), LogLevel.DEBUG); + langserv.log("Starting language server", LogLevel.INFO); + if(useStdio) { + System.err.println("Started Language Server, awaiting connections"); + } + launcher.startListening(); + } catch(Throwable t) { + t.printStackTrace(System.err); + System.exit(1); + } + } + + @Override + public boolean noExitOnReturn() { + return true; + } + } + + // + public void log(String s, LogLevel level) { + log(() -> s, level); + } + + public void log(MSLog.StringProvider s, LogLevel level) { + MSLog.GetLogger().Log(LANGSERVLOGTAG, level, s, Target.UNKNOWN); + if(client != null && MSLog.GetLogger().WillLog(LANGSERVLOGTAG, level)) { + MessageType type; + type = switch(level) { + case DEBUG -> + MessageType.Log; + case INFO -> + MessageType.Info; + case WARNING -> + MessageType.Warning; + case ERROR -> + MessageType.Error; + default -> + MessageType.Log; + }; + + String full = s.getString(); + client.logMessage(new MessageParams(type, full)); + if(level == LogLevel.ERROR) { + client.showMessage(new MessageParams(MessageType.Error, full)); + } + } + } + + public void loge(Throwable t) { + log(StackTraceUtils.GetStacktrace(t), LogLevel.ERROR); + } + + public void loge(String s) { + log(s, LogLevel.ERROR); + } + + public void loge(MSLog.StringProvider s) { + log(s, LogLevel.ERROR); + } + + public void logw(String s) { + log(s, LogLevel.WARNING); + } + + public void logw(MSLog.StringProvider s) { + log(s, LogLevel.WARNING); + } + + public void logi(String s) { + log(s, LogLevel.INFO); + } + + public void logi(MSLog.StringProvider s) { + log(s, LogLevel.INFO); + } + + public void logd(String s) { + log(s, LogLevel.DEBUG); + } + + public void logd(MSLog.StringProvider s) { + log(s, LogLevel.DEBUG); + } + + public void logv(String s) { + log(s, LogLevel.VERBOSE); + } + + public void logv(MSLog.StringProvider s) { + log(s, LogLevel.VERBOSE); + } + // + + public LangServ(boolean useStdio) { + this.usingStdio = useStdio; + } + + private final boolean usingStdio; + + private LanguageClient client; + private LangServModel model; + + /** + * This executor uses an unbounded thread pool, and should only be used for task in which a user is actively waiting + * for results, however, tasks submitted to this processor will begin immediately, as opposed to the + * lowPriorityProcessors queue, which has a fixed size thread pool. + */ + private final Executor highPriorityProcessors = Executors.newCachedThreadPool(new ThreadFactory() { + private int count = 0; + + @Override + public Thread newThread(Runnable r) { + return new Thread(r, "HighPriority-thread-pool-" + (++count)); + } + }); + /** + * This executor uses a bounded thread pool, and should be used for all tasks that a user is not actively waiting + * on, for instance, compilation of files that are not open. + */ + private final Executor lowPriorityProcessors = Executors.newFixedThreadPool(5, new ThreadFactory() { + private int count = 0; + + @Override + public Thread newThread(Runnable r) { + return new Thread(r, "LowPriority-thread-pool-" + (++count)); + } + }); + + @Override + @SuppressWarnings("UseSpecificCatch") + public void connect(LanguageClient client) { + logv(this.getClass().getName() + "." + StackTraceUtils.currentMethod() + " called"); + this.client = client; + + if(this.model == null) { + this.model = new LangServModel(this); + } + this.model.setClient(client); + this.model.setProcessors(highPriorityProcessors, lowPriorityProcessors); + + this.model.startup(); + } + + @java.lang.annotation.Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + public static @interface Command { + + /** + * The name of the command. + * + * @return + */ + String value(); + } + + public static interface CommandProvider { + + CompletableFuture execute(LanguageClient client, ExecuteCommandParams params); + } + + // Need to implement stuff in the extension before this is useful +// @Command("new-ms-file") +// public static class NewMsFileCommand implements CommandProvider { +// +// @Override +// public CompletableFuture execute(LanguageClient client, ExecuteCommandParams params) { +// CompletableFuture result = new CompletableFuture<>(); +//// ShowMessageRequestParams smrp = new ShowMessageRequestParams(); +//// MessageActionItem action = new MessageActionItem(); +//// action.setTitle("Name of file"); +//// smrp.setActions(Arrays.asList(action)); +//// client.showMessageRequest(smrp).thenAccept(action -> { +//// action. +//// }); +// result.complete(null); +// return result; +// } +// +// } + private final Map commandProviders = new HashMap<>(); + + @Override + public CompletableFuture initialize(InitializeParams params) { + logv(this.getClass().getName() + "." + StackTraceUtils.currentMethod() + " called"); + // So base dir restrictions won't apply + Security.setSecurityEnabled(false); + CompletableFuture cf = new CompletableFuture<>(); + ServerCapabilities sc = new ServerCapabilities(); + sc.setTextDocumentSync(TextDocumentSyncKind.Full); + + DocumentLinkOptions documentLinkOptions = new DocumentLinkOptions(); + documentLinkOptions.setResolveProvider(false); + sc.setDocumentLinkProvider(documentLinkOptions); + + WorkspaceServerCapabilities wsc = new WorkspaceServerCapabilities(); + WorkspaceFoldersOptions wfo = new WorkspaceFoldersOptions(); + wfo.setSupported(true); + wfo.setChangeNotifications(true); + wsc.setWorkspaceFolders(wfo); + sc.setWorkspace(wsc); + + sc.setDocumentSymbolProvider(true); + sc.setDeclarationProvider(true); + sc.setHoverProvider(true); +// sc.setTypeDefinitionProvider(true); + + { + ExecuteCommandOptions eco = new ExecuteCommandOptions(); + List commands = new ArrayList<>(); + for(Class c : ClassDiscovery.getDefaultInstance() + .loadClassesWithAnnotationThatExtend(Command.class, CommandProvider.class)) { + CommandProvider cp; + try { + cp = c.newInstance(); + } catch(InstantiationException | IllegalAccessException ex) { + // We can't recover from this, so just skip it + Logger.getLogger(LangServ.class.getName()).log(Level.SEVERE, null, ex); + continue; + } + String command = c.getAnnotation(Command.class).value(); + commands.add(command); + commandProviders.put(command, cp); + } + eco.setCommands(commands); + sc.setExecuteCommandProvider(eco); + } + CompletionOptions co = new CompletionOptions(true, Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h", + "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "_")); + sc.setCompletionProvider(co); + cf.complete(new InitializeResult(sc)); + if(usingStdio) { + System.err.println("Language Server Connected"); + } + model.addWorkspace(params.getWorkspaceFolders()); + return cf; + } + + @Override + public void initialized(InitializedParams params) { + logv(this.getClass().getName() + "." + StackTraceUtils.currentMethod() + " called"); + client.workspaceFolders().thenAccept((List t) -> { + + }); + } + + @Override + public CompletableFuture shutdown() { + logv(this.getClass().getName() + "." + StackTraceUtils.currentMethod() + " called"); + CompletableFuture cf = new CompletableFuture<>(); + cf.complete(null); + return cf; + } + + @Override + public void exit() { + logv(this.getClass().getName() + "." + StackTraceUtils.currentMethod() + " called"); + System.exit(0); + } + + @Override + public TextDocumentService getTextDocumentService() { + logv(this.getClass().getName() + "." + StackTraceUtils.currentMethod() + " called"); + return this; + } + + @Override + public WorkspaceService getWorkspaceService() { + logv(this.getClass().getName() + "." + StackTraceUtils.currentMethod() + " called"); + return this; + } + + public static DiagnosticSeverity getSeverity(CompilerWarning warning) { + if(warning.getSuppressCategory() == null) { + return DiagnosticSeverity.Warning; + } + switch(warning.getSuppressCategory().getSeverityLevel()) { + case HIGH -> { + return DiagnosticSeverity.Warning; + } + case MEDIUM -> { + return DiagnosticSeverity.Information; + } + case LOW -> { + return DiagnosticSeverity.Hint; + } + } + throw new Error("Unaccounted for case: " + warning.getSuppressCategory()); + } + + @Override + public void didChangeWorkspaceFolders(DidChangeWorkspaceFoldersParams params) { + model.removeWorkspace(params.getEvent().getRemoved()); + model.addWorkspace(params.getEvent().getAdded()); + } + + // + @Override + public void didOpen(DidOpenTextDocumentParams params) { + model.didOpen(params); + } + + @Override + public void didChange(DidChangeTextDocumentParams params) { + model.didChange(params); + } + + @Override + public void didClose(DidCloseTextDocumentParams params) { + model.didClose(params); + } + + @Override + public void didSave(DidSaveTextDocumentParams params) { + model.didSave(params); + } + + // + public static Range convertTargetToRange(Target t) { + int tokenLength = t.length(); + if(tokenLength < 1) { + // Something went wrong, but we always want an error to show up, so set this here + tokenLength = 1; + } + + Position start = new Position(t.line() - 1, t.col() - 1); + Position end = new Position(t.line() - 1, t.col() + tokenLength - 1); + if(start.getLine() < 0) { + start.setLine(0); + } + if(start.getCharacter() < 0) { + start.setCharacter(0); + } + if(end.getLine() < 0) { + end.setLine(0); + } + if(end.getCharacter() < 0) { + end.setCharacter(1); + } + return new Range(start, end); + } + + public static Range convertTargetToRange(ParseTree node) { + return convertTargetToRange(node.getTarget()); + } + + public void convertPositionToParseTree(CompletableFuture future, Executor threadPool, String uri, Position position) { + CompletableFuture privateFuture = new CompletableFuture<>(); + model.getParseTree(privateFuture, uri); + privateFuture.thenAccept((ParseTree t) -> { + ParseTree result = LangServModel.findToken(t, position); + future.complete(result); + }); + } + + public static Location convertTargetToLocation(Target t) { + Range range = convertTargetToRange(t); + Location location = new Location(t.file().toURI().toString(), range); + return location; + } + + public static Location convertTargetToLocation(ParseTree node) { + return convertTargetToLocation(node.getTarget()); + } + + @Override + public void didChangeConfiguration(DidChangeConfigurationParams params) { + logv(this.getClass().getName() + "." + StackTraceUtils.currentMethod() + " called"); + } + + @Override + public void didChangeWatchedFiles(DidChangeWatchedFilesParams params) { + logv(this.getClass().getName() + "." + StackTraceUtils.currentMethod() + " called"); + } + + @Override + public CompletableFuture, CompletionList>> completion(CompletionParams position) { + logv(this.getClass().getName() + "." + StackTraceUtils.currentMethod() + " called"); + logv(() -> String.format("Completion request sent: %s", position)); + CompletableFuture, CompletionList>> result = new CompletableFuture<>(); + highPriorityProcessors.execute(() -> { + result.complete(Either.forLeft(model.getFunctionCompletionItems())); + logv(() -> "Completion list returned with " + model.getFunctionCompletionItems().size() + " items"); + }); + return result; + } + + @Override + public CompletableFuture resolveCompletionItem(CompletionItem unresolved) { + logv(this.getClass().getName() + "." + StackTraceUtils.currentMethod() + " called"); + logv(() -> unresolved.toString()); + CompletableFuture result = new CompletableFuture<>(); + // For now, this will always be correct, but once procs are added, this will not necessarily be true. + result.complete(unresolved); + return result; + } + + @Override + public CompletableFuture> documentLink(DocumentLinkParams params) { + String uri = params.getTextDocument().getUri(); + logv(this.getClass().getName() + "." + StackTraceUtils.currentMethod() + " called"); + logv(() -> "Requested " + uri); + + CompletableFuture future = new CompletableFuture<>(); + CompletableFuture> result = new CompletableFuture<>(); + model.getParseTree(future, uri); + future.thenAccept((tree) -> { + if(tree == null) { + return; + } + Environment env; + try { + env = Static.GenerateStandaloneEnvironment(false); + } catch(IOException | DataSourceException | URISyntaxException | Profiles.InvalidProfileException ex) { + loge(ex); + result.cancel(true); + return; + } + List links = new ArrayList<>(); + tree.getAllNodes().forEach(node -> { + if(node.getData() instanceof CFunction && ((CFunction) (node.getData())).hasFunction()) { + try { + Function f = ((CFunction) (node.getData())).getFunction(); + if(f instanceof DocumentLinkProvider documentLinkProvider) { + logv(() -> "Found DocumentLinkProvider " + f.getName()); + for(ParseTree link : documentLinkProvider.getDocumentLinks(node.getChildren())) { + if(link.isConst()) { + File file = Static.GetFileFromArgument(link.getData().val(), env, link.getTarget(), + null); + if(file != null && file.exists() && file.isFile()) { + logv(() -> "Found document link to " + file.toURI()); + DocumentLink docLink = new DocumentLink(); + docLink.setRange(convertTargetToRange(link)); + docLink.setTarget(file.toURI().toString()); + links.add(docLink); + } + } + } + } + } catch(ConfigCompileException ex) { + // Ignore this. This can be caused by procs, or other errors, but the point is, it's not a + // DocumentLinkProvider. + } + } + }); + result.complete(links); + }); + return result; + } + + @Override + public CompletableFuture, List>> declaration(DeclarationParams params) { + logv(this.getClass().getName() + "." + StackTraceUtils.currentMethod() + " called"); + logv(() -> params.toString()); + CompletableFuture, List>> result = new CompletableFuture<>(); + CompletableFuture future = new CompletableFuture<>(); + String uri = params.getTextDocument().getUri(); + convertPositionToParseTree(future, highPriorityProcessors, uri, params.getPosition()); + future.thenAccept((ParseTree t) -> { + if(t == null) { + result.cancel(true); + return; + } + if(t.getData() instanceof CFunction cf) { + if(cf.hasProcedure()) { + String procName = cf.val(); + StaticAnalysis sa = model.getStaticAnalysis(uri); + List locations = new ArrayList<>(); + Scope scope = sa.getTermScope(t); + if(scope != null) { + Collection decls = scope.getDeclarations(Namespace.PROCEDURE, procName); + for(Declaration decl : decls) { + locations.add(convertTargetToLocation(decl.getTarget())); + } + result.complete(Either.forLeft(locations)); + return; + } + } + } + result.cancel(true); + }); + return result; + } + + @Override + public CompletableFuture, List>> typeDefinition(TypeDefinitionParams params) { + logv(this.getClass().getName() + "." + StackTraceUtils.currentMethod() + " called"); + logv(() -> params.toString()); + CompletableFuture, List>> result = new CompletableFuture<>(); + result.cancel(true); + return result; + } + + @Override + public CompletableFuture hover(HoverParams params) { + logv(this.getClass().getName() + "." + StackTraceUtils.currentMethod() + " called"); + logv(() -> params.toString()); + + CompletableFuture result = new CompletableFuture<>(); + CompletableFuture findParseTree = new CompletableFuture<>(); + String uri = params.getTextDocument().getUri(); + convertPositionToParseTree(findParseTree, highPriorityProcessors, uri, + params.getPosition()); + + findParseTree.thenAccept((ParseTree t) -> { + if(t == null) { + result.cancel(true); + return; + } + Hover hover = null; + StaticAnalysis sa = model.getStaticAnalysis(uri); + if(sa == null) { + // We can't find declarations of anything. + result.cancel(true); + return; + } + if(t.getData() instanceof CFunction cf) { + if(cf.hasProcedure()) { + Scope scope = sa.getTermScope(t); + if(scope == null) { + result.cancel(true); + return; + } + Collection col = scope.getReachableDeclarations(Namespace.PROCEDURE, cf.val()); + if(!col.isEmpty()) { + ProcDeclaration decl = (ProcDeclaration) new ArrayList<>(col).get(0); + SmartComment sc = decl.getNodeModifiers().getComment(); + if(sc != null) { + sc = doReplacements(sc); + } + String content = "## "; + content += decl.getType().getSimpleName() + " " + decl.getIdentifier() + "("; + boolean first = true; + for(ParamDeclaration pDecl : decl.getParameters()) { + if(!first) { + content += ", "; + } + content += pDecl.getType().getSimpleName() + " " + pDecl.getIdentifier(); + first = false; + } + content += ")\n\n"; + if(sc != null) { + content += sc.getBody() + "\n\n"; + List parameters = sc.getAnnotations("param"); + if(!decl.getParameters().isEmpty()) { + content += "### Parameters\n"; + for(ParamDeclaration pDecl : decl.getParameters()) { + content += " - " + pDecl.getType().getSimpleName() + " " + pDecl.getIdentifier(); + ParseTree defaultValue = pDecl.getDefaultValue(); + if(defaultValue != null) { + if(defaultValue.isConst() && defaultValue.getData() != CNull.UNDEFINED) { + Mixed data = defaultValue.getData(); + content += " [default "; + if(data instanceof CString str) { + content += str.getQuote(); + } else { + content += data.val(); + } + content += "]"; + } + } + for(String paramDocs : parameters) { + String[] split = paramDocs.split(" ", 2); + if(split.length > 1) { + if(pDecl.getIdentifier().replace("@", "").equals(split[0])) { + content += " - " + split[1].replace("\r", "").replace("\n", ""); + break; + } + } + } + content += "\n"; + } + } + content += "\n"; + if(!sc.getAnnotations("returns").isEmpty()) { + content += "### Returns\n" + sc.getAnnotations("returns").get(0) + "\n\n"; + } + + if(!sc.getAnnotations("seeAlso").isEmpty()) { + content += "### See Also\n"; + for(String seeAlso : sc.getAnnotations("seeAlso")) { + if(content.matches("https?://.*")) { + content += " - " + convertURLToLink(seeAlso); + } else { + content += " - " + seeAlso; + } + content += "\n"; + } + content += "\n"; + } + } + + MarkupContent mContent = new MarkupContent("markdown", content); + hover = new Hover(); + hover.setContents(mContent); + } + } + } + if(hover == null) { + result.cancel(true); + } else { + result.complete(hover); + } + }); + + return result; + } + + @Override + public CompletableFuture>> documentSymbol(DocumentSymbolParams params) { + String uri = params.getTextDocument().getUri(); + logv(this.getClass().getName() + "." + StackTraceUtils.currentMethod() + " called"); + logv(() -> "Requested symbols for " + uri); + + CompletableFuture>> result = new CompletableFuture<>(); + List> links = new ArrayList<>(); + + // TODO: Sort by kind/link name + // Different handling for msa and ms. msa returns aliases, ms returns procs and things. + if(uri.endsWith(".msa")) { + CompletableFuture> future = new CompletableFuture<>(); + model.doPreprocess(future, lowPriorityProcessors, uri, false); + future.thenAccept((scripts) -> { + for(Script script : scripts) { + String link = script.getSignatureWithoutLabel(); + link = link.replace("[ ", "[").replace(" ]", "]"); + SymbolInformation docSymbol = new SymbolInformation(link, SymbolKind.Method, + convertTargetToLocation(script.getTarget())); + links.add(Either.forLeft(docSymbol)); + } + result.complete(links); + }); + } else { + CompletableFuture future = new CompletableFuture<>(); + model.getParseTree(future, uri); + future.thenAccept((tree) -> { + if(tree == null) { + return; + } + tree.getAllNodes().forEach(node -> { + if(node.getData() instanceof CFunction && ((CFunction) (node.getData())).hasFunction()) { + try { + Function f = ((CFunction) (node.getData())).getFunction(); + if(f instanceof DocumentSymbolProvider documentSymbolProvider) { + logv(() -> "Found DocumentSymbolProvider " + f.getName()); + String link = documentSymbolProvider.symbolDisplayName(node.getChildren()); + if(link != null) { + SymbolInformation docSymbol = new SymbolInformation(link, + documentSymbolProvider.getSymbolKind(), + convertTargetToLocation(node)); + links.add(Either.forLeft(docSymbol)); + } + } + } catch(ConfigCompileException ex) { + // Ignore this. This can be caused errors, but the point is, it's not a + // valid symbol right now. + } + } + }); + result.complete(links); + }); + } + return result; + } + + @SuppressWarnings("Convert2Lambda") + public static SmartComment doReplacements(SmartComment sc) { + return new SmartComment(sc, MapBuilder.empty(String.class, SmartComment.Replacement.class) + .set("code", new SmartComment.Replacement() { + @Override + public String replace(String data) { + return "`" + data + "`"; + } + }) + .set("url", new SmartComment.Replacement() { + @Override + public String replace(String data) { + return convertURLToLink(data); + } + }) + .build()); + } + + private static String convertURLToLink(String annotationText) { + String[] split = annotationText.split(" ", 2); + String url; + String display; + switch(split.length) { + case 0 -> { + return ""; + } + case 1 -> + url = display = split[0]; + default -> { + url = split[0]; + display = split[1]; + } + } + return "[" + display + "](" + url + ")"; + } + + @Override + public CompletableFuture executeCommand(ExecuteCommandParams params) { + return commandProviders.get(params.getCommand()).execute(client, params); + } +} diff --git a/src/main/java/com/laytonsmith/tools/langserv/LangServModel.java b/src/main/java/com/laytonsmith/tools/langserv/LangServModel.java new file mode 100644 index 0000000000..e41e5daa2a --- /dev/null +++ b/src/main/java/com/laytonsmith/tools/langserv/LangServModel.java @@ -0,0 +1,795 @@ +package com.laytonsmith.tools.langserv; + +import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery; +import com.laytonsmith.PureUtilities.Common.FileUtil; +import com.laytonsmith.PureUtilities.Common.OSUtils; +import com.laytonsmith.PureUtilities.Common.StackTraceUtils; +import com.laytonsmith.PureUtilities.URIUtils; +import com.laytonsmith.annotations.api; +import com.laytonsmith.annotations.hide; +import com.laytonsmith.core.FullyQualifiedClassName; +import com.laytonsmith.core.LogLevel; +import com.laytonsmith.core.MSLog; +import com.laytonsmith.core.MethodScriptCompiler; +import com.laytonsmith.core.ParseTree; +import com.laytonsmith.core.Prefs; +import com.laytonsmith.core.Profiles; +import com.laytonsmith.core.Script; +import com.laytonsmith.core.ScriptProvider; +import com.laytonsmith.core.Static; +import com.laytonsmith.core.compiler.CompilerEnvironment; +import com.laytonsmith.core.compiler.CompilerWarning; +import com.laytonsmith.core.compiler.TokenStream; +import com.laytonsmith.core.compiler.analysis.StaticAnalysis; +import com.laytonsmith.core.constructs.NativeTypeList; +import com.laytonsmith.core.constructs.Target; +import com.laytonsmith.core.environments.CommandHelperEnvironment; +import com.laytonsmith.core.environments.Environment; +import com.laytonsmith.core.environments.GlobalEnv; +import com.laytonsmith.core.environments.RuntimeMode; +import com.laytonsmith.core.events.Event; +import com.laytonsmith.core.events.EventList; +import com.laytonsmith.core.exceptions.ConfigCompileException; +import com.laytonsmith.core.exceptions.ConfigCompileGroupException; +import com.laytonsmith.core.functions.FunctionBase; +import com.laytonsmith.core.functions.FunctionList; +import com.laytonsmith.core.functions.IncludeCache; +import com.laytonsmith.core.natives.interfaces.Mixed; +import com.laytonsmith.persistence.DataSourceException; +import com.laytonsmith.tools.docgen.DocGen; +import static com.laytonsmith.tools.langserv.LangServ.LANGSERVLOGTAG; +import static com.laytonsmith.tools.langserv.LangServ.convertTargetToRange; +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import org.eclipse.lsp4j.CompletionItem; +import org.eclipse.lsp4j.CompletionItemKind; +import org.eclipse.lsp4j.Diagnostic; +import org.eclipse.lsp4j.DiagnosticSeverity; +import org.eclipse.lsp4j.DidChangeTextDocumentParams; +import org.eclipse.lsp4j.DidCloseTextDocumentParams; +import org.eclipse.lsp4j.DidOpenTextDocumentParams; +import org.eclipse.lsp4j.DidSaveTextDocumentParams; +import org.eclipse.lsp4j.MessageParams; +import org.eclipse.lsp4j.MessageType; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.PublishDiagnosticsParams; +import org.eclipse.lsp4j.TextDocumentContentChangeEvent; +import org.eclipse.lsp4j.WorkspaceFolder; +import org.eclipse.lsp4j.services.LanguageClient; + +/** + * This class contains the database for a given LangServ project. This contains runtime data which the operators in + * LangServ operate on. Non-destructive actions can operate directly on the model, without having to do any further + * compilation, and eventually, incremental and partial dirtying can potentially occur. Getting data while the tree is + * dirty blocks, so all operations should use completable futures. + */ +public class LangServModel { + + private LanguageClient client; + private final LangServ langServ; + + private volatile boolean isDirty = true; + + private Executor highPriorityProcessors; + private Executor lowPriorityProcessors; + + /** + * Maps from URI to document text. If the document isn't in this map, it may be safely read from disk. + */ + private final Map documents = new HashMap<>(); + + private static List functionCompletionItems = null; + private static List objectCompletionItems = null; + private static List eventCompletionItems = null; + private static List allCompletionItems = null; + + private final List workspaceFolders = new ArrayList<>(); + + public LangServModel(LangServ server) { + this.langServ = server; + } + + public void setClient(LanguageClient client) { + this.client = client; + } + + public void setProcessors(Executor highPriorityExecutor, Executor lowPriorityExecutor) { + this.highPriorityProcessors = highPriorityExecutor; + this.lowPriorityProcessors = lowPriorityExecutor; + } + + public List getFunctionCompletionItems() { + return functionCompletionItems; + } + + public List getObjectCompletionItems() { + return objectCompletionItems; + } + + public List getEventCompletionItems() { + return eventCompletionItems; + } + + public List getAllCompletionItems() { + return allCompletionItems; + } + + /** + * Called when other model changes would cause the tree to dirty. This version of the function dirties everything. + * The tree is NOT attempted to be rebuilt immediately after, so if the change that caused the tree to be dirtied is + * completed, then the caller should also call rebuildTree with a low priority executor so that it will be rebuilt + * in the background as soon as possible. + */ + private void dirtyTree() { + isDirty = true; + } + + /** + * Combines dirtying and then rebuilding the tree on the given executor. No callback is provided. + * + * @param executor + */ + private void dirtyAndRebuildTree(Executor executor) { + dirtyTree(); + rebuildTree(executor, null); + } + + /** + * Rebuilds the tree, only if necessary. If the tree is not dirty, calls the Runnable immediately. + * + * @param executor + * @param afterBuild + */ + private void rebuildTree(Executor executor, Runnable afterBuild) { + // Set interrupt, as a new change has just come in that invalidates any build that + // may be in progress already, then we'll immediately queue the new one up. + interruptBuilding = true; + executor.execute(() -> { + // TODO + if(isDirty) { + synchronized(LangServModel.this) { + if(isDirty) { + syncRebuild(); + } + } + } + if(afterBuild != null) { + afterBuild.run(); + } + }); + } + + private volatile boolean interruptBuilding = false; + private final Map parseTrees = new HashMap<>(); + private final Map staticAnalysisMap = new HashMap<>(); + private Environment env; + + /** + * Usually this should only be called by rebuildTree. This method blocks until the rebuild is finished. If the tree + * is not dirty, this is a no-op. + */ + private void syncRebuild() { + if(!isDirty) { + return; + } + interruptBuilding = false; + parseTrees.clear(); + staticAnalysisMap.clear(); + // Calculate all auto includes first + List autoIncludes = new ArrayList<>(); + List mainFiles = new ArrayList<>(); + List libraryFiles = new ArrayList<>(); + try { + for(WorkspaceFolder folder : getWorkspaceFolders()) { + URI uuri = new URI(folder.getUri()); + File ai = Paths.get(uuri).toFile(); + File lp = new File(ai, "LocalPackages"); + if(lp.exists()) { + // If this is the parent directory of LocalPackages, + // this may contain special files and directories. + File main = new File(ai, Prefs.MainFile()); + if(main.exists()) { + mainFiles.add(main); + } + File aliases = new File(ai, Prefs.ScriptName()); + if(aliases.exists()) { + mainFiles.add(aliases); + } + File autoInclude = new File(ai, "auto_include.ms"); + if(autoInclude.exists()) { + autoIncludes.add(autoInclude); + } + File includes = new File(ai, "includes"); + if(includes.exists() && includes.isDirectory()) { + FileUtil.recursiveFind(includes, (r) -> { + String path = r.getAbsolutePath().replace('\\', '/'); + if(path.endsWith(".ms")) { + libraryFiles.add(r); + } + }); + } + // Now process LocalPackages + ai = lp; + } + FileUtil.recursiveFind(ai, (r) -> { + String path = r.getAbsolutePath().replace('\\', '/'); + if(!path.contains(".disabled/") && r.isFile()) { + if(path.contains(".library/")) { + if(path.endsWith(".ms")) { + libraryFiles.add(r); + } + } else if(path.endsWith("auto_include.ms")) { + autoIncludes.add(r); + } else if(path.endsWith(".ms") || path.endsWith(".msa")) { + mainFiles.add(r); + } + } + }); + } + } catch(IOException | URISyntaxException ex) { + client.logMessage(new MessageParams(MessageType.Warning, ex.getMessage())); + } + + IncludeCache includeCache = new IncludeCache(); + try { + // Cmdline mode disables things like security checks and whatnot. + // These may be present in the runtime environment, + // but it's not possible for us to tell that at this point. + env = Static.GenerateStandaloneEnvironment(false, EnumSet.of(RuntimeMode.CMDLINE), includeCache, + new StaticAnalysis(true)); + // Make this configurable at some point. For now, however, we need this so we can get + // correct handling on minecraft functions. + env = env.cloneAndAdd(new CommandHelperEnvironment()); + } catch(IOException | DataSourceException | URISyntaxException | Profiles.InvalidProfileException ex) { + throw new RuntimeException(ex); + } + CompilerEnvironment compilerEnv = env.getEnv(CompilerEnvironment.class); + compilerEnv.setLogCompilerWarnings(false); // No one would see them + GlobalEnv gEnv = env.getEnv(GlobalEnv.class); + gEnv.SetScriptProvider((File file) -> { + if(OSUtils.GetOS().isWindows()) { + // On Windows, canonical file paths have a capitalized drive letter. + file = new File(Character.toLowerCase(file.getPath().charAt(0)) + file.getPath().substring(1)); + } + return getDocument(file.toURI().toString()); + }); + Set exceptions = new HashSet<>(); + + langServ.logv(() -> "Providing StaticAnalysis with auto includes: " + autoIncludes); + StaticAnalysis.setAndAnalyzeAutoIncludes(autoIncludes, env, env.getEnvClasses(), exceptions); + + for(File f1 : autoIncludes) { + try { + File cf1 = f1.getCanonicalFile(); + String uri = f1.toURI().toString(); + // Get the parse tree from the include cache if available. Possible exceptions have already been obtained. + if(includeCache.has(cf1)) { + parseTrees.put(uri, IncludeCache.get(cf1, env, env.getEnvClasses(), Target.UNKNOWN)); + staticAnalysisMap.put(uri, includeCache.getStaticAnalysis(cf1)); + } else { + parseTrees.put(uri, null); + } + } catch(IOException ex) { + } + } + + for(File f2 : mainFiles) { + String uri = f2.toURI().toString(); + StaticAnalysis staticAnalysis = new StaticAnalysis(true); + parseTrees.put(uri, doCompilation(uri, new StaticAnalysis(true), env, exceptions)); + staticAnalysisMap.put(uri, staticAnalysis); + } + + for(File f3 : libraryFiles) { + try { + File cf3 = f3.getCanonicalFile(); + String uri = f3.toURI().toString(); + if(includeCache.has(cf3)) { + parseTrees.put(uri, IncludeCache.get(cf3, env, env.getEnvClasses(), Target.UNKNOWN)); + staticAnalysisMap.put(uri, includeCache.getStaticAnalysis(cf3)); + } else { + // This was not included, was dynamically included, or there was a compile exception. + // Can only treat it as an isolated script at this point. + StaticAnalysis staticAnalysis = new StaticAnalysis(true); + parseTrees.put(uri, doCompilation(uri, staticAnalysis, env, exceptions)); + staticAnalysisMap.put(uri, staticAnalysis); + } + } catch(IOException ex) { + } + } + + publishDiagnostics(parseTrees.keySet(), exceptions); + isDirty = false; + } + + private void publishDiagnostics(Set toUpdate, Set exceptions) { + Map> diagnosticsLists = new HashMap<>(); + if(!exceptions.isEmpty()) { + langServ.logi(() -> "Errors found, reporting " + exceptions.size() + " errors"); + for(ConfigCompileException e : exceptions) { + Diagnostic d = new Diagnostic(); + if(e.getTarget().file() == null) { + continue; + } + File file = e.getTarget().file(); + if(OSUtils.GetOS().isWindows()) { + // On Windows, canonical file paths have a capitalized drive letter. + file = new File(Character.toLowerCase(file.getPath().charAt(0)) + file.getPath().substring(1)); + } + String uri = file.toURI().toString(); + d.setRange(convertTargetToRange(e.getTarget())); + d.setSeverity(DiagnosticSeverity.Error); + d.setMessage(e.getMessage()); + List diagnosticsList = diagnosticsLists.get(uri); + if(diagnosticsList == null) { + diagnosticsList = new ArrayList<>(); + diagnosticsLists.put(uri, diagnosticsList); + } + diagnosticsList.add(d); + } + } + + List warnings = env.getEnv(CompilerEnvironment.class).getCompilerWarnings(); + if(!warnings.isEmpty()) { + for(CompilerWarning c : warnings) { + Diagnostic d = new Diagnostic(); + if(c.getTarget().file() == null) { + continue; + } + File file = c.getTarget().file(); + if(OSUtils.GetOS().isWindows()) { + // On Windows, canonical file paths have a capitalized drive letter. + file = new File(Character.toLowerCase(file.getPath().charAt(0)) + file.getPath().substring(1)); + } + String uri = file.toURI().toString(); + d.setRange(convertTargetToRange(c.getTarget())); + d.setSeverity(LangServ.getSeverity(c)); + d.setMessage(c.getMessage()); + List diagnosticsList = diagnosticsLists.get(uri); + if(diagnosticsList == null) { + diagnosticsList = new ArrayList<>(); + diagnosticsLists.put(uri, diagnosticsList); + } + diagnosticsList.add(d); + } + } + + // We need to report to the client always, with an empty list, implying that all problems are fixed. + for(String uri : toUpdate) { + List diagnosticsList = diagnosticsLists.get(uri); + PublishDiagnosticsParams diagnostics + = new PublishDiagnosticsParams(uri, diagnosticsList != null ? diagnosticsList : new ArrayList<>()); + client.publishDiagnostics(diagnostics); + } + } + + /** + * Returns the StaticAnalysis object that was used to compile the given URI.NOTE! This will only return a valid + * value after compilation, so always call getParseTree before calling this method. + * + * @param uri + * @return + */ + public StaticAnalysis getStaticAnalysis(String uri) { + return staticAnalysisMap.get(URIUtils.canonicalize(uri).toString()); + } + + /** + * Returns the ParseTree for the given URI. If the file is already compiled, this will return relatively quickly, + * otherwise it will wait for the file to be compiled and then return it. Note that in some cases, this will return + * null, which indicates that either this file shouldn't or couldn't be compiled. All callers must ensure that null + * returns are handled correctly. + * + * @param future + * @param uri + */ + public void getParseTree(CompletableFuture future, String uri) { + Runnable getter = () -> { + try { + future.complete(parseTrees.get(URIUtils.canonicalize(new URI(uri)).toString())); + } catch(URISyntaxException ex) { + future.completeExceptionally(ex); + } + }; + if(!isDirty) { + getter.run(); + } else { + rebuildTree(highPriorityProcessors, getter); + } + } + + private static boolean onceEverStartupCompleted = false; + + @SuppressWarnings("UseSpecificCatch") + public void startup() { + /* + This method should include only things that literally never change, unless the jar is recompiled. + This is only done once, and the data is stored in static fields. + */ + if(!onceEverStartupCompleted) { + synchronized(LangServModel.class) { + if(!onceEverStartupCompleted) { + highPriorityProcessors.execute(() -> { + // Create the base completion list. + { + List list = new ArrayList<>(); + for(FunctionBase fb : FunctionList.getFunctionList(api.Platforms.INTERPRETER_JAVA, null)) { + if(fb.getClass().getAnnotation(hide.class) != null) { + continue; + } + DocGen.DocInfo di; + try { + di = new DocGen.DocInfo(fb.docs()); + } catch(IllegalArgumentException ex) { + MSLog.GetLogger().Log(LANGSERVLOGTAG, LogLevel.ERROR, "Error parsing function \"" + + fb.getName() + "\". " + ex.getMessage(), Target.UNKNOWN); + continue; + } + CompletionItem ci = new CompletionItem(fb.getName()); + // ci.setCommitCharacters(Arrays.asList("(")); + ci.setKind(CompletionItemKind.Function); + ci.setDetail(di.ret); + ci.setDocumentation(di.originalArgs + "\n\n" + di.desc + + (di.extendedDesc == null ? "" : "\n\n" + di.extendedDesc)); + list.add(ci); + } + functionCompletionItems = list; + langServ.logv("Function completion list completed. (" + list.size() + ")"); + } + { + List list = new ArrayList<>(); + for(Event e : EventList.GetEvents()) { + final DocGen.EventDocInfo edi; + try { + edi = new DocGen.EventDocInfo(e, e.docs(), e.getName(), DocGen.MarkupType.HTML); + } catch(IllegalArgumentException ex) { + MSLog.GetLogger().Log(LANGSERVLOGTAG, LogLevel.ERROR, ex.getMessage(), Target.UNKNOWN); + continue; + } + CompletionItem ci = new CompletionItem(e.getName()); + ci.setCommitCharacters(Arrays.asList("'", "\"")); + ci.setKind(CompletionItemKind.Function); + ci.setDetail("Event Type"); + StringBuilder description = new StringBuilder(); + description.append(edi.description).append("\n"); + if(!edi.prefilter.isEmpty()) { + for(DocGen.EventDocInfo.PrefilterData pdata : edi.prefilter) { + description.append(pdata.name).append(": ") + .append(pdata.formatDescription(DocGen.MarkupType.TEXT)) + .append("\n"); + } + description.append("\n"); + } + if(!edi.eventData.isEmpty()) { + for(DocGen.EventDocInfo.EventData edata : edi.eventData) { + description.append(edata.name) + .append(!edata.description.isEmpty() ? ": " + edata.description : "") + .append("\n"); + } + description.append("\n"); + } + if(!edi.mutability.isEmpty()) { + for(DocGen.EventDocInfo.MutabilityData mdata : edi.mutability) { + description.append(mdata.name) + .append(!mdata.description.isEmpty() ? ": " + mdata.description : "") + .append("\n"); + } + description.append("\n"); + } + ci.setDocumentation(description.toString()); + list.add(ci); + } + eventCompletionItems = list; + langServ.logv("Event completion list completed. (" + list.size() + ")"); + } + { + List list = new ArrayList<>(); + for(FullyQualifiedClassName fqcn : NativeTypeList.getNativeTypeList()) { + try { + Mixed m = NativeTypeList.getInvalidInstanceForUse(fqcn); + CompletionItem ci = new CompletionItem(m.typeof().getSimpleName()); + ci.setKind(CompletionItemKind.TypeParameter); + ci.setDetail(m.getName()); + ci.setDocumentation(m.docs()); + ci.setCommitCharacters(Arrays.asList(" ")); + list.add(ci); + } catch(Throwable ex) { + // Skip it. + } + } + objectCompletionItems = list; + langServ.logv("Object completion list completed. (" + list.size() + ")"); + } + allCompletionItems = new ArrayList<>(); + allCompletionItems.addAll(functionCompletionItems); + allCompletionItems.addAll(eventCompletionItems); + allCompletionItems.addAll(objectCompletionItems); + langServ.logd("Completion list generated."); + }); + onceEverStartupCompleted = true; + } + } + } + } + + public void addWorkspace(List workspace) { + this.workspaceFolders.addAll(workspace); + dirtyTree(); + rebuildTree(lowPriorityProcessors, null); + } + + public void removeWorkspace(List workspace) { + this.workspaceFolders.removeAll(workspace); + dirtyTree(); + rebuildTree(lowPriorityProcessors, null); + } + + public List getWorkspaceFolders() { + return new ArrayList<>(workspaceFolders); + } + + // + /** + * Returns the document text either from the document cache, if the client is managing the document, or from the + * file system if it isn't. + * + * @param uri + * @return + * @throws java.io.IOException + */ + public String getDocument(String uri) throws IOException { + if(documents.containsKey(uri)) { + return documents.get(uri); + } + File f; + try { + f = Paths.get(new URI(uri)).toFile(); + } catch(URISyntaxException ex) { + throw new RuntimeException(ex); + } + return new ScriptProvider.FileSystemScriptProvider().getScript(f); + } + + public void didOpen(DidOpenTextDocumentParams params) { + // The document open notification is sent from the client to the server to signal newly opened text documents. + // The document’s truth is now managed by the client and the server must not try to read the document’s truth + // using the document’s Uri. + langServ.logv(this.getClass().getName() + "." + StackTraceUtils.currentMethod() + " called"); + String uri = URIUtils.canonicalize(params.getTextDocument().getUri()).toString(); + documents.put(uri, params.getTextDocument().getText()); + } + + public void didChange(DidChangeTextDocumentParams params) { + langServ.logv(this.getClass().getName() + "." + StackTraceUtils.currentMethod() + " called"); + langServ.logv(() -> "Changing " + params); + String uri = URIUtils.canonicalize(params.getTextDocument().getUri()).toString(); + // If the processing mode is changed to incremental, this logic needs modification +// String text = documents.get(uri); + if(params.getContentChanges().size() > 1) { + langServ.logw("Unexpected size from didChange event."); + } + for(TextDocumentContentChangeEvent change : params.getContentChanges()) { + String newText = change.getText(); + documents.put(uri, newText); + } +// dirtyAndRebuildTree(lowPriorityProcessors); + } + + public void didClose(DidCloseTextDocumentParams params) { + // The document close notification is sent from the client to the server when the document got closed in the + // client. The document’s truth now exists where the document’s Uri points to (e.g. if the document’s Uri is + // a file Uri the truth now exists on disk). + langServ.logv(this.getClass().getName() + "." + StackTraceUtils.currentMethod() + " called"); + String uri = URIUtils.canonicalize(params.getTextDocument().getUri()).toString(); + documents.remove(uri); + } + + public void didSave(DidSaveTextDocumentParams params) { + langServ.logv(this.getClass().getName() + "." + StackTraceUtils.currentMethod() + " called"); + dirtyAndRebuildTree(lowPriorityProcessors); + } + + // + /** + * Compiles the file. Null is returned if the file couldn't (or shouldn't) be compiled, and any errors will have + * already been reported to the client. + * + * @param uri The URI of the file to compile. + * @param staticAnalysis + * @param env + * @param exceptions + */ + private ParseTree doCompilation(final String uri, StaticAnalysis staticAnalysis, Environment env, + Set exceptions) { + try { + + String code; + // Eventually we want to rework this so that this is available + Set> envs = new HashSet<>(); + for(Class c + : ClassDiscovery.getDefaultInstance().loadClassesThatExtend(Environment.EnvironmentImpl.class)) { + envs.add(c); + } + + File f; + { + URI uuri = new URI(uri); + if("untitled".equals(uuri.getScheme())) { + // For open files that aren't saved to disk, the client sends something like "untitled:untitled-1", + // which isn't a valid file provider, so we can't call Paths.get on it. Instead, we just mock + // the name here. We also need to provide getAbsoluteFile, so that the below call to getParentFile + // will work. + f = new File(uuri.getSchemeSpecificPart()).getAbsoluteFile(); + } else { + f = Paths.get(uuri).toFile(); + } + } + + env.getEnv(GlobalEnv.class).SetRootFolder(f.getParentFile()); + TokenStream tokens = null; + ParseTree tree = null; + try { + ParseTree fTree; + langServ.logd(() -> "Compiling " + f); + code = getDocument(uri); + if(f.getName().endsWith(".msa")) { + tokens = MethodScriptCompiler.lex(code, env, f, false); + fTree = new ParseTree(null); + final Environment finalEnv = env; + MethodScriptCompiler.preprocess(tokens, envs).forEach((script) -> { + try { + script.compile(finalEnv); + } catch(ConfigCompileException ex) { + exceptions.add(ex); + } catch(ConfigCompileGroupException ex) { + exceptions.addAll(ex.getList()); + } + script.getTrees().forEach(r -> fTree.addChild(r)); + }); + } else { + // Actually, for untitled files, this may not be a correct default. However, there's no + // other good way of determining that, so let's just assume it's pure methodscript. + tokens = MethodScriptCompiler.lex(code, env, f, true); + fTree = MethodScriptCompiler.compile(tokens, env, envs, staticAnalysis); + } + tree = fTree; + } catch(ConfigCompileException e) { + exceptions.add(e); + } catch(ConfigCompileGroupException e) { + exceptions.addAll(e.getList()); + } catch(Throwable e) { + // Just skip this, we can't do much here. + langServ.loge(() -> StackTraceUtils.GetStacktrace(e)); + } + + return tree; + } catch(Throwable t) { + client.logMessage(new MessageParams(MessageType.Error, t.getMessage() + "\n" + + StackTraceUtils.GetStacktrace(t))); + } + return null; + } + + /** + * This function compiles MSA files and returns the Script objects.Note that this is not intended for use to get + * compile errors.If there are compile errors, it will not call the future.This is also the case if the uri does not + * point to a msa file.Any other errors will result in the future being called. + * + * @param future + * @param threadPool + * @param uri + * @param withDelay + */ + @SuppressWarnings("UnnecessaryReturnStatement") + public void doPreprocess(CompletableFuture> future, Executor threadPool, final String uri, + boolean withDelay) { + threadPool.execute(() -> { + URI uuri; + String code; + try { + uuri = new URI(uri); + code = getDocument(uri); + } catch(URISyntaxException | IOException ex) { + return; + } + File f; + if("untitled".equals(uuri.getScheme())) { + // For open files that aren't saved to disk, the client sends something like "untitled:untitled-1", + // which isn't a valid file provider, so we can't call Paths.get on it. Instead, we just mock + // the name here. We also need to provide getAbsoluteFile, so that the below call to getParentFile + // will work. + f = new File(uuri.getSchemeSpecificPart()).getAbsoluteFile(); + } else { + f = Paths.get(uuri).toFile(); + } + if(!f.getName().endsWith(".msa")) { + return; + } + + langServ.logd(() -> "Compiling " + f); + + Environment env; + try { + // Cmdline mode disables things like security checks and whatnot. + // These may be present in the runtime environment, + // but it's not possible for us to tell that at this point. + env = Static.GenerateStandaloneEnvironment(false, EnumSet.of(RuntimeMode.CMDLINE)); + // Make this configurable at some point. For now, however, we need this so we can get + // correct handling on minecraft functions. + env = env.cloneAndAdd(new CommandHelperEnvironment()); + } catch(IOException | DataSourceException | URISyntaxException | Profiles.InvalidProfileException ex) { + throw new RuntimeException(ex); + } + CompilerEnvironment compilerEnv = env.getEnv(CompilerEnvironment.class); + compilerEnv.setLogCompilerWarnings(false); // No one would see them + GlobalEnv gEnv = env.getEnv(GlobalEnv.class); + gEnv.SetRootFolder(f.getParentFile()); + // Eventually we want to rework this so that this is available + Set> envs = new HashSet<>(); + for(Class c + : ClassDiscovery.getDefaultInstance().loadClassesThatExtend(Environment.EnvironmentImpl.class)) { + envs.add(c); + } + try { + TokenStream tokens = MethodScriptCompiler.lex(code, env, f, false); + List + +On this page, you can view the downloads. In general, you should download the latest version, as the +time between versioned releases is very long. Only the latest version is currently supported. + +
+ Jars +

This is the normal dev build pipeline, and the recommended build here is what you should get unless you + specifically need something else.

+
+
+ +
+ Loading version information +
+
+

If you need a specific build, these are the manually "pinned" builds of various milestone builds. You should + only download from this list if there is something specific that you need.

+
+
+ +
+ Loading version information +
+
+
+

+This is the Windows Cmdline installer. This is the preferred (though not only) way of installing the cmdline version +on Windows. Unfortunately, we do not have a code signing certificate, so the Windows SmartScreen scan will initally +block installation. Please click "More Info", then "Run anyway" to bypass this. +

+
+
+ +
+ Loading version information +
+
\ No newline at end of file diff --git a/src/main/resources/docs/Enums_and_Masks b/src/main/resources/docs/Enums_and_Masks new file mode 100644 index 0000000000..cd3bf44076 --- /dev/null +++ b/src/main/resources/docs/Enums_and_Masks @@ -0,0 +1,169 @@ +{{unimplemented}} + +Often times you may find yourself with a unique set of predefined constants, for instance, +compass directions NORTH, SOUTH, EAST, and WEST, or days of the week. Additionally, you may +have a set of these enums, which can be represented as a bit mask. For these two situations, +you should use the ''enum'' and ''mask'' types, respectively. + +== Enum Types == +An enum is a specially declared class, which follows certain extra rules, but otherwise behaves +just like a normal class. It may have data members and methods, just like any other class. The +exceptions are that the class is effectively final (it cannot be extended by other classes/enums), +and the constructor, if provided, must be +private. The default constructor for enum values are private as well. Essentially, there must be no +way to instantiate the enum object outside of the context of the enum itself, and it can't be +overridden. To declare a simple enum, use the following syntax: + +<%CODE| +enum Day { + SUNDAY, MONDAY, TUESDAY, WEDNESDAY, + THURSDAY, FRIDAY, SATURDAY +} +%> + +The enum values are considered as if they are public static final Day members +of the class Day. Therefore, you cannot have member variables with the same name as the +enum. To use the enum value, you would use it the same as any other static member: + +<%CODE| +Day::SUNDAY +%> + +Note that there is no "@" symbol here, even though they in some ways act like a member variable. +By convention, the names of enum values are all caps, with words separated by underscores, but +this is not enforced. +Enums can be {{function|switch}}'d over, as such: + +<%CODE| +Day @day = Day::MONDAY; // This might be from user input +switch(@day){ + case MONDAY: + msg('Manic Monday'); + case WEDNESDAY: + msg('Hump day!'); + case FRIDAY: + msg('Almost to the weekend!') + case SATURDAY, SUNDAY: + msg('Yay, weekend!') + default: + msg('What is today again?') +} +%> + +Often times, you have a switch statement, which you need to be sure to update, if +a new value is added. For this, you may use the {{function|switch_all}} function. +This function requires that all enum constants have a case, and no default may be +used. + +<%CODE| +Day @day = Day::MONDAY; +switch_all(@day){ + case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY: + msg('Weekday') + case SATURDAY: // Oops, forgot SUNDAY, this is a compile error + msg('Weekend') +} +%> + +The above code will cause a compile time error, indicating that the SUNDAY enum +was not provided for. This provides for better error checking, in the case where +each enum should have definite handling code, and is subject to new enum values +being added in the future, instead of a generic handler that throws a runtime +exception. + +You may also have more complicated enum class definitions, and you may implement +interfaces as well. If a constructor is provided, it must be private, but otherwise +is acceptable. The special constructor syntax is demonstrated in the example below +as well. As you can see, each parameter is passed in separately for each enum, so +getInfo() for each one would return different data. + +<%CODE| +enum Compass { + NORTH('up'), + SOUTH('down'), + EAST('right'), + WEST('left'); + + private final string @info; + + private Compass(string @info){ + @this->info = @info; + } + + public string getInfo(){ + return(@info); + } +} +%> + +Use of enum constants sometimes have special type inference. If the compiler can +definitely determine the enum to use, the class name can be left off. So, if the +procedure _func() is defined as such: proc _func(Compass @direction, ...) +Then both _func(Compass::NORTH) and _func(NORTH) are +acceptable. This is only possible when the declared type is a concrete enum type, +and not the Enum or mixed superclasses. Additionally, auto string types are cross +castable to an enum, so _func('NORTH') is also valid. + +All enums inherit a few static methods from the enum superclass as well, the +most important being valueOf() and values(). valueOf() converts a string to +the corresponding enum, and values() returns an array of all enum values in the +enum. + +== Mask Types == + +An enum mask is used when you could have a set of enums. The Mask class provides +methods for converting a set of enums to an integer, and vice versa. Enums that +have 64 or less enums may be used as a mask. The default integer mask value of each enum is +based on the ordinal value, 2 ** ordinal. However, this default may be changed by overriding +the mask() method, and returning whatever integer value you like. It is worth noting that +changing existing enum order (besides simply renaming them) causes any old integer masks +to be incompatible with the new versions. To use a mask, you must use Mask's static +method create(), and provide the enum class, if it can't be inferred. + +<%CODE| +Mask @day = Mask::create(); # This can be inferred, because the return type must be Mask +msg(Mask::create(Day)); # This must be specified, since the compiler can't infer the return type. +%> + +Mask implements Set, so all the operations available with a Set are also available to the Mask, +but of note are two extra methods, toInt(), and the static fromInt(). + +<%CODE| +enum Compass { + NORTH, // mask value: 1 + SOUTH, // mask value: 2 + EAST, // mask value: 4 + WEST, // mask value: 8 +} + +Mask @c = Mask::create(); +@c->add(NORTH); +@c->add(SOUTH); +msg(@c->toInt()); # returns 3, which is the equivalent of bit_or(1, 2), or 0011 in binary + +Mask @c = Mask::fromInt(12); // 12 in binary is 1100, which represents EAST and WEST +msg(Mask::fromInt(Compass, 12)); // Required to provide the type, since inference won't work here +%> + +The default implementation of mask() looks like this: +<%CODE| +public int mask(){ + return(2 ** ordinal()); +} +%> + +However, you may override this to return a different value, depending on the circumstances. +Do note however, that overriding this method incurs a performance penalty, because you could +dynamically change the mask values, when fromInt is called, it will not cache the mask values +for your enum, like it will for the default. + +To see if an enum value is in the mask, use contains(). + +<%CODE| +Mask @a = Mask::create(); +@a->add(NORTH); +@a->add(SOUTH); + +msg(@a->contains(NORTH)); // true +msg(@a->contains(WEST)); // false +%> \ No newline at end of file diff --git a/src/main/resources/docs/Events b/src/main/resources/docs/Events new file mode 100644 index 0000000000..f140d0665e --- /dev/null +++ b/src/main/resources/docs/Events @@ -0,0 +1,187 @@ + +Event handling allows users to register MScripts to be triggered in many other places, not just on a command. This +expands the usefulness of CommandHelper to the point where it is roughly equivalent to any other plugin that can be +written for Minecraft. + +==main.ms== +In order to initially register an event, you must include a hook inside a "main" function. Upon server startup, any +script in main.ms will be run. This should be used as an opportunity to register your initial hooks. Now, this doesn't +prevent you from registering a hook elsewhere in a script, but for the common case, you'll want to put your hooks in +here. You can also use this opportunity to do other things, perhaps writing out to the console or something, but +typically only event registrations will be in here. This does work as a "server start" event, but it gets re-run +whenever you /reloadaliases also. +==Registering an Event== +To register an event, use the {{function|bind}} function. Let's take a look at the function's signature: +<%CODE| +bind(string eventName, array options, array prefilter, @event_object, [@custom_params...]) { + +} +%> +If you note, this looks very similar to the {{function|proc}} function. In essence, it's basically the same, except +CommandHelper is responsible for calling the function, when the specified event occurs, not you. The options object +allows you to set certain options for this event, including setting a custom event id, and event priority. The prefilter +allows you to tell the event handler to pre-filter which events are intercepted (Prefilters are a more complicated +subject, and have their own [[Prefilters|page]]). Both the options parameter and the prefilter +parameter may be null. The information in the @event_object depends on the event. Each event sends different parameters. +Finally, the custom params are discussed below in the scope section. So, let's look at the player_interact +event, which occurs whenever the player left or right clicks a block or the air, and show a few use cases. First, we +need to know the event's signature though, which can be looked up in the [[Event_API|Event API]]. + +player_interact: +* @event['action']: One of either: left_click_block, right_click_block, left_click_air, or right_click_air +* @event['block']: The id of the block they clicked, or 0 if they clicked the air. If they clicked the air, neither \ +facing or location will be present. +* @event['player']: The player associated with this event +* @event['facing']: The (lowercase) face of the block they clicked. +* @event['location']: The (x, y, z, world) location of the block they clicked + +<%CODE| +bind(player_interact, null, null, @event) { + if(@event['action'] == 'left_click_block') { + msg('You just left clicked a block with id ' . @event['block']); + } +} +%> + +So, what's going on here? @event contains the event data. It is an associative array that contains at least the +following information: +* "type" - The event type. Normally you've already got this data, because you registered it, however, wildcard events \ +may be possible in the future. +* "macrotype" - This determines what kind of data there will be in the rest of the event + +The rest of the data in the event is dependent on the event type, and this information can be looked up in the event +table. + +So, what happens? If the player left clicks a block, it will tell them the block id they just clicked. Not terribly +useful by itself, but that should give you an idea of how events work in general. + +==Unregistering an Event== +Each event returns some piece of data that uniquely identifies this registration. To unregister an event, you need to +know this data (i.e., store it in a variable). This data is not guaranteed to be unique across reloads of the server +however, so persisting it across reloads doesn't make sense. If you need to always know what the event id is, you can +force a particular id on the event. This id must be unique across all events. To ensure the data is unique compared to +automatically generated event ids, you may not register an id with the following syntax: "string:int". This formatting +is enforced by the function. To unregister the handler, use the {{function|unbind}} function. Let's look at actual code +to more clearly see usage. +<%CODE| +// Automatically generated id +@event_id = bind(player_interact, null, null, @event) { + '' #Handling code for event +}; + +unbind(@event_id); // This was the id returned by bind() +%> + +In the case where we assign our own id, so that we can persist, or otherwise always know what our event's id is, we can +use the following syntax +<%CODE| +bind(player_interact, array(id: 'interact-123'), null, @event) { + '' // Handling code +} + +unbind('interact-123'); +%> + +In addition, an event may unregister itself from within the handler by running unbind() without any +arguments. This will cause the handler to no longer run, but it will finish running this last time. This is useful for +onetime event handlers, perhaps if the event was registered in a command. + +<%CODE| +bind(player_interact, null, null, @event) { + // Event handling code goes here + unbind(); +} +%> + +==Running inside an event handler== +The context of events are sometimes different than when a command is run. For instance, in a mob spawn event, no player +is involved, so {{function|player}} will return null. For player based events however, player() does return the player +that triggered the event. + +===Scope=== +Events have the same type of scope as functions, i.e. only variables passed in are assigned. This is what the custom +params are for. If you need to have these values passed in to the event, you may do that with these extra parameters. +The values are copied over ''at bind time'', not at event run time. Though the values in the variables may be changed +during the execution of the script, they are reset to the bind time values each time the function is triggered. Using +{{function|import}} and {{function|export}} is a good way to get data in and out of the event handlers, if needed, +though you may consider redesigning if you're doing it this way, because there is likely a simpler way. + +===Return=== +If {{function|return}} is called from the code, it will cause the event handler to stop running, which can +be a convenient way to stop the handler short. Currently, any values returned are ignored, in the future, it will +be a compile error to return anything other than void. + +==Order of events== +Event handlers (the code inside a bind) are fired off in order from highest to lowest, then monitor. All +CommandHelper events are cancellable; that is to say that if an event is cancelled, a flag is set and +{{function|is_cancelled}} will return true, and if the underlying minecraft event is also cancellable, it will also be +cancelled. To cancel an event, call cancel([state]) in your code. The rest of your code will continue +running, so if you need to stop after cancelling, you should return(). + +Handler priorities are handled as such: Suppose we have three events registered, at high, low, and lowest. The handler +at high gets the event first. The handler is free to modify event parameters, which will then be passed to the low +priority handler, which is then allowed to further modify the event as required, and then it is passed on further to the +lowest priority handler, which is also free to edit the event parameters. Finally, the monitor level handlers are +allowed to see a read-only version of the event. This chain can be modified in two ways. First, a handler may call +{{function|lock}} on an event, which will cause calls to modify_event by lower priority handlers to fail. +Events essentially become read only at that point, however handlers may still react otherwise. In addition, parameters +may be sent to lock(), which will only lock the specified event parameters, leaving the rest of the +parameters freely editable by lower priority handlers. The second option a higher priority handler has, is to +{{function|consume}} an event. If an event is consumed, it is not even passed to lower priority handlers (except +monitor). This will prevent lower priority events from even seeing the event in the first place. + + + +So, what is the purpose of all this complexity in handler priorities? When handlers fight, things can get messy, and +simply having 5 or 6 priority levels isn't usually enough flexibility to specify the desired behavior. For instance, +if a handler absolutely needs to read the original value of an event parameter and act on the event externally, but only +trigger something else as long as some other parameter is some value upon it actually triggering, this would be +impossible without the callbacks. Or if a handler wants to act on some event, and other events also acting on it would +cause issues, it can consume the event, and not have to worry about undesired behavior. This is why choosing a priority +is important, and the priority you choose should be based on the following guidelines: +* LOWEST: Lowest priority handlers should expect to not be able to edit parameters, or even run, but should instead be \ +a "default" occurrence, should nothing else choose to deal with this event. +* LOW: Handlers that don't need to run at all, but would like to be able to edit and see at least some events should \ +register at low priority. +* NORMAL: Normal priority handlers intend on being run as they expect, but there would be not be a big loss if they \ +weren't able to run as intended. +* HIGH: Handlers that play an important role, but don't need absolute say over an event should register as a high \ +priority handler. +* HIGHEST: Highest level handlers receive the event first. Handlers that need to have absolute say about the event \ +should register at this level. +* MONITOR: Monitor level handlers receive the event last, and cannot edit the event in any way. This should be used \ +for logging type handlers. In general, if the handler hooks in to game functionality (even if it doesn't intend on \ +modifying the event) it should register at Lowest or Low. The exception to this is if a handler wants to get the event \ +even if it is cancelled or consumed, at which point it is appropriate to use monitor. Monitor level handlers still \ +receive cancelled and consumed events, and can check the status of those flags with the is_cancelled() \ +and is_consumed() functions. + +In the event that two or more handlers register at the same priority, other handlers will receive the event even if it +is consumed (however, they can not cause it to stop being consumed) and they can still modify the event parameters +(though they cannot "unlock" the event for lower priority handlers). The order that handlers fire in within priorities +is determined by bind order. + +==Debugging== +If you are having trouble getting a script to do what you want, especially if you have many scripts running, it may be +helpful to use some of the debugging tools at your disposal. {{function|dump_events}} will print out all the currently +bound events, including their exact location in your script, so you can easily refer to it. Also, if you need to get +meta information at runtime, you can +use the following code snippet: + +<%CODE| +bind(event_name, array(priority: 'MONITOR'), null, @event) { + console(event_meta()); +} +%> + +Because the event handler is registered at monitor level, it will run last, and it will print out meta information about +the active event, including information about what handlers received, locked, consumed, modified, or cancelled events. +Because logging information for {{function|event_meta}} does use marginally more resources, history is only logged if +debug mode is on (this can be set in preferences.txt). + + diff --git a/src/main/resources/docs/Exceptions b/src/main/resources/docs/Exceptions index cf4e92224d..0761018144 100644 --- a/src/main/resources/docs/Exceptions +++ b/src/main/resources/docs/Exceptions @@ -1,88 +1,223 @@ +Exceptions can be thrown by various functions at runtime. This indicates that there was some issue with the +input data. There is a straightforward way to deal with these errors however, using a try/catch block. + +== Example == + +A good example is code that takes a user input. Assume that we want to validate that the user has provided us +with an integer like string. We might write the following: + +%%CODE| +@userInput = _getUserInput(); +@value = integer(@userInput); +_doSomethingWithTheValue(@value); +%% + +In this case, if the user input procedure returns '2', then the code will work as intended. If it +returns 'string' however, it will throw an exception. As it stands, with no exception handling in +place, you would see a similar error to this: + +%%PRE| +CastException: Expecting a double, but received string instead + at proc _throw:/home/user/include2.ms:13.3 + at <>:/home/user/include2.ms:16.2 + at <>:/home/user/include1.ms:10.2 + at <>:/home/user/test.ms:12.3 +%% + +The stack trace can help you trace back the actual error, as the error itself is not necessarily with the top +line in the stacktrace, which is where the actual exception was thrown. Instead, it might be because you're +passing in an invalid parameter to a proc, so it may be useful to step back a few items in the stack trace to +see what's calling what. Several things are added to the stack trace, particularly procedure stack, but also +closures and includes. + +This is the default exception handling mechanism. It prints out the exception type, exception message, and a +stacktrace. But perhaps we want to print a custom message to the user. In this case, we can trap the exception +using a try/catch block, and handle it in a custom way. In this case, we want to "catch" the CastException that +is "thrown". + +%%CODE| +try { + @userInput = _getUserInput(); + @value = integer(@userInput); // Can throw CastException + _doSomethingWithTheValue(@value); +} catch(CastException @e){ + // This will run if integer(@userInput) throws a CastException + msg("The value must be an integer"); +} +%% + +This will message the user if the value is not correct, using a custom error message. + +== Multicatch == + +Sometimes, a block of code might throw multiple types of exceptions, and we want to handle each type differently. +Perhaps our _doSomethingWithTheValue procedure was capable of throwing an IOException. We ''could'' wrap +the entire block in a second try catch block, but there's an easier way, using multicatch. + +%%CODE| +try { + @userInput = _getUserInput(); + @value = integer(@userInput); // Can throw CastException + _doSomethingWithTheValue(@value); // Can throw IOException +} catch(CastException @e) { + msg("The value must be an integer"); +} catch(IOException @e) { + msg("There was an error with the disk, and your request could not be completed."); +} +%% + +Each catch block will only run if the exception thrown matches the type. If an exception is thrown that doesn't +match any of the types, it will continue up the stack as if there were no try/catch in place at that point. + +=== Catch clause specificity === + +When an exception is thrown, the catch clauses are checked one by one, in declaration order, for exception matches. +This means that you should order your catch clauses from most specific to least specific. Consider the following code: + +%%CODE| +try { + code(); +} catch(Exception @e){ + msg("Will catch the exception"); +} catch(IOException @e){ + msg("Will never run, because the catch-Exception clause will always catch the exception first, because" + . " IOException extends Exception"); +} +%% + +Be sure to always order your catch clauses appropriately. + +== The exception object == + +The exception that is thrown is an associative array that has some perhaps useful information in it, such as the exception +message, line number, and things like that. Here is an implementation of the default handling, except it is being handled +from within the script. + +%%CODE| +try { + code(); +} catch(CastException @e) { + @classType = @e['classType']; + @message = @e['message']; + @stackTrace = @e['stackTrace']; + msg(colorize("&c@classType&f: @message")); + foreach(@element in @stackTrace){ + @procedureName = @element['id']; // This could be a procedure or a closure, or a few other things + @file = @element['file']; + @line = @element['line']; + msg(colorize("\t&a@procedureName&f:&6@file&f:&b@line")); + } +} +%% + +The above example demonstrates the complex usage of the exception object. In addition, it's worth noting that if you are +having trouble with code, you could get a stacktrace by throwing and catching a custom exception, however, it's more +straightforward to use {{function|get_stack_trace}} + +== The Throwable type == + +All exceptions extend the Exception type, which further extends the Throwable type. Another type of Throwable is +a the Error type (and subclasses). If you specifically catch Errors, they can be caught, but this is not recommended, +as it usually indicates a very severe error. While it is possible to directly catch a Throwable of any type, you should +generally not catch that, and instead catch specific types that you are interested in, or Exception, if need be. + +== Throwing your own exceptions == + +You may also throw your own exceptions. See the documentation for {{functions|throw}} for more information. + +== finally clause == + +Sometimes you may want to run code after the try block, regardless of what happens in the try block, including successful +invocation of the code, an exception being thrown and further exceptions being thrown from the catch block, +or simply return()ing in the try block. The finally clause +can be used to accomplish this. The finally clause must come after all catch clauses, but may be used even if no +catch clauses are defined (this is called a try/finally block). This is most useful for cleanup that needs to happen +regardless of how the code exited. + +There are three specific examples that this might be useful for: + + +# If code within the `try` block exits via `return` +# If code within a catch block either rethrows the caught exception, or--accidentally or intentionally--ends up throwing a new one. +# If the code within the `try` block encounters an exception for which there is no catch. + +Take the following examples: -Exceptions are thrown by various functions at runtime. Since MScript is not yet strongly typed, -it is not possible to catch all errors at compile time. Due to this, it is useful to be able -to programmatically determine if a function failed. To this end, since version 3.1.2, -MScript has the {{function|try}} function. This function takes 3 or 4 arguments: %%CODE| -try(try_code, ivar, catch_code, [interested_types]) +try { + @value = codeThatOpensAResource(); + return(@value); +} finally { + cleanUpResource(); +} %% -If you are familiar with other programming languages' try-catch mechanisms, then this -construct will be familiar. The code at try_code is run normally. If any -function in the code causes an exception to be thrown, execution will halt, and program -flow will start at catch_code. The exception thrown will be stored in -ivar, so that you can programmatically examine the cause of the exception. -Many times, you are only interested in a certain type of exception. This is where the optional -interested_types variable comes in. If omitted, all exceptions are caught. -If provided, it may be a single string, or an array of strings, where the provided values -are one or more of the exception types listed below. There are a select few errors that can -be caused by runtime issues which cannot be caught, but by far, most runtime issues can be -caught in a try function. In addition, you can trigger an exception being thrown with the -{{function|throw}} function. + %%CODE| -throw(exception_type, msg) +try { + code(); +} catch(Exception @e) { + throw(IOException, "Another exception"); +} finally { + // This section still runs +} %% -throw accepts any valid exception type listed below, as well as a message. -The line number will automatically be added. This exception is then passed up the chain, -just as if any other function had thrown the exception. If the exception type is null, -this exception is uncatchable, however, it is best practice to use the {{function|die}} -function if you intend on killing a script. - -If an exception is passed all the way to the root of the script, and the interpreter has to -catch the exception, the script will terminate, and the default message will be logged to -console, and displayed to the user. In most cases, this may be enough. Also, in general, -exceptions have been massively improved, all exceptions give a much more detailed error -message, and also provide a line number to assist in debugging your scripts. Also note -that if debug-mode is on, ALL exceptions that are thrown will log to the console, even if -they are caught. This can help debug a potential problem with your script. The API has been -updated to include a list of possible exceptions that can be thrown by a function, and a -list of what the exception types are, and what might cause them to be thrown are listed below. -Please note that it is entirely possible that an exception being thrown was not noted in the -API -- this is a bug in the documentation. Please report it so that it can be corrected. Also -note that it is possible for the try function itself to throw an exception, if the arguments -are not of the proper type. Though it is possible to further catch those exceptions, it -probably means that your code is poorly designed. - -==Example== -When accepting user input, it is important to verify that their input is valid. Using -exceptions allows you to easily catch errors in their input. + %%CODE| -/test $index= >>> - # Assign an array to the variable @a - assign(@a, array('0', '1', '2')) - # array_get can possibly throw an IndexOverflowException, or a CastException - try( - msg(array_get(@a, $index)) - , @ex, - # The zeroth element in the exception tells us the exception type. - # That is important to get here, so we display proper error messages - if(equals(array_get(@ex, 0), IndexOverflowException) - , die('That index is not valid') - , die('Please enter an actual number') - ) - # We could have left this blank, but this allows us to more precisely filter our exceptions - , array(IndexOverflowException, CastException)) -<<< +try { + codeThatThrowsANullPointerException(); +} catch(IOException @e) { + // This block won't be run in this case +} finally { + // This code runs anyways, though the NPE will be thrown further up +} %% -==Non-Exception data validation== - -In addition to exception handling in 3.1.2, several functions have been added that allow you -to validate data without using the try-catch framework. Make note of the existence of the -following functions: -*{{function|array_index_exists}} -*{{function|is_array}} -*{{function|is_boolean}} -*{{function|is_double}} -*{{function|is_integer}} -*{{function|is_null}} -*{{function|is_string}} - -These functions will allow you to validate that the data entered is in fact of the specified -type, or can be cast to the specified type. - -==Exception Types== -Both the spelling and capitalization are important when using the name of an exception. The -proper format is displayed in the header of each section. +== Caused By, and rethrowing == +Exceptions might need to be rethrown. This is supported by {{function|throw}}. For instance: + +%%CODE| +try { + throw(IOException, 'message'); +} catch(IOException @e){ + if(/* some condition is true */){ + // We don't care about this exception + } else { + throw(@e); + } +} +%% + +This can be used if you want to conditionally handle the exception, but continue to throw the exception up the +chain in other cases. + +You may find that you have to throw an exception due to another exception. To do this, you may also used the Caused By mechanism. This will +cause the exception to chain. This is also supported with {{function|throw}}. + +%%CODE| +try { + try { + throw(IOException, 'ioexception'); + } catch(IOException @e){ + throw(CastException, 'castexception', @e); + } +} catch(CastException @e){ + msg(@e['causedBy']); // This will be the IOException +} +%% + +This can be useful for times that you need to throw another exception from within the catch block, but don't want to hide +the original exception, or when you need to otherwise wrap the original exception. It is quite useful to know what exception +originally caused this chain. This can be accessed from the causedBy index in the exception object. + + +== Old usage == + +There is an old, now deprecated usage as well, see the article [[OldExceptions|here]] +for information on how to use it. For new code, however, the new format should always be used. + +== Known Exception Types == + +Exception types can be added by extension authors, but here is a list (and documentation) for all known exceptions. %%EXCEPTION_TYPES%% diff --git a/src/main/resources/docs/Execution_Queue b/src/main/resources/docs/Execution_Queue new file mode 100644 index 0000000000..61e9261844 --- /dev/null +++ b/src/main/resources/docs/Execution_Queue @@ -0,0 +1,93 @@ +An execution queue is a queue of closures, which are queued up to be run in sequence by the engine. Unlike set_timeout +and set_interval, there is no time component, it's simply a queue of operations to execute sequentially. There can be +multiple execution queues as well, though there is a single default queue. The general idea is that queueing up a +closure is instant. All the queue operations return immediately, and the closure is run at a later time. Delays and +queue management functions are all provided to make doing complex operations possible. + +== Basic Queue Usage == +The most basic operation is the {{function|queue_push}} function. This pushes a new routine at the end of the queue. +For instance: + +<%CODE| +queue_push(closure() { + msg('Hello World!'); +}); +%> + +would be valid. This will run the code msg('Hello World!') at the next available opportunity, after other +queued operations occur. Each +time the queue runs an operation, it frees the server thread back up, so this is a useful mechanism for scheduling +extremely long running tasks, without +killing the server. Any value returned from the closure is ignored however, so it is not useful for things that need to +block. Note that between operation executions, there will likely be around a 10ms delay, while control of the main +thread is re-gained, and a 50ms delay between noticeable ticks. This makes +the system undesirable for fluid animations, however, this is not a limitation of MethodScript itself. + +== Queue Names == +There can be multiple queues. Each queue is simply named something, with the default queue being "default". Operations +across queues are not executed in any particular order with respect to each other, but within each queue, operations are +guaranteed to be sequential. Each queue function takes an optional parameter which specifies the queue name. + +<%CODE| +queue_push(closure() { + msg('Hello World!'); +}, 'default'); +%> + +To make a new queue, just use a new name, it will be created automatically. + +== Delays == +Delays can be inserted into a queue as well. This delay will suspend the queue's operation momentarily, but will not +freeze the server, since the delay will be on the execution queue's thread, not the server thread. This is a separate +operation, and uses the {{function|queue_delay}} function. + +<%CODE|queue_delay(1000, 'default'); #Put a 1 second delay on the default queue. 'default' is optional.%> + +== Other Queue Operations == +The queue works like a doubly ended queue. There are other operations to control this queue, with a few caveats. Once an +operation has been submitted for execution, it cannot be cancelled anymore, and operations are always pulled from the +front of the queue. You can use these other methods to control the queue: + +{| +|- +| {{function|queue_remove}} +| Removes the last operation on the queue. +|- +| {{function|queue_remove_front}} +| Removes the operation on the queue that would be running next +|- +| {{function|queue_push_front}} +| Instead of pushing an operation onto the back of the queue, pushes it to the front. \ +Barring other calls to queue_push_front, that means that this operation will execute next. +|- +| {{function|queue_clear}} +| Clears all pending operations from the queue +|- +| {{function|queue_running}} +| Returns true if a queue is currently running +|- +| {{function|queue_delay_front}} +| Works like queue_delay(), but pushes the delay on the front of the queue +|} + +== Example == + +This example splits a loop up into many smaller chunks, which should cause the server to not freeze + +<%CODE| +for(int @i = -5000, @i < 5000, @i++) { + queue_push(closure() { + set_block_at(@i, 50, @i, 1, 'world'); + }, 'block-set'); + // Sets a diagonal line of blocks to stone, + // from [-5000, 50, -5000] to [5000, 50, 5000] +} + +queue_push(closure() { + msg('Block set queue is finished.'); +}, 'block-set'); +%> + +Queueing up the operations should complete relatively fast, but the full task will take a while. + + diff --git a/src/main/resources/docs/Extension_Development b/src/main/resources/docs/Extension_Development index 47a1c03abc..60c44d6dea 100644 --- a/src/main/resources/docs/Extension_Development +++ b/src/main/resources/docs/Extension_Development @@ -1,12 +1,12 @@ == Developing an extension == -Extensions provide a means of adding functionality to CommandHelper and the MethodScript engine. While the API used is under construction still, it is still deemed stable enough for production use. Any breakages will be minimal, and deprecation will be used where it applies. +Extensions provide a means of adding functionality to CommandHelper and the MethodScript engine. An extension currently consists of three core parts: -# The lifecycle class, which takes care of the life and identity of the extension. -# Events, which add new events that MethodScript can use, and -# Functions, which add new functionality to MethodScript. +# LifeCycle +# Functions +# Events === LifeCycles === @@ -17,7 +17,7 @@ The lifecycle class is the central part of the extension and does two things: The minimal definition is as follows: - +%%SYNTAX|java| @MSExtension("ExtensionName") // Place your extension name here! public class MyExtension extends AbstractExtension { @Override @@ -25,15 +25,17 @@ public class MyExtension extends AbstractExtension { return new SimpleVersion(1,2,3); // Set your version here. } } - +%% -The class name given by the definition doesn't matter, but does assist with debugging in case of a stacktrace. See the source code of AbstractExtension for the full set of methods available to this class. +The class name given by the definition doesn't matter, but does assist with debugging in case of a stacktrace. +See the source code of AbstractExtension for the full set of methods available to this class. === Functions === -Functions will usually be the first thing a simpler extension will be used for. Usually, functions are "wrapped" in a parent class, which provides general documentation for that group of functions. The full class definition for functions is as follows: +Functions will usually be the first thing a simpler extension will be used for. Functions should be "wrapped" in a parent class, +which provides general documentation for that group of functions. The full class definition for functions is as follows: - +%%SYNTAX|java| public class FunctionGroup { public static String docs() { return "These functions provide an example to new extension creators!"; @@ -46,7 +48,7 @@ public class FunctionGroup { } - +%% === Events === @@ -56,56 +58,68 @@ Events are a three part thing: # The actual extension point, which converts the bindable object into a MethodScript enabled event, with supporting methods. # The actual call to fire the event. -The bindable event layout is simply a class that extends BindableEvent, with one override, _GetObject(). If wrapping a Bukkit event, the Bukkit event should be returned here, given that the BindableEvent instance has been instantiated/provide with one. Otherwise, just return null. +The bindable event layout is simply a class that extends BindableEvent, with one override, _GetObject(). +If wrapping a Bukkit event, the Bukkit event should be returned here, given that the BindableEvent instance +has been instantiated/provide with one. Otherwise, just return null. The actual extension point is laid out as follows: - +%%SYNTAX|java| public class EventGroup { public static String docs() { return "These events provide an example to new extension creators!"; } @api - public static class some_event extends AbstractEvent { + public static class some_event extends AbstractGenericEvent { } } - +%% Notice that the event classes extend AbstractEvent. As one can see, the layout is very similar to that of functions. -Events can be triggered from anywhere, with a call to EventUtils.TriggerListener(Driver.EXTENSION, , );. The event name given must be the same as given in the event that has been defined, and the event instance an instance of that event. +Events can be triggered from anywhere, with a call to EventUtils.TriggerListener(Driver.EXTENSION, <event name>, <event instance>);. +The event name given must be the same as given in the event that has been defined, and the event instance an instance of that event. + +=== Other Extension Points === + +==== Command Line Tools ==== + +Implement the CommandLineTool interface (or extend the AbstractCommandLineTool class) and tag the class with the @tool +annotation. All of the built in tools use this mechanism, and extensions can provide new tools just the same way. + +==== Persistence Network Data Sources ==== + +Implement the DataSource interface (or extend the AbstractDataSource class) and tag the class with the @datasource +annotation. === Maven === -Our build and dependency manager of choice is maven. While a quick and dirty setup with a bare-minimal pom is possible, run-time speedups and compile-time checks will be missed out on if a few details aren't included. +Our build and dependency manager of choice is maven. While a quick and dirty setup with a bare-minimal pom is possible, +run-time speedups and compile-time checks will be missed out on if a few details aren't included. -Please include the following snippit under the section in the project's pom: +Please include the following snippet under the <build><plugins> section in the project's pom: - +%%SYNTAX|xml| org.bsc.maven maven-processor-plugin - 2.2.4 - + 5.0 process process-classes - process - src/main/generated - com.laytonsmith.core.extensions.ExtensionAnnotationProcessor @@ -116,8 +130,7 @@ Please include the following snippit under the sec org.codehaus.mojo exec-maven-plugin - 1.2.1 - + 3.2.0 cache-annotations @@ -127,23 +140,26 @@ Please include the following snippit under the sec - com.laytonsmith.PureUtilities.ClassLoading.Annotations.CacheAnnotations - ${basedir}/target/classes ${basedir}/target/classes - +%% === Obfuscation/ProGuard === -Some extension devs have expressed a desire to obfuscate their code. Unfortunately, there's a gotcha: the caching system we use runs before ProGuard obfuscates things, causing the original class names to be saved to the cache instead of the obfuscated ones. The only way currently known to get around this is to tell ProGuard to not obfuscate any of the extension points (lifecycle, function or event classes), via the -keep class option in the plugin's configuration section. +Some extension devs have expressed a desire to obfuscate their code. Unfortunately, there's a gotcha: the caching +system we use runs before ProGuard obfuscates things, causing the original class names to be saved to the cache +instead of the obfuscated ones. The only way currently known to get around this is to tell ProGuard to not obfuscate +any of the extension points (lifecycle, function or event classes), via the -keep class option in the +plugin's configuration section. -However, one should be able to use the annotated extension points as wrappers for the true code, which would reside in a different class. +However, one should be able to use the annotated extension points as wrappers for the true code, which would reside +in a different class. === Coding Standards === @@ -152,7 +168,7 @@ To prevent issues in the future, here are a few rules you should follow when cre Names of functions/events should be all lowercase and use underscores as spaces. The function name may NOT start with a single underscore, and functions that start with two underscores are highly discouraged, except for meta functions that deal with the compiler directly. Documentation should follow the same conventions, so that functions like -{{function|reflect_docs}} will work. The version that you return does not need to correspond to the MethodScript version, it may -be your extension's version number. If your function can be optimized, you are encouraged to do so. +{{function|reflect_docs}} will work. The version that you return does not need to correspond to the MethodScript version, +it may be your extension's version number. If your function can be optimized, you are encouraged to do so. -{{LearningTrail}} \ No newline at end of file +{{LearningTrail}} diff --git a/src/main/resources/docs/Extensions b/src/main/resources/docs/Extensions new file mode 100644 index 0000000000..7fe854206d --- /dev/null +++ b/src/main/resources/docs/Extensions @@ -0,0 +1,17 @@ + +Extensions are a way to extend MethodScript using native Java code. Extensions can add a number of types of +functionality, from new functions and events, to new commandline tools. + +While the extension framework is officially supported, no individual extensions are themselves officially supported. +Extension developers do not need to register with the project, and there is no central repository of extensions, so +it isn't possible to say what extensions are out there. A number of extensions can be found +[https://letsbuild.net/jenkins/ here], but note that this is not exhaustive, and is a community supported effort. + +{{TakeNote|text=Note that extensions completely bypass the security restrictions in place in MethodScript, and therefore +should be carefully installed, and only downloaded from trusted sources.}} + +In order to install extensions, simply drop the jar into the extensions folder, in your MethodScript installation, and +restart. The extensions will be automatically loaded from there. + +Extensions can provide access to any features that can be provided in Java, and so can therefore be quite powerful. If +you're interested in developing your own extension, see the page on [[Extension_Development|Extension Development]]. \ No newline at end of file diff --git a/src/main/resources/docs/FAQ b/src/main/resources/docs/FAQ new file mode 100644 index 0000000000..cdc15a7857 --- /dev/null +++ b/src/main/resources/docs/FAQ @@ -0,0 +1,37 @@ + +This page lists several frequently asked questions, with a quick, succinct answer, and links to further resources on the +topic, if applicable. + +==General== +<%QA|How do I create aliases?|Aliases are put in the aliases.msa file for global aliases, and added in game with the +/alias command for user aliases. To refresh your aliases after making changes to the aliases.msa file (or any CH script +file) use /reloadaliases from either in game, or the console.%> + +<%QA|How do I run CommandHelper commands from other plugins?|Prefix the alias with /runalias%> + +<%QA|What is the .cache folder?|The .cache folder contains files that speed up the startup. Deleting it will cause the +next startup to take slightly longer, but otherwise will have no effect, and can be beneficial to delete every once and +a while to clear out old, unused cache files%> + +==Persistance== +<%QA|Why is "persistance" misspelled? Isn't it "persistence"?|Yes, and I fixed it :p%> + + +==Common Errors== +<%QA|I got a compile error. What does that mean?|It means you have messed up your syntax somewhere. There's a huge range +of possible errors, so you'll have to read the error message carefully, and try to understand what it's telling you. If +an error message is unclear (or wrong) it may be a bug in CommandHelper, so jump on IRC and ask, or make a post on the +forum, but try to understand what the message says before you ask for help.%> + +==Dev builds, Bug reporting, Releases== +<%QA|Where can I report bugs?|https://github.com/EngineHub/CommandHelper/issues%> +<%QA|Where can I get the latest dev builds?|http://builds.enginehub.org/job/commandhelper/ Note that the latest build is +the only version that is supported however, so if you're using a random build, expect to have to upgrade if we can't +quickly figure out a problem you're having.%> +<%QA|When will the next release be available?|Releases happen when they are ready. Because this is a 100% volunteer +project, IRL always comes first, and so it's very hard to predict a time based ETA. Having said that, feature based ETAs +are possible; that is, "after feature X, Y, and Z are complete, and there are no bugs left, we will release". If you are +dying for new features, the development builds are generally quite stable. CommandHelper is a well tested piece of +software; there are over 500 unit tests, and it is a high priority to keep tests from failing, as well as continually +add more tests as new features are added. Having said that, there still may be bugs, and it still may break things, so +always keep backups, especially if you are running it on a production server.%> \ No newline at end of file diff --git a/src/main/resources/docs/Federation b/src/main/resources/docs/Federation index 5c798e8524..5a6526eef3 100644 --- a/src/main/resources/docs/Federation +++ b/src/main/resources/docs/Federation @@ -1,3 +1,5 @@ +{{unimplemented}} + The Federation system allows for easy, and secure execution of code on remote systems. Usage consists of two steps for the client, and one step for the server. @@ -7,16 +9,16 @@ Usage consists of two steps for the client, and one step for the server. All MethodScript processes support Federation, even cmdline programs, which allows for easy access to a MethodScript process from other sources. The Federation protocol is even straightforward enough that third party systems could be made to interface with -a Federated server. +a Federated server. For the purposes of this article, the following definitions are used: -* "MethodScript process" - refers to the process that is running a MethodScript +* "MethodScript process" - refers to the process that is running a MethodScript interpreter. This might be a server that is hosting the MethodScript process, a cmdline process, or some other system. * "Server" - refers to a Federated server, not the server that is running the MethodScript process. -* "Client" - refers to a Federated client. All MethodScript processes can +* "Client" - refers to a Federated client. All MethodScript processes can simultaneously be both a server and a client. * "Federated System" - any system that is either a server or a client, and knows the Federation Protocol. Third party tools, so long as they properly implement @@ -25,7 +27,7 @@ the Federation Protocol are considered a Federation System. All Federated Systems must use standard TCP Sockets, though they are allowed to fall back to other communication systems if the server and client can agree on the communication medium. For those interested in implementing the protocol, -or generally learning more about the specifics of the protocol, see the +or generally learning more about the specifics of the protocol, see the %%DOCLINK|FederationProtocol|technical description%% of the protocol. == Server Setup == diff --git a/src/main/resources/docs/FederationProtocol b/src/main/resources/docs/FederationProtocol index a2794b7147..67f3569019 100644 --- a/src/main/resources/docs/FederationProtocol +++ b/src/main/resources/docs/FederationProtocol @@ -1,3 +1,5 @@ +{{unimplemented}} + The Federation Protocol is the way that the client and server speak with each other. It is an object based streaming protocol. There are several steps required to set up a connection, but once the connection is established, making requests @@ -57,7 +59,7 @@ server. Each slave server should regularly check that the master port is bound to, and if not, it should spin up a master server in addition to the slave server it is already running. This can be done in the heartbeat thread for the slave server. Each -slave server is responsible for picking a random unused port (usually between +slave server is responsible for picking a random unused port (usually between %%CONST|com.laytonsmith.core.federation.Federation.DYNAMIC_PORT_MINIMUM%% and %%CONST|com.laytonsmith.core.federation.Federation.DYNAMIC_PORT_MAXIMUM%%), and then registering that information with the file system. In standard MethodScript @@ -71,7 +73,7 @@ checking for the master server, should update this timestamp on a regular basis. A client request to the master server simply requests the port that the slave server is on. This is required, since the request may come in from an outside host, that wouldn't have access to the file system. The master server looks up the requested -server in the registry, reports the port, and kills the socket. +server in the registry, reports the port, and kills the socket. If and only if the slave server only allows connections from localhost, may it not start up the master server. On the other hand, if a client is connecting @@ -93,7 +95,7 @@ it will disconnect. Assuming the HELLO and version check are successful, the client will write the string "GET PORT", then another string, the server name it is requesting the port of. The -server will respond with the string "OK", then the integer port number, if it found the server, +server will respond with the string "OK", then the integer port number, if it found the server, or the string "ERROR" and then another string, which is the error message, if it could not. Then the connection is closed. diff --git a/src/main/resources/docs/File_Options b/src/main/resources/docs/File_Options new file mode 100644 index 0000000000..992fee4c73 --- /dev/null +++ b/src/main/resources/docs/File_Options @@ -0,0 +1,102 @@ +File options are a way to granularly control options for a single file. Some file options affect the compiler directly, +others are simply used by the reflection mechanism to provide info during runtime, or for future readers of the code. +Unrecognized file options are ignored, but are otherwise not an error, however, you should prefix all custom file +options with "x-" to prevent future conflicts if new file options are introduced. + +The general format of a file option is: option: value; where the semicolon is optional for the last value. +However, some file options are booleans. For booleans, either true or on are considered +positive values, and false or off are considered negative values. Furthermore, +leaving the value off entirely, e.g. option; is equivalent to option: on; + +The file options must be defined at the top of the file, and can only be preceeded by comments, and start with +<! and end with +>. Newlines within the file options are allowed. If, within the value, you need a literal +> or semicolon, you must escape it with \, so \> or \;. + +== Inheritance == + +Default file options can be supplied on a per directory basis, by creating a file named .msfileoptions in +any directory. Scripts in that directory as well as subdirectories will then use the defaults set in this file. If there +are multiple .msfileoptions files in the parent directory chain, then they are read in from root to +subfolder, using the value set in the file closest to the script file. That is, if you have a directory with files +/scripts/myScript.ms, /scripts/.msfileoptions and /.msfileoptions, then options +in /scripts/.msfileoptions will take precedence over options in /.msfileoptions for script +/scripts/myScript.ms. +In any case, the file options in the file itself always take precedence. + +The format of this file is the same as for file options directly in source code, except you leave off the beginning +<! and end >. + +== Examples == +<%CODE|%> +Enables strict mode. + +<%CODE|%> +Enables strict mode and adds a description to the file + +<%CODE|%> +Disables strict mode for this file and adds a description. + +Below, the various file options and their effects are described: + +== strict == + +Enables strict mode for just this single file. See the page on [[Strict_Mode|strict mode]] for a discussion of what this +mode actually does. + +== suppressWarnings == + +Suppresses the given compiler warnings for this file. The list should be comma separated, but can contain multiple +types. + +<%SUPPRESS_WARNINGS_LIST%> + +== name == + +This should be the name of the file. If it exists, and the file name does not match this value, the a compiler warning +will be issued. The name must simply match the end of the actual file path, so providing a partial path, just the file +name, the path within the project, or the absolute path are all acceptable. It is recommended that you do not provide +the absolute path however, or the files cannot be easily moved. Both forward and backward slashes are accepted, and +spaces in file paths are allowed. + +== author == + +The author of this file. This value is not used by the system, but is available using {{function|reflect_pull}}. + +== created == + +The date this file was created. This value is not used by the compiler, but is perhaps useful information for future +readers of the script. This value is accessible via {{function|reflect_pull}}. + +== description == + +A description of this file. This value is not used by the compiler, but is perhaps useful information for future +readers of the script. This value is accessible via {{function|reflect_pull}}. + +== copyright == + +The copyright information for the file. This value is accessible via {{function|reflect_pull}}. + +== license == + +The license under which the code is released. This can be the full license text or just the name of the license, +or perhaps a link to the full license file. Generally, proprietary code does not need a license. +This value is accessible via {{function|reflect_pull}}. + +== requiredExtensions == + +A comma separated list of extensions that must be loaded in order to compile this file. While it is possible to be +more granular by using {{function|function_exists}} and {{function|event_exists}}, if the file cannot be useful without +a given extension, this directive is preferred. + +== compilerOptions == + +A comma separated list of compiler options. These options are considered to be unset if not present, and set if present. + +<%COMPILER_OPTIONS_LIST%> \ No newline at end of file diff --git a/src/main/resources/docs/Generics b/src/main/resources/docs/Generics new file mode 100644 index 0000000000..20e5f4c44d --- /dev/null +++ b/src/main/resources/docs/Generics @@ -0,0 +1,623 @@ +{{unimplemented}} + +Generics are a way to make a class be able to specify a type which is to be determined later, and generically use +that type no matter what specific type it is. An example of this is a collection, such as array, which can be typed +using generics so that only values of a specific type can be put into the array. + +== Using Defined Generics == +The first place you'll likely encounter generics is simply when using classes which were defined with a generic +parameter. A good first example is the array type. In general, let's consider a simple use without generics. + +<%CODE| +array @a = array(); // Using the array function +array @b = new array(); // Using the array type, and creating a new isntance of it +%> + +Under the hood, the array function simply constructs a new instance of an array and returns it, so these two examples +are equivalent. + +In both cases, the array is implicitely constructed with an ''auto type parameter''. We can keep the same +behavior but make it explicit by doing this: + +<%CODE| +array @a = array(); +array @b = new array(); +%> + +In this case, we have passed the auto type to the array function/constructor. This tells the array +that we only want to allow any value to be added to or read from the array. We can instead put a specific +type here, such as number, and we will change the behavior slightly. + +<%CODE| +array @a = new array(); +%> + +Now, we have told the compiler that we only want to allow numbers (and subtypes of number) to be put into the array. + +<%CODE| +@a[] = 1; // Valid, int extends number +@a[] = 3.14; // Valid, double extends number +number @n = _procThatReturnsNumber(); +@a[] = @n; // Valid, we don't know the exact type of @n at compile time, but we know it's of type number +@a[] = "hi"; // Invalid, string doesn't extend number +auto @u = _procThatReturnsAuto(); +@a[] = @u; // Valid, but might cause a runtime error, if the type returned isn't a number +@a[] = "12345"; // Invalid, because strings do not cross cast to number, which is the implied type. +@a[] = "12345" as int; // Valid, because string does crosscast to int, which is a subtype of number. We do have to be + // explicit here though. +%> + +Note that we will always still be returning a value of type number, so when reading from the array, it's slightly +different. + +<%CODE| +number @n0 = @a[0]; // Valid +double @n1 = @a[1]; // Invalid. Even though we put 3.14 in index 1, the compiler doesn't assume that, since it can change. +%> + +In this case, we always have to refer to returned values as number, because any subtype of number can be placed into +the array, and we won't know specifically what type they are, just that they are some subclass of number. + +Let's next look at how to define a class using generics. + +== Defining a Class == + +In order to use generics in your own classes, you need to provide the generic type parameters in the class definition. + +<%CODE| +class A {} +%> + +In this extremely simple class, we have defined a class named A, and stated that it has a type parameter which we are +calling T. (Note that in most examples, the letter T is used, but this doesn't have to be a single letter, +you can name it however you like.) This allows +us to use T within the class as if it were a real data type, and can use it for return values and parameter types. + +<%CODE| +class A { + T @v; + + public T get() { + return(@v); + } + + public set(T @value) { + this->@v = @value; + } +} +%> + +Later, when we construct an instance of A, we will provide the type as a generic parameter. + +<%CODE| +A @a = new A(); +%> + +When we construct the instance with int, then T is effectively replaced with int for that one instance, so +you can sort of think of it as if the class had been defined like: +<%CODE| +class A { + int @v; + + public int get() { + return(@v); + } + + public set(int @value) { + this->@v = @value; + } +} +%> + +Each instance may have a different type, however. This allows us to then provide different types to the object. + +<%CODE| +A @ai = new A(); +@ai->set(12345); +int @i = @ai->get(); + +A @as = new A(); +@as->set("Hello World!"); +string @s = @as->get(); + +@as->set(12345); // Compile error, 12345 is not a string +@ai->set("Hi"); // Compile error, "Hi" is not an int +%> + +== Constraints == + +So far, we've only discussed ''unconstrained'' generics, but we can define the class with a given constraint, and +then that constraint will be applied when considering the type that the object is being constructed with. There are +multiple types of constraints, and 3 different rulesets for where these constraints can be used. + +<%TAKENOTE|These terms are used throughout the rest of the article, and you should understand them well before reading +further%> + +The three different locations are: +# The definition site, that is, where class A is defined +# The left hand side of an assignment (LHS) +# The right hand side of an assignment (RHS). + +The terms LHS and RHS also apply to function calls, the parameters passed to a function are the RHS, and +the arguments in the function definition are the LHS. The return value of a function is also the RHS. + +<%CODE| +Type @a; // LHS +@a = @value; // RHS + +proc _p(Type @n) {} // LHS +_p(@value); // @value is RHS, also the return value from _p is RHS +%> + +Depending on what constraint is used, and where the constraint is used, this gives restrictions in different places in +the code. Some within the class where the generic is defined, and some at the use site of the class. Each is discussed +below. + +=== Exact Type === + +An exact type is what we've shown above. But it's worth pointing out that if the class is defined with a type, you +cannot provide an instance with a subtype for the generic parameter. Consider the following: + +<%CODE| +A @a = new A(); // Valid +A @b = new A(); // Valid +A @c = new A(); // Invalid! +%> + +While int is a subclass of number, generic parameters do not inheret in the same way. If we consider the example class +A, we can see why if we think about the difference between the 3 places that constraints can be. + +Also, recall that the LHS and the RHS do not have to happen in the same place in a regular assignment either, if a +variable is forward declared, then the RHS might come later, and vary depending on what happens. + +<%CODE| +A @a; // Forward declaration +if($input == "true") { + @a = new A(); // Compile error +} else { + @a = new A(); // Compile error +} +%> + +In the above example, you might think this should work, but it doesn't. But let's assume it did work, and consider +what happens lower in the code if we tried to use @a. + +<%CODE| +@a->set(1.0); // 1.0 (a double) is a number. However, what if we had hit the top branch? +%> + +Now, if $input is false, this should work fine, because A's T value is a double. But what happens if $input was true? +Then all of a sudden, this will stop working! Inside of A's set function, we would have tried to assign a double to an +int. + +Therefore, when simply providing an unbounded type parameter, you MUST use the same value on the LHS and RHS. + +This does not constrain you in what values you can pass when using the class though. This is perfectly valid: + +<%CODE| +A @a = new A(); +int @value = 1234; +@a->set(@value); // This works, despite @value being an int +int @return = @a->get(); // Compile error, we're trying to assign number to int +%> + +The only "catch" here is that T is of type number, so the return type is also a number, and even though we know we sent +it an int, it can in general return a double as well, so the call to get() must accept that possibility. + +This is also the only type of constraint which supports auto, and the only type where the object can be +used without generics despite the class being defined with a generic parameter. + +<%CODE| +A @a = new A(); // Valid, equivalent to the next line +A @a = new A(); +%> + +=== Upper/Lower Bound === + +==== LHS/RHS ==== + +However, we might not want this behavior, we might want to sometimes actually have a subclass in the generic parameter. +If so, we can use an boundary constraint, which is defined on the LHS. Let's consider an upper bound first. An upper +bound is defined with the syntax ? extends Type. The question mark (called a wildcard) is there because +we aren't actually defining a generic parameter here (like T), we're simply saying that the RHS for this must match this +constraint. + +<%CODE| +A @a; +if($input == "true") { + @a = new A(); // Now this works! +} else { + @a = new A(); // Now this works! +} +%> + +But won't we just run into the same problem as discussed above? No, but that's because the code now has additional +restrictions on it. When we do this, whenever T is used on the RHS, it is treated as if it is a number, but it isn't +allowed to be on the LHS at all. Why? Assume @a was defined as in the above code. + +<%CODE| +number @n = @a->get(); // Valid! @a definitely contains a number +@a->set(1.0); // Not valid! What if $input had been true? @a would be A, so this would cause an error. +%> + +This begs the question, how would we get a value into A in the first place then? There are a number of ways, but +mostly it involves temporary transfers or casts. + + +<%CODE| +A @a; +if($input == "true") { + A @temp = new A(); + @temp->set(1); + @a = @temp; +} else { + @a = new A(); + (@a as A)->set(1.0); +} +@a->get(); // Either 1 or 1.0 depending on the value of $input +%> + +In the first example, we're creating a temporary variable, where on the LHS, we declare it to only hold ints. Then, we +can use the methods in the class unrestricted. Then, we assign that to @a, where the restrictions exist. In the second +example, we're casting the value @a to A<double>, which in this case we can clearly see will be fine, but once we +leave the else block, won't necessarily be. The compiler can't figure this out though, so it needs the cast to be told +that this is safe in this one instance. + +If we don't care about the type at all, we can also use the constraint ?, which is usually just shorthand for +? extends mixed when the type on the definition is unbounded. + +<%CODE| +class A {} + +A @a; +switch($input) { + case "1": @a = new A(); + case "2": @a = new A(); + cast "3": @a = new A(); +} +%> + +This does change however, if the class definition has provided an upper bound. See below. + +==== Upper Bound in Definition ==== + +We may want to generally constrain the value of T to some class OR subclass from within the class itself. +We can do that with this code: + +<%CODE| +class A {} +%> + +Now, when we instantiate a new instance of A, we are required to provide a type which extends number, such as int or +double (or number itself), or another wildcard upper bound. + +<%CODE| +A @a = new A(); // Valid +A @i = new A(); // Valid +A @e = new A; // Valid, on the LHS is a subtype of + // in the definition +A @s = new A(); // Invalid! string does not extend number +%> + +Now, from within the class A, we can always treat the ''input parameter'' values of type T as if they are a number, +so any operations defined in number will be applicable and allowed. + +<%CODE| +class A { + public T triple(T @value) { + return(@value * 3); // All numbers can be multiplied, so if this is either an int or a double, this will still work + } +} +%> + +If the class definition contains an upper bound, then the behavior of the plain wildcard varies as well. In the example +above, ? would be equivalent to ? extends number. + +<%CODE| +A @a = new A(); // Valid +A @a = new A(); // Invalid +%> + +=== Lower Bound (super) === + +A lower bound is the opposite of an upper bound. This is generally less useful than an upper bound, but it is useful +for write-only values. Consider the following definition of @a. + +<%CODE| +A @a; +if($input == "true") { + @a = new A(); +} else { + @a = new A(); +} +%> + +In this case, @a is defined with the generic type ? super number. This means that the type on the RHS must +contain a type which is a superclass of number. number has two superclasses, primitive, and mixed, so these are the +only two options in this case. As in the above example, we use either primitive or mixed, based on the value of $input. + +In principal, a lower bound acts in reverse of an upper bound. This means that the values are "write only", that is, +whenever T is used on the LHS, it is treated as if it is a number, but it isn't allowed to be on the RHS at all +(actually, it is, but then it's treated as if it's of type mixed). Let's consider why this is. Let's write some values +into @a. + +<%CODE| +@a->set(1.0); // double is a number, this is fine +@a->set(42); // int is also a number, so this is also fine +%> + +In both cases, writing either a double or an int into a value of type number, primitive, or mixed, will all work just +fine. But we can't read the values from the object, (or at least we can't assume they're numbers) because in general, +code elsewhere could have written non-numbers into the object. + +<%CODE| +A @a; +A @temp = new A(); +@temp->set('string'); // Valid, because a string is a subtype of mixed +@a = @temp; // Valid, because is a supertype of +number @n = @a->get(); // Invalid! This would return a string. +mixed @m = @a->get(); // Valid, but only because everything is of type mixed +%> + +In general, a lower bound is useful in less cases than an upper bound, but can come in handy sometimes. + +Note that lower bounds cannot be used in generic definitions, the following doesn't make any sense. + +<%CODE| +class A { // Invalid! + T @v; // We would always just have to consider T to be of type mixed, because it might be constructed with new A() +} +%> + +If you think you want to do this, you can get the intended behavior by just using mixed instead of T from within the +class, and not having a generic parameter at all. + +=== Constructor Constraint === + +A constructor constraint asserts that the given type has a constructor with the specified signature. An example with +the no-argument constructor would be this: + +<%CODE| +class A {} +%> + +This constrains the caller such that they must now provide an instance of a class which contains a no argument, public +constructor. From within the class though, we are then able to construct a new instance of T. + +<%CODE| +class A { + public T getInstance() { + return(new T()); + } +} + +class MyClass { + public MyClass() {} +} + +A @a = new A(); +MyClass @newInstance = @a->getInstance(); + +// Note on the LHS, we could have used "new()" if we don't generally care about the specific type, though that means +// that we have to treat T as mixed when it's used on the RHS, unless there were additional constraints +A @dontcare = new A(); +mixed @m = @dontcare->getInstance(); +%> + +We can also provide parameters to new() to state that there must be a public constructor which accepts the given types. + +<%CODE| +// Assume X, Y, and Z are classes +class A { + public T getInstance() { + return(new T(new X(), new Y(), new Z())); + } +} + +class MyClass { + public MyClass(X @x, Y @y, Z @z) {} +} + +A @a = new A(); +MyClass @newInstance = @a->getInstance(); +%> + +This is useful in cases where you may need to construct a new instance of T from within the class, such as in a factory +class or similar. The constructor constraint cannot be used on the RHS generic parameter. + +=== Annotation Constraint === + +An annotation constraint states that the class passed in to the generic parameter must have a specific annotation +on it. + +<%CODE| +class A<@{NonNull} T> { + public void print(T @obj) { + // Since T has @{NonNull}, then we know we can never assign null to a variable of this type, + // thus this code is guaranteed to never throw a NullPointerException. + msg(@obj->toString()); + } +} +%> + +Any annotation which can be put on a class type may be used, but there is no way to specify the specific annotation +parameters themselves. + +== Multiple Constraints == + +So far we have only discussed single constraints, however, it is possible to specify that the generic type match +multiple constraints at the same time. These are separated by the & symbol. + +<%CODE| +class A { + public void test() { + T @t = new T(); // We can construct a new instance of this type... + length(@t); // We can check the length of it (from Sizeable)... + mixed @m = @t[0]; // And we can use bracket accessors on it (from ArrayAccess). + } +} + +class MyClass implements ArrayAccess, Sizeable { + public MyClass() {} + // etc +} + +A @a = new A(); +%> + +Note that the type variable name in the definition site must be the same within each constraint, in this case, T. + +The more constraints you have, the more typing you have to do. You may consider defining all the constraints +in a [[Typedefs|typedef]]. + +<%CODE| +typedef AConstraints = T extends ArrayAccess & T extends Sizeable & new T(); +class A {} + +A @a = ...; +%> + +Note that typedefs automatically translate the type variable (in this case T) into the appropriate wildcard when used +on the LHS. Typedefs of constraints cannot be used on the RHS, as a concrete class must always be provided there. + +== Multiple Parameters == + +Some classes might wish to provide type parameters. This is possible by comma separating them. + +<%CODE| +class A { + T @t; + U @u; +} + +A @a = new A(); +%> + +== Variadic Type Parameters == + +The number of type parameters on the class can also vary. + +<%CODE| +class A { + public void method(T... @values) { + /* */ + } +} +%> + +When used in this way, T can only be used as a type of a variadic parameter in methods, and cannot be used as the type +of a field or of the return type of a method. When construct an instance of this type, we must specify the types on the +LHS, as well as the RHS. + +<%CODE| +A @a = new A(); +// Now method() is defined as if it were method(string, int) +@a->method('string', 1); +@a->method(); // Compile error! Expected (string, int), but found () +%> + +Variadic type parameters can be used in combination with multiple parameters, but it must be last in the list. It may +have additional constraints, however. + +From within the class, we won't know the type of T at compile time, but the types are still reified anyways, and T will +actually be defined as array<ClassType>, and can be iterated at runtime to get the types that were +defined on that specific instance. + +One great example of this is how Callable is implemented. + +<%CODE| +class Callable { + public Return invoke(Parameters... @parameters) { + ... + } +} +%> + +This is what mechanism you're using when when you define a fully typed closure. + +<%CODE| +Callable @closure = string closure(int @i, int @j) { + return(string(@i + @j)); +} +%> + +You can also define variadic parameter types in the LHS. + +<%CODE| +Callable @c = string closure(string... @s) {}; +%> + +By default, if this isn't specified, it uses the type auto... + +<%CODE| +// These two lines are equivalent +Callable @c; +Callable @d; +%> + +== Type Parameters on Methods == + +So far in the article, we have only discussed type parameters on classes, but these can be scoped to a single method +as well. The syntax for this is such: + +<%CODE| +class A { + public T method(T @value) { + msg(typeof(T)); // Note that we're printing out the type of T when we call the method + return(@value); + } +} +%> + +This defines that a type parameter named T will be used just within this method, and then it can be used as either the +return type or the parameter types. In this example, since we're using T for both the return and parameter types, then +that means that if we call the method and send it an int, then it will also return an int. The binding of the specific +type of T can happen in a number of ways. Usually, it guesses the type of T correctly, and if you use the same type +everywhere, this will be correct. However, you may wish to specify a superclass, in which case you'll need to explicitly +specify this on the method use site. + +<%CODE| +A @a = new A(); +int @i = @a->method(1); // T is int. This can be inferred because all the types match +number @n = @a->method(2); // T is number. This is also inferred, by finding the least common supertype of number and int +@a->method(3); // T is int. The return type isn't used, so only 3 is used to get the type. But if we wanted T to be number, we can do... +@a->method(3); // Now T is number (int is a subclass of number, so this is still valid code) +%> + +== Inferred Type Parameters (Diamond Operator) == + +In all the above examples, we have been explicit about the type passed to the RHS, but this isn't necessary. We can +instead on the RHS used the ''inferred type'', if that is correct, and simply omit the type on the RHS. + +<%CODE| +class A {} + +A @a = new A<>(); // equivalent to new A() +A @a = new A(); // Different! Compiler warning is issued here, this is equivalent to new A() +%> + +Different types of constraints have different inference rules. + +<%CODE| +A @a = new A<>(); // Inferred type new A() +A @a = new A<>(); // Inferred type new A() +A @a = new A<>(); // Compile error, new() cannot be inferred + +class B {} +B @b = new B<>(); // Inferred type new B() +%> + +When there are multiple parameters, some of them may not be inferrable, while others are. In that case, you can +force inference of the ones that are inferrable by using ? on the RHS for those, and providing the specific parameters +where not inferrable. + +<%CODE| +class C {} + +C @c = new C(); // Inferred to new C() +%> + +At a basic level, generics are fairly easy to understand, but especially with some of the constraints and more +advanced techniques, it can be fairly difficult to understand, so don't be discouraged if you don't get everything in +this article at first. + diff --git a/src/main/resources/docs/Help b/src/main/resources/docs/Help new file mode 100644 index 0000000000..04def490d9 --- /dev/null +++ b/src/main/resources/docs/Help @@ -0,0 +1,9 @@ + + +Need more help? + +Chat with us on on [https://discord.gg/Z7jpHed Discord], the +[https://github.com/EngineHub/CommandHelper/issues issue tracker], IRC in #CommandHelper on irc.esper.net, +or just use the IRC widget below: + + diff --git a/src/main/resources/docs/IRC b/src/main/resources/docs/IRC new file mode 100644 index 0000000000..b4cceeb904 --- /dev/null +++ b/src/main/resources/docs/IRC @@ -0,0 +1,96 @@ +%%UNIMPLEMENTED%% + +IRC commands allow a server to send and process IRC messages. Since IRC is two-way, there are functions that can send +messages, and events that are fired when a message is received. Before either can happen however, a connection to the +server must be created. + +==Creating connection== +To create a connection, you must know two things, the server url, and the username. (The username is up to you to pick.) +Because joining a server is a blocking process, and can take a moment, creating a connection is multithreaded. However, +the script itself does not block, but instead can call a function once it's done, which will allow for further actions +if you must wait for a connection before you take action. Usually this can be ignored though, because the connect +function takes much effort to use reasonable defaults if no callback is given, and usually if a connection isn't up, the +messages you are sending can be discarded anyways. Also keep in mind that when the server restarts, the connection would +also restart as well. + +To connect, use the following code: + +<%CODE| +irc_connect('irc.esper.net', 'CommandBot', array('#CommandHelper')) +%> + +That's it! Since no callback was provided, and a channel list was given, the following defaults will happen: It will +attempt to connect to irc.esper.net. Once joined, it will pick the name CommandBot. If the connection to the server +could not be established, it will echo a message to the console, and try again in 10 seconds, in case the server is just +unreachable temporarily, however, this likely means you are connecting to an invalid server. If the name is already +taken, it will try CommandBot_, then CommandBot__, etc until it gets a name that isn't taken. It will occasionally try +to change names back to CommandBot once it becomes available. Once it joins, it will join the channel #CommandHelper +(and others if provided). Now that it has joined, you can send messages, and events will activate. + +So, what if we want to change the default behavior? You can provide a callback function, and it will be called when +either the connection fails or succeeds. (It will not try again automatically.) You may also optionally provide an array +of channels to join if the connection is successful, and those will automatically be joined. The callback function +accepts a few parameters: The server name, so that if needed, this callback can be linked to the appropriate request, +the name attempted, a boolean which indicates if the join was successful, an error code (null if @success was true, a +string otherwise) and an array of channels that were joined. Here is an example of a callback that might be used: + +<%CODE| +# Define our callback first +proc(_irc_callback, @server, @username, @success, @error, @channels, + if(@success, + # We joined successfully, so lets greet all the channels we joined + foreach(@channels, @channel, + # Sending a message is discussed below + irc_message(@username, @channel, 'Oh, hai!') + ) + , #else + # The connection wasn't successful, lets try again if it was just because the username was taken + if(equals(@error, 'USERNAME_TAKEN'), + irc_connect(@server, concat(@username, '_'), @channels, _irc_callback) + ) + ) +) + +# Fire off the connect now +irc_connect('irc.esper.net', 'CommandBot', array('#CommandHelper', '#MyServer'), _irc_callback) +%> + +This method more or less duplicates the behavior of the default behavior, which the exception of trying to change names +continually if the initial name is taken. Now that we have connected, we are free to send messages without causing +exceptions. In our success handler, we also could have done things like authenticate with the server. + +==Sending messages== +There are a few functions for sending a message. All of them have a switch that can be set that will make them throw an +exception, instead of simply ignoring the message if the connection is invalid (which can be caused by the connection +not being up yet, even though it will eventually connect). + +irc_message(@username, @channel, @msg, [@throw]) accepts the following parameters, @username, which is the name of the +bot that is connected, which represents a valid connection, @channel, which is the channel we are sending to, @msg, +which is the message to send. Optionally, you can also send a boolean, @throw, which defaults to false. If false, if you +try to send to an invalid connection, nothing happens, the error is just ignored, but if true, the function will throw +an exception, which can be caught in case you need to take alternate action if the message is not successfully sent. + +irc_message_bypass works the exact same way, however it bypasses the messages queue and immediately sends a message to +the channel. irc_message queues messages, so that if many messages are immediately queued, it will delay sending them, +to avoid setting off spam filters. + +irc_command(@username, @channel, @msg, [@throw]) works very similarly to irc_message, except it sends a command (which +should start with a '/'). There is a difference between a command and a normal message, so this distinction allows +greater flexibility in what you send to the channel. + +==Connection Identifiers== +A quick note on connection identifiers: It is possible to have multiple connections up to different servers. Since +usernames are unique across individual servers, the username is typically enough to differentiate between connections. +However, if you have multiple connections to different servers with the same username, you must use the full identifier +for the connection, which is the format username@url[:repeater] (example: CommandBot@irc.esper.net:_). It is acceptable +to always use the full identifier, even if the username alone would be enough to identify the connection. The url is the +url of the server, and the username is the username that was selected. The optional repeater part is for in case there +is no connection matching username@url, it will check for a connection that otherwise matches, but has any number of +repeaters on the end. By default, it is empty, and won't match any connection if an alternate was selected +automatically. (Keep in mind that the default repeater is '_'). + +==Receiving messages== + +==Possible errors== + + diff --git a/src/main/resources/docs/Interpreter_Mode b/src/main/resources/docs/Interpreter_Mode new file mode 100644 index 0000000000..16a325c04e --- /dev/null +++ b/src/main/resources/docs/Interpreter_Mode @@ -0,0 +1,50 @@ +Interpreter mode is a mode that allows you to easily run mscripts from within game, straight from the chat bar! To enter +interpreter mode, type /interpreter. You must have the interpreter enabled in your preferences file, and the user must +have the commandhelper.interpreter permission. Once you are in interpreter mode, your chat bar turns into a script +window, and you cannot chat anymore (well, you could with the chat function... :D). In order to leave interpreter mode, +type a single dash(-) on a line on its own. Now, when you "chat", you can run arbitrary mscripts. To enter multiline +mode, type >>> on a line of its own. Now when you hit enter, the line is added to an ongoing script, which isn't run +until you end the script with <<<. You can cancel the script by exiting interpreter mode with a single dash. + +So, what happens when you actually run a script? Well, the script is run exactly how you typed it in. If there are +errors with the script, you'll be notified with an exception, but otherwise scripts will run normally. So, calling: +<%CODE| +msg('hi!') +%> +will cause the word 'hi!' to appear on your screen. One thing to note about the output of a script: just like typical +scripts, if a command ultimately outputs a command, that command is run. The only difference is that other output will +also be displayed on screen, unlike in regular scripts, where it is just ignored. So, for instance, say you have this +script in your config file: +<%ALIAS| +/test = pinfo() +%> +Running the command /test will actually not do anything in this case. That's because pinfo() returns an array, but +otherwise doesn't have any visible effects. When running pinfo() in interpreter mode though, the array will be printed +out to your screen. This will allow you to play around with scripts much more easily, without having to worry about +echoing everything out. One thing to note about the returned output is that all output returned in this fashion will +display on screen starting with a colon. This is to help you differentiate what the output is compared to what the +script is causing. For example: +<%CODE| +msg('') +%> +would cause the following output: +
+
+:
+
+(note the completely blank line above the colon). This is because msg('') outputs an empty string to +the screen, and it itself returns void, which is displayed as an empty string. All output that would normally be ignored +by CH is shown in green, and output that CH would act on (running a command) is shown in yellow. All errors are shown in +red. Also note that the die function does not return anything, which is different than returning void. Due to the +underlying mechanism, if a script runs {{function|die}}, no output will be returned at all, which is in fact the same +behavior in normal scripts. + +Running: +<%CODE| +msg('Hello World!') This is normally ignored +%> + +Outputs:
+Hello World!
+:This is normally ignored + diff --git a/src/main/resources/docs/LLVM_Development b/src/main/resources/docs/LLVM_Development new file mode 100644 index 0000000000..9e26f692c0 --- /dev/null +++ b/src/main/resources/docs/LLVM_Development @@ -0,0 +1,332 @@ +Contributing to the native compiler backend is a great way to help MethodScript achieve its goals as +a general purpose programming language. Natively compiled programs help code break out of the confines +of the JVM, in principal are faster, and can integrate with code in other languages. + +Working on the native compiler backend is unfortunately more complex than contributing to the interpreter +however, and so it is recommended that you have a complete understanding of how interpreted MethodScript +works before embarking on the native framework, even if you're already familiar with LLVM or other assembly +programming. This page is intended to help jumpstart someone who is otherwise familiar with the general +concepts of contributing to MethodScript, and has some pointers to resources that can help with learning +the basics of LLVM. + +Before covering technical details, it's important to understand some underlying goals of the project. + +* The Java Interpreter is the reference implementation of MethodScript. +* While it's possible to diverge from \ +the Java Interpreter when absolutely necessary, "the implementation is hard" is not a good enough reason \ +to do so. Differences in implementation will be strongly challenged before they are allowed. If the implementation \ +must vary for technical reasons, it should be made exceedingly clear in the documentation, and mechanisms must be \ +added to allow user code to account for these differences at compile time. +* If a new feature is added to the native \ +backend, it must first be added to the Java Intepreter, unless this is also technically impossible. +* Native code may have different implementations per platform, but the effect of each should be the same. +* The Java Interpreter does the compilation process first, so features that exist in the compiler will \ +inherently take precedence. +* Functions and other features are allowed to be missing from the native compiler while parity is reached, \ +but this should result in compiler errors. Once parity is reached, it will be required to implement on all \ +platforms at once, but this is a future concern. +* Core functions (marked by the @core annotation in Java) are particularly sensitive to these requirements. +* Foundational concepts cannot be changed under any circumstances. For instance, variables that are untyped \ +(implied or explicit auto) must continue to be supported. +* The C Runtime Library is the primary runtime library, though others can be added as needed. +* Windows, Ubuntu, and MacOS are the supported OSes. More may be added later if sponsorship is obtained. In general, \ +Debian is expected to work, but it is not a supported OS, and no bugs filed against Debian will currently be accepted. + +Native MethodScript uses LLVM (which stands for Low Level Virtual Machine) to create the native binaries. +Thus, it's important to eventually understand various concepts within LLVM in order to successfully understand +the native compiler. In theory, LLVM should be able to be swapped out for any given assembly language, by simply +creating a new platform resolver, however, it's unlikely that for native binaries LLVM would ever be swapped out. +Nonetheless, the fact that LLVM is used should never be exposed to the end user, as this is not a guarantee from +a user code or tooling perspective, hence why, for instance, the command to compile to native is "asm", not "llvm". + +== Architecture == + +In general, the user code goes through a series of passes, moving closer to outputting native code. +Each of these passes contains a lot of steps, which are detailed below. + +=== Java Compilation === +First, the existing Java frameworks compile the user code into an AST. This is the same compilation that happens when +using MethodScript in Java. The standard optimizations are run on this +code, thus the optimizations made by the Java Interpreter (JI) take precedence. In the future, mechanisms will be added +to allow the LLVM platform functions to preempt this optimization, but in general this should be avoided, to prevent +divergence of functionality. Most of the JI optimizations are fairly generic anyways, and deal more with eliminating +dead code, and running various logic that is platform agnostic anyways. Once the AST is compiled, it is traversed, +at which point the LLVM functions are run. + +=== LLVM conversion === +While the JI is focused on providing the runtime actions for the code, the LLVM code is only run at compile time, and +instead generates the LLVM IR (Intermediate Representation), which can be thought of as a slightly higher level +assembly language. Because of this, some concepts, which are easier to grasp in a runtime environment (types and +values are known for sure at runtime, and may not be known at compile time), are more difficult to comprehend. +Additionally, programming happens in a linear/flattened way. Jump statements are used instead of traditional high +level branches, and code to evaluate parameters of a function is written before the evaluation of the function call +itself. This paradigm shift takes some getting used to for high level developers, but it can help to view the output +of already written programs, as well as output from simple C programs compiled to LLVM with clang. + +Learning LLVM or assembly concepts is beyond the scope of this article, as are things like using the C runtime library, +though resources for doing so are provided at the bottom. + +The functions are responsible for outputting the necessary IR. Input parameters are passed in as meta structures, TODO + +Once the IR is generated, it is written out to .ll files. This file contains the human readable LLVM IR, including +comments indicating where in the original MethodScript the code came from. + +<%NOTE|In general, it is possible to output either IR or bitcode, which is a 1:1 mapping of IR. When using the LLVM library +bindings, it is possible to output the bitcode directly, however a conscious decision was made to output IR instead of +bitcode. When using clang, you may see references to .bc files, which contain the bitcode. This can be converted to +IR using the llvm-dis tool, which is included in the toolchain if installed through MethodScript. For a discussion on +why IR was chosen instead of bitcode, see the section below.%> + +=== IR to O(bj) === + +The next step is to compile the IR into object files. Object files are basically the native assembly, which has not +been linked into a final binary yet. Starting from this stage, the process is handled entirely with the LLVM toolset. +"llc" is the program responsible for compiling the IR into the object files. + +=== O(bj) to Executable === +TODO: Linking is more complex than this, go into more detail. "lld" converts the object file(s) into the executable. + +== Toolchain == +"llc" and "lld" are the core tools that are used when compiling code, however, additional tools are installed by default +when using the MethodScript toolchain installer. llvm-as, llvm-dis, and opt are included. These tools are useful for +developers, and may be used as part of the core process if certain flags (such as debug flags) are set by the user, in +the future. + +== Testing and Debugging == +When adding additional functions, it's important to make sure that everything works as intended. Unfortunately, since +some functions have very platform specific code, it makes it more difficult to test. Luckily, it's easy to get Virtual +Machines for both Ubuntu and Windows. (Newer Mac OS is difficult, and thus support for Macs is currently limited.) +See the section below for information and steps on setting up these VMs. + +The IR is written out to .ll files, and this can be read to see the code in full context. + +To reverse engineer executable binaries, it might be helpful to use [https://ghidra-sre.org/ Ghidra], which is the premier open +source reverse engineering toolchain, though reading the .ll files is probably easier, and should be attempted first. + +One technique that is very useful is to reverse engineer equivalent C programs that have been compiled with clang. To +do this, write an equivalent program in C, then compile it with clang -Weverything -S -emit-llvm code.c. +This will emit the .ll file instead of compiling a binary. Clang is included with the LLVM installation. +This is also useful for helping to map common high level concepts into a lower level format, so that you can +interactively compare the two. Note that -Weverything is specified, this is generally important to have, so that all +warnings are emitted. You may choose to ignore certain errors if they are not relevant (many aren't, and some simply +aren't useful), but you should make sure to resolve all relevant and useful warnings before copying code, to make it +less likely that you've written code that isn't a good example. + +Another technique is hand coding IR, then compiling that. This can result in faster development cycles as you figure +out various concepts. To do this, you can hand write an IR file (you can use an empty MethodScript program to set up +the boilerplate), then simply run mscript -- asm handwritten.ll. Because this is a common use case, the +compiler can instead of taking MethodScript, directly accept a LLVM IR file. If this is done, the "output directory" +becomes the same as the .ll file, and the object file and executable will be placed beside it. + +== IR vs Bitcode == + +To compile LLVM, one can choose to either write human readable LLVM IR, or LLVM bitcode, which is the binary format of +the IR. Certainly, the LLVM compiler has to do more work to compile the IR compared to the bitcode, so generating the +bitcode directly should in theory make compilation faster. However, a number of pros come from using the IR instead. +First of all, the libraries that exist for generating bitcode are written in C/C++. Wrappers for Java do exist, but +then this has to be installed on top of the normal toolchain. Development of the IR should be easier for new +contributors. It also gives us more flexibility in generating the IR, which makes it easier to debug. + +It may be that eventually, we find a good reason to switch to bitcode, but for now, the pros of using the IR seem to +outweigh the cons. + +== Development Environment == + +Due to the inherently complex nature of the task, setting up a dev environment is also somewhat complicated. To a +large degree, most features can be done on whatever computer you're currently using, but it's possible that you +may need additional OSes to test on. In all cases, OSes are available for test purposes as virtual machines, using +any virtualization software you want, but in general, VirtualBox is the preferred solution. + +In all cases, setting up the VMs is free, though for non-free OSes, there are limitations that make them unusable +for daily use, however, for our purposes, they are good enough. For Windows installations in particular, the VMs can +be rather annoying, so it is recommended to use an actual copy of Windows, rather than the development VMs. + +If you aren't willing to install other development environments when adding new features, don't assume that new features +that you're adding will work on other OSes. If you have reason to believe they will (for instance, only uses LLVM +specific constructs and makes no system calls) then it's fine to make this assumption, but it's better to +add a compile error when used in an untested system than it is to assume your feature will work. PRs that do OS specific +tasks will not be accepted without proof of testing on all OSes that are supported in the code. + +=== Installation === + +First, download the latest version of VirtualBox. https://www.virtualbox.org/ We will then download and configure +each guest OS one by one. On Windows hosts, for installing the MacOS guest, you additionally need to download Cygwin, +from https://cygwin.com/install.html, see the MacOS instructions for more details and installation options. + +Once the guest OS is installed, it is useful to set up a shared folder, from where you install MethodScript, allowing +you to continue development on your standard computer, yet also not have to constantly copy files over. For Windows and +Linux guests, this is straightforward. Add the target/ folder of the build output as a shared folder in VirtualBox. +Open the Settings for the VM, go to Shared Folders, select the target/ folder in the host, and set it to auto mount, +then start the VM. From inside the VM, this should show up as a shared network folder. This requires the VirtualBox +guest additions to be installed in the guest. + +For MacOS Big Sur, Guest Additions doesn't currently work. Once it works, the above instructions should be relevant, but +in the meantime, a decent enough workaround is discussed below in the MacOS instructions. + +==== Windows Guest ==== +Download a Windows 10 or higher VM from here: https://developer.microsoft.com/en-us/microsoft-edge/tools/vms/ +Select VirtualBox for the VM platform. Unzip the file, and place the .vdi file in a reasonable location. Open +VirtualBox, and select File->Import Appliance. Select the .vdi file, and follow the prompts. + +IMPORTANT! *BEFORE* booting the VM for the first time, it's important to take a snapshot that can be reset easily once +the Windows trial expires. Select the additional options button in the right side of the VM entry, and select Snapshots. +Click "Take" in the top, and name the snapshot something reasonable, like "Pristine". You can then boot the VM like +normal, and once the trial expires, or for whatever reason if you want to return to a brand new OS state, restore the +snapshot. + +==== Ubuntu Guest ==== + +Download Ubuntu 20.04 LTS from here https://ubuntu.com/download/desktop. This is the ISO file, which is like a disk +image, and will need to be manually installed and imported. Go to Machine -> New, and follow the prompts. Select the +disk drive, and insert the ISO file into the drive, and follow the normal Ubuntu installation procedures. Like the +Windows installation, you may wish to create a pristine snapshot, but Ubuntu is free, so will not require resets due to +expired trials. + +==== Mac OS Guest ==== + +These instructions use https://github.com/myspaghetti/macos-virtualbox to download a MacOS guest. For Windows hosts, +this requires Cygwin from https://cygwin.com/install.html. When installing Cygwin, install the latest versions +of the following packages which are not included in the default installation, but are required for this script: +* wget +* unzip +* xxd + +Download the script, and run it: +<%SYNTAX|bash| +curl https://raw.githubusercontent.com/myspaghetti/macos-virtualbox/master/macos-guest-virtualbox.sh > macos-guest-virtualbox.sh +sh macos-guest-virtualbox.sh +%> + +This will run for a while, and requires a bit of interaction, but the instructions are obvious and easy to follow. + +Once Catalina is installed, you can use Software Update to install Big Sur. + +You will also need to allow unsigned code to run. From a terminal, run `sudo spctl --master-disable` to allow any +software to run. + +Since Guest Additions don't currently work, in order to use shared file systems with the host, this requires a different +approach. Follow the directions below on how to set up the host only adapter in the "Debugging the compiler on a VM" +section first. Once the host only adapter is configured, use your host OS's standard mechanism for creating a shared +folder. From the Mac guest, open Finder, Go, Connect to Server, and configure the shared folder through the IP address. + +Once successfully mounting the shared folder, it should show up in /Volumes/target or whatever you named the shared +folder, and can be used as normal from there. On reboot, you may need to reconnect to the share. + +=== Debugging the compiler on a VM === + +In general, the mscript wrapper supports debugging the JVM out of the box. Simply set the environment variable +"DEBUG_MSCRIPT" to 1, (bash: export DEBUG_MSCRIPT=1, cmd.exe: set DEBUG_MSCRIPT=1, powershell: +$env:DEBUG_MSCRIPT = 1) and run the mscript command. This will start the java process with the debugger +enabled, listening on all addresses, on port 9001. However, to access the network on the guest, you need to first +configure a new network adapter on both the host and the guest. + +Shut down the guest, and open the VM's settings in VirtualBox, and go to the Network tab. Go to adapter 2, +and enable it, and change the attached to: to Host-only Adapter. This also requires properly configuring the host +system. Go to File -> Host Network Manager. Make sure that the Adapter is configured automatically, and the DHCP Server +is enabled. (For brand new installations of VirtualBox, this is the default.) In your host, open the network connections +and find the network adapter labeled something like "VirtualBox Host-Only Ethernet Adapter". Run ipconfig/ifconfig from +the host, and note the IP address of this adapter. This is the IP address that you can access the host from the guest. +Vice versa, run ipconfig/ifconfig from the guest, and note the IP address of the second ethernet adapter. This is the +IP you can connect to the guest from the host. In your host system's IDE, start a remote debugging session, and enter +the IP from the guest, and port 9001 here to connect. + +== Key Concepts == + +It's important to understand a few key concepts about native code, which may not be immediately obvious to a Java +developer. + +Intermediate files have different typical extensions on different platforms: +<%PRE| +UNIX Mac OS X Windows +*.o *.obj *.obj object file +*.a *.a *.lib static library +*.so *.dylib *.dll shared object (ELF targets) +* * *.exe binary (no suffix on UNIX) +%> + +Largely, each type has similar functionality though. TODO: Discuss static vs dynamic linking, and what object files +contain. + +== Research Scenarios == + +While developing, you may need to research various scenarios. + +=== Find symbols defined in a binary === + +For Windows, you can use dumpbin.exe to find details about an object file, such as a dll or lib file. This must +be run from a visual studio developer prompt. + +* dumpbin.exe /headers "C:\Program Files (x86)\Windows Kits\10\Lib\10.0.19041.0\ucrt\x64\ucrt.lib" + +For linux/mac, you can use nm or objdump + +* nm /usr/lib/x86_64-linux-gnu/libc.a +* objdump -x /usr/lib/x86_64-linux-gnu/libc.a + +=== Adjusting Linker Options === + +At the early stages of development, it's possible that the linker options are very wrong, and will need to be adjusted. +Certainly, as new native code is integrated with, we will need to add more files to the linker. In order to get a good +template for this, it is easiest to run clang against an equivalent C program, and view the linker options it uses. +Use clang -v test.c to see the verbose output for clang, including the linker options, and use that as +your template. + +Note that the linker option format varies fairly widely from OS to OS, so any change to these options will almost +certainly require testing on all supported OSes, unfortunately. + +== Common Errors == + +* error: instruction expected to be numbered %X + +This can happen when an local variable was expected, but wasn't provided, so it was automatically inserted by the +LLVM compiler. This usually means the previous instruction was wrong, rather than the instruction it's pointing to. +There can be different reasons for this, for instance if a "call i32 @function" return value is being ignored. Since +call returns a value here, even if it isn't used, it should still be assigned to a value, to make it explicit, and +to increment the counter. In fact, this probably needs to be used anyways, many C functions return -1 or something +to indicate failure, which actually should be converted into a MethodScript exception, and thrown, rather than being +silently ignored. + +Another one is implied blocks. This mostly occurs with function definitions, which is why main looks like: + +<%PRE| +define dso_local i32 @main(i32 %0, i8** %1) { + %3 = ... +} +%> + +Because there's an implied block named %2. + +<%PRE| +define dso_local i32 @main(i32 %0, i8** %1) { +%2: + %3 = ... +} +%> +For functions, this is accounted for automatically, but other block +creation can cause this to happen in unexpected places, for instance when using a block terminating statement, such +as ret, br, unreachable, etc. Not accounting for these implied variables is often a sign of a bug, so it's good that +this error is caught, though unfortunate that the error message isn't terribly helpful. + +* error: '%X' defined with type 'Y*' but expected 'Y' +* When %X was defined with alloca + +When doing %x = alloca i64, then %x is actually of type i64*. Say you have the following code: + +<%PRE| +%1 = alloca i64 ; %1 is i64* +store i64 0, i64* %1 +%> + +Now %1 is a pointer to memory, which contains the value 0. To get 0 back, we have to load the value. + +<%PRE| +%2 = load i64, i64* %1 +%> + +== Resources == + +* [https://llvm.org/docs/LangRef.html LLVM IR Documentation] - This is the primary source of "official" documentation \ +for the various IR commands. Note that this is not necessarily the authoritative source though, reverse engineering \ +clang output is. +* [https://borretti.me/article/compiling-llvm-ir-binary Compiling hand written LLVM, for test purposes.] +* [https://mapping-high-level-constructs-to-llvm-ir.readthedocs.io/en/latest/README.html Guide for mapping high level language concepts to LLVM.] \ No newline at end of file diff --git a/src/main/resources/docs/Local_Packages b/src/main/resources/docs/Local_Packages new file mode 100644 index 0000000000..fa4b856124 --- /dev/null +++ b/src/main/resources/docs/Local_Packages @@ -0,0 +1,86 @@ +Local Packages are a intermediate feature that allow you to break up your scripts and alias files into smaller, more +manageable chunks; as the name suggests, packages. Unlike {{function|include}} and auto_include.ms, this allows you to +break the alias files out into more files, as well as automatically including certain other ms files. Local Packages +have limitations; They are meant as an intermediate feature before snap-ins are completed. These limitations will become +clear if you are not careful in your design of packages. + +== Local Packages == +For both Simple Directory Packages, and MSLP zips, the following functionality applies. Local Packages are placed in the +plugins/CommandHelper/LocalPackages folder. Any number of nested folders may be placed inside, CommandHelper recursively +scans the folders for relevant files. Unrecognized files are simply skipped, so you can have extra things in the folder, +such as ReadMe files, or some such. For the sake of forward compatibility, only .txt files are guaranteed to never be +used, so you should avoid adding other file types, in the event they are used in the future. + +== Simple Directory Packages == + +This is the easiest and most straightforward use of Local Packages. Files that end in .ms or +.msa and are simple ascii files on the file system are read in and compiled. .ms files are +pure mscript; no aliases may be defined here, and these files should work the same as any other file you might +{{function|include}}. .msa files are the equivalent of the (by default) aliases.msa file. They must follow +the exact same format as the aliases.msa file. One special exception is auto_include.ms files in the +LocalPackages directory. These work exactly like the main auto_include.ms file, in that they are compiled, +cached, and included before every script run. This means this is the perfect place to define procs or other variables +that you need to be in scope for your scripts. All auto_includes are essentially run at the same time, and there is no +guarantee of run order. + +Folders may be indefinitely nested, and CH will recurse into them as deep as necessary. The one exception is if the +folder name ends in .disabled, in which case the entire folder is ignored. This gives you a handy way to +"remove" scripts from your system, temporarily. + +== MSLP Zips == + +A .mslp file may also be placed anywhere in the directory structure. A MSLP file is simply a zip file, with +the file extension changed. Within the MSLP file, the exact same rules apply as with the Simple Directory Package +structure. .ms and .msa files. + +This mechanism allows for easier distribution of a self-contained script, but has the disadvantage of not being easily +edited, however, a simple unzip operation will take care of that. + +There is a packager built in. You may create a mslp by simply running +java -jar CommandHelper.jar --mslp path/to/toplevel/folder. In addition to creating the mslp, it will +attempt to compile it first, failing to create the mslp if compilation errors are present. The name of the mslp is +decided by the name of the folder; i.e. myScripts/ will become myScripts.mslp. + +== Disadvantages == + +The order in which files load is undefined. This means that dependencies among seperate "projects" is not usually a good +idea. You may be able to game the system by naming files one thing or another, but in general that method will not be +reliable. However, you ''can'' reliably handle dependencies ''within'' a package by putting includes in your +auto_include.ms file, and using the .library mechanism. + +== Inter-Package Interaction == + +Usually programs have a "main" file, which is the thing that is guaranteed to run first. This is the starting point of a +program usually. Local Packages can emulate this behavior, but within different "projects", the load order is completely +undefined, and there is currently no mechanism for defining this. The one exception to this rule is that the main +auto_include.ms will always run first. However, using the .library mechanism, along with +careful file layouts will allow you to move pure ms files around, yet still guarantee their load order. A folder that +ends in .library works the exact same way as a .disabled folder, the name is just a convention +that hints at that folder's purpose. Files that are included inside of the .library folder are not directly +loaded by the Package mechanism, but can still be loaded by your code using an {{function|include}}. .msa +files are not really affected by this, since all aliases are global anyways, and even if one depends on another, they +will all get loaded before any are used. .ms files may get affected by this, since they are executed as +soon as they are run, and one file may define a proc that another file needs to use, and if the load order is incorrect, +it would fail. + +Typical package structure may look like this then: +
+plugins/CommandHelper/LocalPackages/
+--MyPackage/
+----main.msa #Defines the commands used by this package
+----main.ms #Initializes events, first running include('includes.library/file.ms')
+----includes.library/
+------file.ms #Defines several procs used in main.ms, and also possibly main.msa
+--AnotherPackage/
+----main.msa #More commands
+----main.ms #More events and an include
+----folder.library/
+------name.ms
+
+ +Since the Local Package mechanism is really just a way to organize your own personal scripts, if you try and distribute +packages, you may run into namespace conflicts. This is again a limitation of Local Packages that will be addressed with +Snap-Ins. + +{{LearningTrail}} + diff --git a/src/main/resources/docs/Localization b/src/main/resources/docs/Localization new file mode 100644 index 0000000000..1b575bdf10 --- /dev/null +++ b/src/main/resources/docs/Localization @@ -0,0 +1,171 @@ +<%NOTE|This is implemented, but hasn't been used yet, and so may still have missing features or bugs. Please get in +touch if you're willing to help with the localization effort%> + +The MethodScript website supports localization efforts, to better serve communities that are primarily non-English +speaking. While much of the application itself is necessarily written in English (function names, etc would be a +disaster to try to localize), the documentation for the application does not necessarily need to be in English. + +To this end, a Localization (or L10n) framework has been put in place to facilitate bilingual localizers to help +translate the documentation into languages other than English! Much care has been put into ensuring the accuracy of +the documentation, and the language of it is no exception. This document describes the l10n framework, +which is important to understand if you wish to contribute l10ns. + +== General Overview == + +There are a few steps in the l10n process, and some complication is introduced to the system to reduce the amount of +work that is needed to be done by l10n experts. The general process is this: + +First, when the website is generated, the deployment tooling automatically runs to create the translation database. +The first step is to take each and every page that is eligible for l10n, and split it into '''segments'''. The logic is +a bit complex, but the idea here is to split each page into many small, logical segments, rather than keeping it as one +giant page. What defines a segment varies, but for instance, in a page with lots of paragraphs (such as this one), each +paragraph becomes one segment. Tables are another good example, each cell in the table becomes a segment as well. Bullet +points in a list each become their own segment, and so forth, and so on. Many of these segments are repeated across the +site, and doing this segmentation in a very specific way can help identify segments of a page that are repeated across +more than one page. In this way, we can ensure that when this segment is translated, every page across the site can +inherit this translation, without the l10n expert having to translate the same segment multiple times. This also makes +it easier for new information to be added, without requiring full re-localization of the page. It's better to have new +information be available in English than not at all, and so untranslated segments are always shown as is, alongside any +already translated segments. + +There are also lots of segments of text that are perfectly translatable by machine. That is, there isn't any text in the +segment that needs to remain English for compatibility purposes, such as names of functions or enums, etc. In that case, +all locales can use machine translation on that segment, which can save lots of time for many segments. There are +however, many places where these machine translations might be wrong, or where the translation for the general text +may not be appropriate in the context of an individual page. Therefore, mechanisms are in place to allow for l10n +experts to override these where necessary. + +== Technical Discussion == + +Due to the complexity of the system, various tooling is in place to assist in keeping things correct, but it is +important to have an overview of the core of the system. The primary database is hosted on +[http://github.com github.] + +There are three layers to the database. The summary file, +the locale master file, and individual page files. Each translation file is an xml file. For each segment, there are +at least 2 pieces of information that all segments have, the translation id, and the original English. + +In the summary file, (summary.xml) we find information that relates to the segment across all locales, for instance, +whether or not this segment should be eligible for machine translation. If the value is null, then this segment hasn't +been judged yet, as this requires human review. There is also a comment field, which can be used to leave a note for +l10n experts about this particular field as a whole, without regard to the particular locale. There is also a flag +that marks this segment as '''untranslatable''', which means that the whole segment is something like a function name, +or some other string that would be incorrect to localize. This effectively means that the English is the "translation" +for this segment, and it is removed from the "missing translation" lists. + +At the root of the database, we find a folder for each supported locale. Within the folder, there exists a master +locale file (master.tmem.xml), along with many other files which are page specific files. For the purposes of the +l10n expert, the master file is the most important, though individual page files are sometimes useful to use. The +master locale file is the file that the bulk of translations should go in. This file is somewhat a duplicate of the +summary file, +in that all segments in the system should be in this file, but it contains locale specific information. The id and +English key are present, but there are also locale specific fields. The comment field allows a l10n expert to leave a +locale specific note about this particular segment. The translation field is where the non-English translation should +go. For segments that have been marked as eligible for machine translation, a separate "auto" field is added for that +translation. In all cases, if both a manual translation and a machine translated version are available, the manual +translation is used instead. + +Finally, we have individual pages of translations. These contain the same information as the master locale file, but +only contain the translations that are used on that particular page on the site. Normally, these values are inherited +from the master file, but if the overrideMaster flag is set to true, then this translation will not inherit the +translation from the master file, and will use the translation specified for that particular page. In general, if this +occurs, this probably points to a larger problem with the segment, and instead of doing that, we should consider how +the pages are segmented instead. + +New translation blocks are not allowed to be created by hand. New segments are created automatically during website +generation, and the blank templates are automatically committed to the central github repository. This prevents +very messy merge conflicts if multiple l10n experts are working on the same pages, as well as prevents any conflicts +that the automated systems can't recover from. + +On individual page loads, we don't want to load the entire translation memory, as this is quite large, which is why +each page gets it own translation memory file, with just the segments that are on that page. When deciding how to +translate each page, a page prefers to use the manual translation, followed by the machine translation, followed by the +original English. + +There is an artificial language tag, which is always available for testing, the "art" locale. Since machine translations +cost real money to generate, and it can be useful to do testing locally, the art locale is a programmatically +modified English language, which can help detect issues in the machine localization, as well as testing other locale +changes without breaking real languages. + +== Using the Localization Tool == + +Because keeping all of these files in sync manually would be extremely difficult, in general, the rule is that only +the tooling should directly modify the database. Editing the xml files by hand would likely cause issues, and so using +the GUI is the only supported, and easiest way to modify the files. Use the '''l10n-ui''' tool to launch the +Localization UI. + +The l10n database is just a git repo, so to contribute to localizations, you need a git client installed. On first run, +you can tell the tool to help you check out the official repository, or you can specify your own, if you are just +testing things. + +The localization tool allows you to operate in two main modes, General Mode, and Locale Specific Mode. In general mode, +you can go through and mark segments as machine translatable or not, and this will apply to all supported locales. You +can also add comments to the segments, which will be seen in all locales. You can also run machine translation for +segments that were marked as machine translation supported. (This requires a subscription to Azure Translator Text API, +which has a limited, free tier, but may cost money.) + +In locale specific mode, you can view translations for that particular locale. From this, you can view the entire master +translation file, or you can view the segments for individual pages. From within a selected page, you can add a manual +translation or a locale or page specific comment. + +Saving writes the database out to file, and you can also create a pull request to the main repository. + +== Special symbols and untranslatable strings within segments == + +To facilitate easier parsing of the text, some segments have been modified to remove information that should definitely +not be localized. For instance, urls, code, template markers, and other strings of that nature. +In place of those symbols, '''%s''' has +been added instead. THESE MUST BE LEFT IN PLACE. It should generally be obvious where it should go, often times these +are within brackets, or surrounding a single word, etc. There are often time other symbols, such as html escapes, +brackets, and other symbols that should be left intact in the translation, unless it's obviously supposed to be changed. +There are also other cases where it's impossible to automatically determine whether or not to exclude a particular word +from a translation, depending on the context. For instance, function names an other references to code should never be +localized, as function names may only be in English. You must use common sense when doing the localization, and ensure +that you don't change things that must be there for site functionality or may confuse the reader. In some cases, the +entire segment is correct without any translation, for instance segments that are just the name of a function. In this +case, do not copy the english over, instead, switch to general mode and mark it as untranslatable. This will cause it +to use the English in all locales, but also remove it from the list of "missing translations". Unfortunately, +segments that contain strings that shouldn't be translated are not going to be eligible for automatic translation, and +must be manually translated, ensuring that the untranslatable parts are left intact. + +Code blocks are currently wholly excluded from the l10n framework, though it should be possible in the future to +allow comments and strings within the code to be localized. + +== Testing locally == + +Before creating a Pull Request, you should make a deployment locally, and verify that your changes look alright. To +create a local deployment, follow the directions [[SiteDeployTool|here]], but you'll additionally need to start up +a server that serves your local copy of the database. These directions can be modified if you wish, but the easiest +method to do so is to do the following. + +Before running the site-deploy tool, in your site-deploy.ini file, set production-translations to +"http://localhost:7585". Run the site-deploy tool like normal, with the latest version of the codebase. You can +either download the latest release, or build it yourself, but to avoid missing segments, it must be based on the latest +release build. + +<%TAKENOTE|Adding new segments in pull requests is not allowed! Only the automated pipeline may add new segements, +so you must wait until the segment templates are in place before you can localize them. Likewise, removing +segments is not allowed either. Segments that are unused will occasionally be culled manually.%> + +Follow the remaining directions to get a local site +up and running, then: + +* Open a new terminal, in addition to the terminal running the server for the main site. +* Run <%PRE|npx http-server -c-1 -p 7585 --cors%> + +Now, when you save the localizations in the UI, you simply need to refresh the page to see the new translations. + +<%TAKENOTE|Please proofread all your translations, to ensure they are correct, and have appeared on the local site +correctly.%> + +== Pull Request Acceptance == + +Once the pull request is made, your changes will be validated, to ensure that they won't break anything. Once accepted, +the PR will filter down into the website, though due to a delay in cache updates, some translations may take a bit to +appear on the website. + +== Errors in original documentation == +If you find errors or typos in the original English, do not translate them, because the segment will be invalidated and +no longer translated. Submit a PR against the original English first, then allow the segment to update before +translating it. You can also mark it as a suspect segment, and it will be flagged for review, and prevent others +from translating it before it is replaced. \ No newline at end of file diff --git a/src/main/resources/docs/Logging b/src/main/resources/docs/Logging new file mode 100644 index 0000000000..b199a467e1 --- /dev/null +++ b/src/main/resources/docs/Logging @@ -0,0 +1,12 @@ +CommandHelper has a built in logger, which allows for subsystems to be individually monitored and logged. There are +various settings that affect what data is logged, and how it is shown. There are two steps to configuring the logger, +but reasonable defaults have been selected for you. To change the main settings, go to logs/debug/loggerPreferences.txt, +which is the configuration settings for the logger. You can adjust settings on the individual subsystems from here. + +In addition, if a message would be logged to file, depending on other settings, it will also be displayed on screen. The +following rules apply if the message is set to be logged based on your settings: + +If debug mode is on, all messages are logged to the screen too. If show-warnings is true, warnings and errors are shown +on screen. Otherwise, just error messages are shown on screen. Assuming your preferences are set correctly, the console +will color code the output as well. + diff --git a/src/main/resources/docs/Logic b/src/main/resources/docs/Logic index f904c28f2b..0878865ef2 100644 --- a/src/main/resources/docs/Logic +++ b/src/main/resources/docs/Logic @@ -5,7 +5,7 @@ you want to do one thing or another. This is where the logic operators come in h functions that all act relatively the same in the BasicLogic class. Essentially, they compare certain values, then return a boolean. -''Note: The examples in this tutorial primarily use operators, see the page on [[CommandHelper/Staged/Operators|Operators]]'' +''Note: The examples in this tutorial primarily use operators, see the page on [[Operators|Operators]]'' ''for a refresher, if the operators are unclear.'' ==Basic Usage== @@ -13,9 +13,9 @@ values, then return a boolean. Let's take the {{function|lt}} function into consideration (< operator). lt returns true if the value on the left is less than the value on the right. In a typical algebra class, when comparing two numbers, you would write something like this: -
+%%PRE|
 x < y
-
+%% Assuming x is 3 and y is 4, this statement is true. However, if we swap the values, and make x 4 and y 3, the statement is false. Written in code, it would look like this: @@ -23,7 +23,7 @@ the statement is false. Written in code, it would look like this: @x < @y %% -All the functions in the [[CommandHelper/API#BasicLogic|BasicLogic class]] return a +All the functions in the [[API#BasicLogic|BasicLogic class]] return a boolean based on certain conditions. You should be familiar with the lt (less than <), lte (less than or equals to <=), gt (greater than >), gte (greater than or equals >=) functions @@ -146,8 +146,13 @@ The following are considered to evaluate to true: * The keyword and boolean value true * Any non-empty string * Any number different from 0 +* A non-empty array -Anything else, including arrays, are considered false. +Null values are always considered false. All primitives, and some more complex objects implement the +ms.lang.Booleanish type, which allows them to return true if the value is '''trueish''', meaning that while the value +itself is not a boolean, it has a true/false interpretation, and can explicitly be cast to a boolean with the +{{function|boolean}} function. This is a generic concept though, and so it's not possible to create a comprehensive +list of things that are supported with this mechanism. ==else if== if/else if/else chains can be formed as well, if multiple conditions need to be checked. Using only @@ -203,14 +208,18 @@ if(@value == 'value1'){ } switch(@value){ - case 'value1': - code1(); - case 'value2': - code2(); - case 'value3': - code3(); - default: - code4(); + case 'value1': + code1(); + break(); + case 'value2': + code2(); + break(); + case 'value3': + code3(); + break(); + default: + code4(); + break(); } %% As you can see, it's a bit less code using the switch statement, but the results can be achieved either way. @@ -227,12 +236,15 @@ of the values specified, the corresponding code is run. switch(@val){ case 'test1': code1(); + break(); case 'test2': case 'test3': case 'test4': code234(); + break(); default: defaultCode(); + break(); } %% @@ -246,8 +258,10 @@ Slices may also be used, and if so, if the test value falls within the range of switch(@value){ case 0: case0(); + break(); case 1..5: case1_5(); // This one will run + break(); } %% @@ -268,6 +282,36 @@ switch(@value){ Had we left the break() out, case 2 and the default case would have merged together. In all other cases, break() is optional. +{{TakeNote|text=While break() is in fact optional, it is still recommended that you use it when applicable.}} + +Using break() is still recommended however, even though it is technically optional. Consider the following example. + +%%CODE| +switch(@v){ + case 1: + msg('1'); + case 2: + msg('2'); +} +%% + +In this example, if @v is 1, we msg out 1, and if @v is 2, we message out 2. Consider however, if we comment out line 3, msg('1'). We might expect that the code +simply won't message anything at all if @v is 1. This is incorrect. Due to the way fallthroughs work, it will simply change it to a combined case 1 and 2. We can +add a bit more code to ensure that this won't happen: + +%%CODE| +switch(@v){ + case 1: + msg('1'); + break(); + case 2: + msg('2'); + break(); +} +%% + +Now, if we comment out msg('1'), as expected, nothing happens when @v is 1. + switch also has a pure functional syntax, but it is not recommended for use in new code. {{LearningTrail}} diff --git a/src/main/resources/docs/MScript_Protocol b/src/main/resources/docs/MScript_Protocol index 0c8d800437..4eb7fc66ea 100644 --- a/src/main/resources/docs/MScript_Protocol +++ b/src/main/resources/docs/MScript_Protocol @@ -1,3 +1,5 @@ +{{unimplemented}} + The MScript protocol is a defined standard which works much like [http://en.wikipedia.org/wiki/Remote_procedure_call RPC], but is specific to MScript. The advantages it offers over other protocols is that the underlying transport mechanism is undefined, leaving it open to specific needs, depending on the means of connection to the remote system (i.e. TCP, HTTP, DBus, etc). All data is passed as UTF-8 strings, and is interpreted on the remote end. @@ -6,13 +8,13 @@ Mechanisms exist to support passing of data, (primitives and objects/arrays), fu == Overview == The end of each pipe must simply implement a two way protocol, which translates network/remote requests into UTF-8 strings, which are passed to/from the MScript interpreter. The interpreter will always provide at minimum a TCP socket connection, but other platforms/extensions may provide support -for others, such as DBus or named pipe communication. Connections may be stateless, though maintaining psuedo state is the responsibility +for others, such as DBus or named pipe communication. Connections may be stateless, though maintaining pseudo state is the responsibility of the transport extension, and the interpreter expects the connection to appear stateful. ===Authentication=== In the first stage of connection, the interpreter handshakes with the remote connection, and requests an initial capabilities list. These capabilities are considered non-authoritative client side, but are used to minimize -network connections that would fail. All capabilites may be claimed to be dynamic however, and the server is free +network connections that would fail. All capabilities may be claimed to be dynamic however, and the server is free to send meta information to adjust this list at any time. The client has the option in certain circumstances to ignore this list, so it is important to note that this is not a security mechanism. If a request does fail for capability reasons, that information is cached in these tables. This capability list doubles as a "permission" list. diff --git a/src/main/resources/docs/Manifests b/src/main/resources/docs/Manifests new file mode 100644 index 0000000000..c50b823f3d --- /dev/null +++ b/src/main/resources/docs/Manifests @@ -0,0 +1,17 @@ +{{unimplemented}} + +A manifest file describes information about the project as a whole, including things like the version number, +author name, etc. + +== Comments in the manifest == + +It is often times useful to add comments to a configuration item, but alas, manifests use json, not xml, and you +can't put comments! Not to worry though! Every element is defined with a "comment" field, which is totally +ignored by the manifest system, but can be added anywhere you wish in your configuration to add information +for the reader of the config file. + +== Manifest Objects == + +The root object is defined as such: + +// TODO just link to the ms.lang.manifest.ManifestRoot file. diff --git a/src/main/resources/docs/NativeMemoryManagement b/src/main/resources/docs/NativeMemoryManagement new file mode 100644 index 0000000000..f05d3c8e2f --- /dev/null +++ b/src/main/resources/docs/NativeMemoryManagement @@ -0,0 +1,180 @@ +{{unimplemented}} + +Memory Management is a huge field of study, of which entire university courses or PhD programs could be devoted. +Therefore, this article will necessarily skip most of the details about the subject. However, a general overview on +the subject is provided, as well as the information necessary to change the default settings. In perhaps 90% of all +cases, the default settings should be appropriate, and shouldn't be changed. + +Also note that this article only applies to MethodScript which is compiled to native binaries. Interpreted MethodScript +relies entirely on the memory management of the JVM, and has no specific memory management options. + +== Memory Management Overview == + +All useful programs rely on allocating memory in RAM. There are two conceptual types of allocations, those which happen +on the stack, and those which happen on the heap. For stack based allocations, these are easy enough for the system to +manage, because the lifetime of the memory only lasts as long as the function call is running. Once the function is +complete, any memory allocated can merely be popped off and discarded entirely (hence the name, "stack"). This is only +suitable in certain cases, however. Large objects, and those that need to continue existing after the function returns +cannot use this technique. The alternative then is to allocate this memory on the heap. +Memory allocated on the heap is more difficult to manage, however, in part because the lifetime of the memory is uncertain. +In a sense, you can think of this memory as "global variables", or at least memory that is generally addressable by +multiple functions. There are plenty of concerns for memory on the heap, from heap fragmentation, to memory paging +strategies, all of which have to be addressed by either the programmer or the programming language or the OS. +For these examples in particular, the programming language or OS can generally handle these in a way that is fairly +win-win, in other words, there are no downsides to the implementation, and it doesn't require specific thought from +the programmer. The other, larger problem revolves around when to free up some memory. When RAM is no longer in use, it +should be freed, so that the system no longer has to manage it. While all modern desktop OSes support virtual memory +by paging out blocks of memory onto the disk when the physical RAM is full, this technique introduces a serious +performance hit, and even still doesn't provide infinite memory. Therefore, it's important that when a program is +finished with a piece of memory, it is freed up somehow. + +Unfortunately, this is not a straightforward thing to determine in the general case. In some cases, it is easy, you +simply count up the number of things that have reference to some memory, and as soon as the reference count drops to 0, +you know that it's no longer necessary to keep that memory around, because no one could use it anymore even if they +wanted. However, circular references prevent this from being possible in general, because two objects might reference +each other, and even though the number of other references to these blocks of memory might be zero, since they reference +each other, they will have reference counts above 0, making the system unable to automatically free them. + +In general, there are two different approaches that programming languages can take. They can simply not address the +issue at all, and instead provide a mechanism for the programmer to manually free the memory when it's done, (such as +in C and C++). Doing it this way relies on the programmer to ensure that memory is not freed too early (when references +to the object still exist, causing what's known as a dangling pointer) or too late (a memory leak) or more than once +(double free bugs). The other +alternative is to not rely on the programmer at all, and instead using a ''Garbage Collector'' (GC). A GC works by +continually scanning the memory, and seeing when there are no longer references to an object, and clearing it at that +time. There are multiple strategies to doing this scan, each with their own pros and cons. Overall, these two approaches +also have their own pros and cons, and it's not clear to say in general that one is better than the other. Manual +memory management requires humans to make sure they don't write bugs (a hard task), but if done correctly, has less +overhead and is more predictable. Garbage Collection will automatically manage memory, making things like dangling +pointers and memory leaks impossible (assuming the GC algorithm is correctly implemented), but means that performance +might be impacted, and in any case makes the behavior of the system less predictable. + +For most programs, however, using a Garbage Collector is the best option. The performanace overhead is usually +acceptable, especially given the correctness guarantee that comes along with it, and precisely when an object is freed +from memory doesn't really matter, so long as it's eventually freed and the memory reclaimed after it's done being used. +Therefore, the default setting of MethodScript is to do just that. Include a GC in the compiled program, and allowing +the programmer to not have to worry about this at all. In rare circumstances however, this might not be acceptable, +therefore additional compilation options are available, and details can be modified where necessary. In almost all +cases though, unless you have a very specific use case, AND you have positively identified through benchmarking that the +default option is insufficient, the default options should be left alone. + +== GC Options == + +The default option may change as new features are implemented in the language, but for now the default setting is to +use reference counting plus a backup mark & sweep garbage collector. + +There are a number of options that can be used to control the garbage collector that is compiled in to your program. +These options are added to the compile command. + +=== --no-gc === +If you have a program that is guaranteed to be quick running, or will never allocate more than a fixed amount of memory, +it may be suitable to completely disable garbage collection. No GC will be compiled in, and memory will therefore be +consumed permanently while the process runs. Eventually, a deallocation mechanism will be provided, and this will allow +you to manage memory for longer running programs without running out of memory. + +When using library code that has not been written with no GC in mind, this may make it impossible to write a program +without memory leaks. Therefore, libraries specifically have to state that they are No-GC safe through a file option, +otherwise it is a compile error to use them with this mode. (Use file option no-gc: true to allow this +option. This must be included in the file options for all files that are intended on being compiled in.) You can +override this check by using --force-no-gc instead. + +=== --reference-counting === +For some programs, reference counting may be sufficient. Reference counting has very little overhead, and makes object +deallocation predicable. Reference counting alone cannot detect cycles however, and so in general cannot be used without +potentially introducing memory leaks. For carefully crafted code however, this may be sufficient. One can manually break +cycles by setting one of the references in the cycle to null. This will decrement the reference counter for that object +to zero, which will then cause it to be collected, which will then reduce the count to the first object by one, and once +your code loses its reference as well, it will drop to zero, causing it to be collected as well. + +When using library code that has not been written with this in mind, this may make it impossible to write a program +without memory leaks. Therefore, libraries specifically have to state that they are reference counting safe through a +file option, otherwise it is a compile error to use them with this mode. (Use file option ref-count: true +to allow this option. This must be included in the file options for all files that are intended on being compiled in.) +You can override this check by using --force-reference-counting instead. + +=== --mark-and-sweep === +Mark and sweep is a stop-the-world algorithm, which can reliably be used in all cases where this is acceptable. This +disables reference counting, which slightly decreases the memory used on a per object basis, at the cost of potentially +require more CPU time. + +=== --rc-mark-and-sweep === +Reference counting + mark and sweep to detect cycles. This is the default option, and is suitable for most programs. This +algorithm uses reference counting as a first line - any reference that goes to 0 is definitely ok to be collected +immediately, and is likely to work for most allocations, and the mark and sweep GC runs as a backup to detect cycles. + +This algorithm balances all the factors in the middle - each object will have a little more memory allocated in the +object header, but will require less overall work by the mark and sweep collector. + +=== Future Algorithms === +In the future, a generational collector will be implemented. It will also be possible to include multiple garbage +collectors in the binary, allowing the user to switch algorithms through parameters passed to the binary. + +== free() == + +In future versions of MethodScript, it will be possible to disable automatic memory management and instead use manual +techniques. Currently, there is no free function available to programs, and so for programs which need real +time or predicable collection, MethodScript is not yet suitable. The rest of this section outlines how it will work once +implemented. + +Allowing a free() function with no garbage collector creates situations that are harder to reason through, and in +particular can open code up to 3 separate issues. + +* Dangling pointers - These are created when two separate pieces of code have a reference to the same object in memory \ +which is then freed. In the meantime, the memory in that location may be re-used for another object. This can cause \ +errors or unexpected behavior in the best case, and security issues in the worst case. +* Double free - These errors are created when memory is freed twice, the second time potentially causing some other \ +object to be deleted or partially deleted. +* Memory leaks - These errors are created when the programmer forgets to free memory that should have been freed, but \ +then loses all references to the memory, making it impossible to free in the future. If this continues happening, the \ +system will eventually run out of memory, causing the program to crash. + +When using free() with a garbage collector, it's possible to avoid all these issues, and still use free() to simply +run the finalizers on an object, which can be useful in the case of objects with external references, such as file +handles or network connections. When using free() with a GC, the object itself is generally deallocated, but the header +for the object remains, with a deallocated flag set, so that any references to the object will not point to invalid +memory, and can instead provide defined behavior by causing a null pointer exception, rather than allowing reading from +unexpected memory locations. If the object is freed again, this will simply be a no-op. The +header of the object is then garbage collected when it otherwise naturally would, preventing memory leaks. + +Nonetheless, in all cases, using free() in one place in the code can cause code in another location to stop working, as +suddenly references to values may go to null or point to invalid memory, +making it extremely hard to reason through. Therefore, it's important for code to be aware of the use of free() +elsewhere in the code base, even if it does not use free() itself. For code which accepts the use of free(), it can be +marked with the allow-free: true file option. (Using the no-gc file option does not imply allow-free, since +the code might have only been intended to be used with short running or known finite memory programs.) +If any of the code in the project uses free(), and the project includes files +that don't declare allow-free, then this becomes a compile error to use free(), unless the --force-allow-free compiler +option is specified. + +Note that direct integrations with non-MethodScript code is unable to detect safety, and so all external code is treated +as if it uses free() and is therefore unsafe. + +== ofree() == + +To assist programmers in writing multipurpose code, an additional version of free() exists, ofree() (optional free). +This is a no-op if the code is compiled with a garbage collector, and simply free() if it is compiled with --no-gc. This +prevents triggering of the allow-free check when free() would be used for normal memory cleanup in the course of using +manual memory management techniques, while still allowing for free() when it's specifically meant to be used even in a +GC environment. + +It is recommended to use this version of free where appropriate to make code more reuseable, and to prevent requiring +the allow-free file option throughout the whole project when not actually necessary. + +== Multiple Heaps == + +In the future, multiple heaps will be supported, some of which can be used with a GC, and some of which are manually +managed. In this way, code which is particularly performance critical can be run in a real-time and deterministic way +with no additional overhead, and other code can be managed by the GC. Threads will be configured at startup to have +access to one heap or the other, with well defined ways of passing memory between the two thread types. This is not +implemented currently, and is not planned for the immediate future. + +== Library Authors == + +There is a higher burden placed on authors of library code, if they wish their code to be useable in all modes. +Care must be used to write code with all modes in mind, and set appropriate file options. In particular, code must +manually manage memory using ofree(), and not use free() unnecessarily. Additionally, code must avoid circular +references, and set the ref-count: true file option in order to make code work under the reference +counting strategy. + +In general, tests should be run under the --no-gc flag, and verify that code compiles under the --reference-counting +flag and against a file with the allow-free option set to false with no warnings. \ No newline at end of file diff --git a/src/main/resources/docs/NativeObjectArchitecture b/src/main/resources/docs/NativeObjectArchitecture new file mode 100644 index 0000000000..1439f96e6e --- /dev/null +++ b/src/main/resources/docs/NativeObjectArchitecture @@ -0,0 +1,101 @@ +<%UNIMPLEMENTED%> + +To increase portability and flexibility, much of the functionality of native objects are defined directly in +MethodScript, using most of the same compiler features available to user defined classes. Of course, some of the +functionality is required to be different, as native code must be run in some cases, but the goal is to reduce this as +much as possible. Doing so allows the compiler to be re-implemented in other languages more easily, and causes the code +itself to "eat its own dogfood". + +To facilitate this, there are a few architectural features, and a few language features. + +== native class == + +A native class, defined with the ''native class'' keyword, implies that this class is mirroring a class defined in the +java, with a @typeof annotation of the same name. This causes the system to merge the two classes' functionality. + +All classes in the java marked with the @typeof annotation will be available in the code, regardless of whether or not +there is a corresponding ''native class'', and can be used as normal in the java functions, but there will be no fields +or methods available in user code. A java class with the typeof annotation and no corresponding MethodScript class will +thus be synthesized as such: + +<%CODE| +public native class ClassName extends ParentClasses implements ParentInterfaces {} +%> + +The ParentClasses and ParentInterfaces are calculated based on the return value of the corresponding methods in the +java. Any documentation for the class itself will come from the MethodScript definition, if present, and the java class +otherwise. + +It is not strictly required that the java class implement mixed, but then the class would be unusable by most native +code, so it generally should. + +== native methods == + +The native keyword can also be applied to methods within a native class. Native methods outside of a native class are +not valid. These methods are defined in MethodScript per normal, but with no method body. The documentation and other +method information comes from the MethodScript, but the code itself is defined in the java. + +<%CODE| +public native class ClassName { + /** + * This is the method documentation + */ + public native int myMethod(int @a); + + public native static void myStaticMethod(); +} +%> + +Then within the java, you must use the @NativeElement annotation on the method with the same name and signature. + +<%SYNTAX|java| +@typeof("ClassName") +public class ClassName implements Mixed { + + @NativeElement + public CInt myMethod(CInt a) { + return ...; + } + + @NativeElement + public static CVoid myStaticMethod() { + return CVoid.VOID; + } +} +%> + +It is worth noting that native methods can only work with other native classes, due to the tight coupling between the +need for the return value and parameter types to be addressable in the java. No such requirement is placed on non-native +methods however, and they may address objects fully defined elsewhere in MethodScript. + +In addition, non-native methods may be defined within the MethodScript, along side native methods. + +<%CODE| +public native class ClassName { + public native void myMethod(); + + public void myMSMethod() { + return(); + } +} +%> + +Such methods are not callable by java code, so if this is something that native code requires access to, it should be +defined as a native method instead. + +Fields may be native as well. In these cases, it is as if they are defined with auto getters and setters that simply +set and return the value. + +<%CODE| +public native class ClassName { + public native int MAX; +} +%> + +== Precompilation == + +Classes defined directly in MethodScript are pre-compiled at build time. The classes are defined in a certain folder, +with files ending in .msc and at build time, these are scanned, compiled into ClassInfo objects, serialized, and stored +in the resulting jar. At startup, these files are quickly loaded, pre-compiled, and added to the native class list. + +This step is important to reduce startup time, and extensions should follow the same logic when adding native classes. \ No newline at end of file diff --git a/src/main/resources/docs/NewObjects b/src/main/resources/docs/NewObjects index 0f191cdc94..a5f93b7df1 100644 --- a/src/main/resources/docs/NewObjects +++ b/src/main/resources/docs/NewObjects @@ -1,14 +1,16 @@ -Objects in MethodScript allow for object oriented approaches to designing your -code. Object in MethodScript are extremely flexible and powerful, yet easy to use +{{unimplemented}} + +Objects in MethodScript allow for object oriented approaches to designing your +code. Object in MethodScript are extremely flexible and powerful, yet easy to use and understand. === Definition vs Instance === -An object is an instance of a class. A class is defined in code, and is used to -define methods and members that will be in an instance of that object. A class -can be instantiated, and get a real object out of it at runtime. You can think -of the difference between the two as a blueprint for a house, there is only one -blueprint, but multiple houses that are actually built, and in each house, some +An object is an instance of a class. A class is defined in code, and is used to +define methods and members that will be in an instance of that object. A class +can be instantiated, and get a real object out of it at runtime. You can think +of the difference between the two as a blueprint for a house, there is only one +blueprint, but multiple houses that are actually built, and in each house, some things are different, like, the paint color. === Example === @@ -17,25 +19,24 @@ Before getting into the details about objects, lets look at an example: %%CODE| class A { - members { - protected @b, - private static int @c = 0 - } - public A(@b = 0){ - this->@b = @b; - @c++ + protected @b; + private static int @C = 0; + + public A(int @b = 0){ + @this->b = @b; + @C++; } - - public static int method1(int @a){ - return(@a + 1) + + public static int Method1(int @a){ + return(@a + 1); } public method2(int @b){ - return(@b + this->@b) + return(@b + @this->b); } - public static final int instantiations(){ - return(@c) + public static final int Instantiations(){ + return(@C); } } %% @@ -44,158 +45,288 @@ This example will be used in the explanation below. === Members and Methods === -An object contains any number of '''members''' and '''methods'''. A member is a -variable, and a method is a function. Both members and variables can have various -'''modifiers''', which affect behavior in various ways. In the example, you can -see that we are defining @b as a member variable, and method1 and method2 -are methods. The modifiers in use include '''public''', '''static''', '''final''', -'''int''', '''protected''' and '''private''' though there are others too. Members -are defined in the '''member block''', members{ }. The member block -may appear anywhere in the class, but there may be only one block. The rest of -the class definition should be method definitions. +An object contains any number of '''members''' and '''methods''', collectively +called '''elements'''. A member is a variable, and a method is a function. Both +members and variables can have various +'''modifiers''', which affect behavior in various ways. In the example, you can +see that we are defining @b as a member variable, and Method1 and method2 +are methods. The modifiers in use include '''public''', '''static''', '''final''', +'''int''', '''protected''' and '''private''' though there are others too. + +Additionally in this example, there is one '''constructor''', which is the code that +is called when a new instance of the object is created (see below). The constructor (or constructors, +there can be several). A constructor is defined by creating a method with the same name +as the object it's contained in, with no return type. The constructor is where you +may place code that is necessary for initial configuration of the object. + +By convention, static elements are named with capital first letters, and +non-static elements are named with lowercase first letters, and all elements +use camel case. === Basic Type Manipulation === -Before moving on to using objects, it is important to understand the type system -in MethodScript. All variables are ''mixed'' by default, which is actually an -object type. All objects extend from mixed, and all user defined objects extend -from Object. "Primitives" are in fact objects, though they are somewhat special, -in that they are handled differently internally, but in code, they behave exactly -like user defined objects, meaning you can dereference them to access their -methods. If a type isn't explicitly specified when defining a variable, it is -considered to be mixed, and any value can be put in it. Other than this caveat -which makes MethodScript different from other strongly typed languages, it is -otherwise a full strongly typed language, meaning types are checked at compile -time, and invalid types will cause compiler errors. Type intersections and -unions are discussed later, since they are an advanced topic, but they are also +Before moving on to using objects, it is important to understand the type system +in MethodScript. All variables are ''mixed'' by default, which is actually an +object type. All objects extend from mixed, and all user defined objects extend +from Object. "Primitives" are in fact objects, though they are somewhat special, +in that they are handled differently internally, but in code, they behave exactly +like user defined objects, meaning you can dereference them to access their +methods. If a type isn't explicitly specified when defining a variable, it is +considered to be mixed, and any value can be put in it. Other than this caveat +which makes MethodScript different from other strongly typed languages, it is +otherwise a full strongly typed language, meaning types are checked at compile +time, and invalid types will cause compiler errors. Type intersections and +unions are discussed later, since they are an advanced topic, but they are also supported. === Instantiation === -In order to use an object, the first thing that must happen is that it is -instantiated. To instantiate our object that we defined above, we would use +In order to use an object, the first thing that must happen is that it is +instantiated. To instantiate our object that we defined above, we would use the following code: %%CODE| -@A = new A() +A @A = new A(); %% -Now, we can use the '''dereference operator''' to access members or methods in +Now, we can use the '''dereference operator''' to access members or methods in the object, for instance, %%CODE| -msg(@A->method2(2)) +msg(@A->method2(2)); %% -would output 2, since we called the method that returns our input plus @b, +would output 2, since we called the method that returns our input plus @b, which in this case is 0. -We can only have one constructor per object, but we can use default parameters, +We can only have one constructor per object, but we can use default parameters, and static methods can be created to fake multiple constructors, or we can use -type unions. -The constructor is simply a method that is defined with the same name of the +type unions. +The constructor is simply a method that is defined with the same name of the class, and no return type. +=== Overloaded Methods === + +Unlike procs, it is allowed to have multiple methods with the same name, so long +as they are distinguishable between each other based on the parameter lists. +This is allowed, for instance: + +<%CODE| +class A { + int method(){ return(1); } + int method(int @a) { return(@a); } +} +%> + +We can easily determine which method to call, based on whether or not we pass an int +to the method when we call it. + +<%CODE| + new A()->method(); // calls the first method + new A()->method(1); // calls the second method +%> + +Where this becomes a problem is when two parameter lists are indistinguishable from +each other. + +<%CODE| +class A { + void method(int @a, number @b) {} + void method(number @a, int @b) {} +} + +// This is a compile error, because which method should we be calling here, the first +// or the second? Both parameters match both methods. +new A()->method(1, 1); + +// TODO: Actually, should this be allowed? When it comes to inheritance, we will +// probably run into the same problem as with varied return types. + +// This is allowed, however, because we are explicitly typing the method, which allows +// us to exactly distinguish the method. This notation is in general allowed for any +// method, though is not necessary most of the time. +new A()->method(1, 1); + +// By the way, this is how you reference the Callable object, if you want to get +// a reference to the method dynamically, and there are multiple methods with the +// same name. +Callable @m = A->method; +%> + +A method may be overridden with simply changing the return type, but then, just as +in the above example, would require fully qualifying the method each time it were +called. Due to this, anytime you define a method that would require disambiguation +every time it's called, a compiler warning is issued for the definition. This can +be suppressed, however. + === Inheritance === -An object can '''inherit''' from, or ''extend'' another object. Let's define the +An object can '''inherit''' from, or ''extend'' another object. Let's define the object B, which extends A. %%CODE| class B extends A { public B(){ - super() + super(); } public method2(int @b){ - return(@b + 3) + return(@b + 3); } } %% -In this case, we are extending A, and '''overriding''' method2. In order to -override a method, you simply name it the same as the method in the parent -class. However, it is important to note that the method signatures must be -'''type compatible''', that is, it cannot completely redefine the types of the -variables that are passed in, but it can use ''more specificity''. For instance, -if class A defined the method public f(Object @o) it would be -invalid to define in class B the method public f(int @i), however, -if class A instead defined f as public f(mixed @o), it would be -valid for B to '''narrow''' the type required. - -Examining the constructor, there is the new function, super(), -which is used to call the parent's constructor. super is optional, -however, in the case where a call to super() is omitted, it is -implied to have been called at the top of the child's constructor. If -super() is called, it must be called ''before'' manipulating the -parent's members, even transiently, otherwise it is a compiler error. For +In this case, we are extending A, and '''overriding''' method2. In order to +override a method, you simply name it the same as the method in the parent +class. However, it is important to note that the method signatures must be +'''type compatible''', that is, it cannot completely redefine the types of the +variables that are passed in, and there are specific rules for '''narrowing''' and +'''broadening''' types. Take the following examples: + +<%CODE| +class A { + number f(number @a) { /*...*/ } +} + +class B extends A { + // We are allowed to "narrow" the return type of a method, and this still + // overrides the A->f method. Note that the override keyword is in place + // to ensure this method does indeed override a parent method. If it does + // not, it will cause a compile error, and actually, it is required + // if the method does override a parent method. + override int f(number @a) { /*...*/ } +} + +class C extends A { + // Not allowed! Actually, the only reason this isn't allowed is because we have + // added the override keyword. It is possible to create this method anyways, + // (without the override keyword), but then if the method were called, it would + // follow the normal overloading rules of deciding which method to call, as + // if we had simply created both methods within the same class. + override number f(int @a) { /*...*/ } +} + +class D extends A { + // This is not allowed, (with or without the override keyword, it's a compiler error with it), + // because the rules governing inheritance do not allow for return type changes. + string f(number @a) { /*...*/ } +} +%> + +In the last example, it is shown that return type changes are not allowed (only narrowing is). +Why is this, when return type changes are allowed within a single class? Consider the following code. + +<%CODE| +// We are creating a new D, but the variable @a is defined as type A +A @a = new D(); +// One might think this should work, because D defines a method with return type +// string, accepting one number parameter. However, the variable is defined as A, not D! +// It is entirely possible that instead of an object of type D, we have an object of +// type A, which does not define a method. +@a->f(1); +%> + +Examining the constructor, there is the new function, super(), +which is used to call the parent's constructor. super is optional, +however, in the case where a call to super() is omitted, it is +implied to have been called at the top of the child's constructor. If +super() is called, it must be called ''before'' manipulating the +parent's members, even transiently, otherwise it is a compiler error. For instance, consider the following: %%CODE| -class A{ - members{ - private @a - } +class A { + + private int @a; + public A(){ - @a = 0 + @a = 0; } public void initA(){ - @a = 9000 + @a = 9000; } public int getA(){ - if(time() % 2 == 0){ - initA() + if(time() % 2 == 0){ + initA(); } } } -class B{ +class B extends A { public B(){ - @var = getA() - super() + int @var = getA(); + super(); // Compile error } } %% -In this example, we have a compile error, because the call to getA() could -potentially call initA(), which manipulates @a, which is a member variable. -The reason for this is so that the object is never in an ''inconsistent state'' -which could occur if a subclass manipulates members of the parent before it is -allowed to run its initialization. Had we left off the call to super, it would -have been automatically placed at the top of the constructor, and this would not +In this example, we have a compile error, because the call to getA() could +potentially call initA(), which manipulates @a, which is a member variable. +The reason for this is so that the object is never in an ''inconsistent state'' +which could occur if a subclass manipulates members of the parent before it is +allowed to run its initialization. Had we left off the call to super, it would +have been automatically placed at the top of the constructor, and this would not have been a compile error. -==== Default Constructors ==== - -If no constructors are provided, a default constructor is always provided, with -the definition public A(){ } (assuming the class name is A). - -==== Destructors ==== +This works differently if there are no default constructors defined in the superclass, +and the subclass defines constructors with different signatures (which is allowed). +If so, in that case, it is required to call any constructor on the superclass +manually. For instance: -A destructor is automatically called when an object goes out of scope, and can -be used to do various things. It is never required to implement, however. It is -implemented in a '''destructor block''', much like member blocks. +<%CODE| +class A { + public A(int @a) { + // Note we are defining a constructor that accepts an int + } +} -%%CODE| -class A{ - members{ - private static int @instances = 0 - } +class B extends A { + public B(int @b) { + // super(@b); + // No call to super is required, it will be added automatically for us + // at the top, because we are defining a constructor with the same signature + // as one defined in A. It may be necessary to make a call to the superclass + // anyways, however, if you wish to change the value that is sent to the + // superclass's constructor. However, by default, the one added automatically + // will simply re-call super with the list of arguments that were passed in + // to this constructor. + } + public B() { + // In this case, we are defining a constructor that accepts no + // argument. But there is no constructor defined with no arguments + // in A, so we must manually call it ourselves, and provide a value. + super(0); + } +} - public static int totalInstances(){ - return(@instances) - } +%> - public A(){ - @instances++ - } +==== Default Constructors ==== - destructor{ - @instances-- - } +If zero constructors are provided, a default constructor is always provided, with +the definition: + +<%CODE| +/** + * @{InheritDoc} + */ +@{Inject} +public A(){ + if(@self != mixed) { + super(); + } } -%% +%> + +(Assuming the class name is A). However, +if a superclass defined a constructor that accepted parameters, without also +defining a constructor that takes no arguments, this will not happen, and you +must manually define a constructor which calls super with the appropriate +inputs. +Note that the default constructor is annotated with the @{Inject} annotation. If you do not +wish your class to be automatically injectable, you need to implement a no-arg constructor yourself, with +the @{NoInject} annotation. (This is used in the [[Dependency Injection|DependencyInjection]] framework.) === Static members and methods === You may have noticed that the keyword '''static''' has been used a few times. @@ -221,19 +352,141 @@ it is not possible to call a static method by dereferencing an instance. If you a pure utility class, that only has methods (no members), then consider using a namespace instead. -==== self keyword ==== -The self keyword is +==== @this and @self variables, and super and self keywords ==== +There are two variables that are specially defined within code inside a class, +@this and @self. @this refers to the +current instance of the class, and while not usually necessary, can be used in +all cases to provide clarity to the code. In some cases, however, it is necessary, +when a local variable hides a member variable. In our original class example, we +have the following code: + +<%CODE| + protected @b; + + public method2(int @b){ + return(@b + @this->b); + } +%> + +In this case, we must use the @this keyword, as we are naming the method's argument +@b, and so to differentiate between that and the member variable, we must +use @this->b. @this->b refers to the member variable, and +@b refers to the variable that is passed in to the method, that is, +the local variable. + +@self works in somewhat the same way, but it ALWAYS refers to the class +that it is used in, whereas @this refers to the instance that the type +is at runtime. To be very precise, @this refers to the current +instance of the class, which may be an instance of the current class, or it may be +an instance of a subclass. However, @self always refers to the current +class. This can be useful to call a method that is not private, when you want to ensure +that the code being called is not code that is overridden by a subclass. This feature +should generally speaking be used sparingly, as it is a violation of the Liskov Substitution +Principal, though it can be used in meta cases, for instance, to dynamically get the current +class definition for reflective purposes. It can also be useful when calling static methods. + +<%CODE| +// This will ALWAYS call the method defined in this class, and may be easier to type +// than the full name of the current class name. +@self::StaticMethod(); + +// If the subclass does not redefine the StaticMethod method, then using @this is the +// same thing. However, if a subclass redefines this method, and the instance was +// constructed as that subclass, this will refer to a different method. +@this::StaticMethod(); +%> + +The super keyword refers to the direct superclass. In the above examples, +we used it to call the superclass's constructor, but it can more generally be used to +refer to an element defined within a superclass. + +<%CODE| +class A { + public string @x = 'a'; + public string method() { + return(@x); + } +} + +class B { + @{Override} // It is good practice to use this annotation, and is required in strict mode + public string method() { + return(super->method()->toUpperCase()); + } +} + +A @a = new A(); +msg(@a->method()); // 'a' +B @b = new B(); +msg(@b->method()); // 'A' +@b->x = 'xyz'; // Note we are changing @x which is defined and only referenced in A +msg(@b->method()); // 'XYZ' +%> + +The self keyword refers to the ClassType of the currently defining class. This is a shortcut +to listing the current defining type, and can generally be thought of as simply replacing the self keyword +at compile time with the ClassType of the currently defining class. (Though this isn't exactly the case.) +This is mostly useful when defining method signatures. For instance: + +<%CODE| +class A { + /** + * If this method is overridden by a subclass, it must return an object of its own class (or a subclass). As it + * stands in this method, the return type must be of class A. + */ + self newInstance() { + return(new A()); + } +} + +class B extends A { + /** + * Since the return type was defined as self, we MUST also define the return type here as self, for this + * to be a proper override. But in this case, we must return a value of type B, not type A. + */ + override self newInstance() { + return(new B()); + } +} +%> + +The self keyword can also be used in generic definitions, such as ClassType, and within +general code, such as self @a = new self(); or msg(self);. === Access Modifiers === -'''public''', '''private''', and '''protected''' are the three modifiers that -control what can access an object's methods or members. If something is public, -anything can access it. If it is private, only that class can access it, and if -it is protected, only that class, or subclasses can access it. +'''public''', '''protected''', '''internal''', '''package''', and '''private''' are the five modifiers that +control what can access an object's methods or members (in order of visibility). If something is public, +anything can access it. If it is private, only that class can access it, and if +it is protected, only that class, or subclasses can access it. '''package''' means +that only classes defined within the same folder (or subfolders) may access it, and '''internal''' +means that only classes within that project (or class library, depending on how the class +library is set up) can access it. + +The access modifier may be left off. In that case, the behavior is a bit dynamic. If the method +is defined in a parent class or interface, then the method will adopt the overridden +method's access modifier. For classes, it will be whatever the parent specifically set +it as, and for methods defined in an interface, they will be public. (They are necessarily +public anyways, because all methods in an interface are public.) If the defined method +is not overriding a method in a parent class, then the method is considered internal. +Fields that have the default visibility default to internal. + +In general, when overriding methods in a parent class, it is only possible to grant it +a higher visibility state. For instance, if the parent class defines a method as public, +the child class cannot make it private. However, the opposite is allowed. If a parent +class defines a method as protected, a child class can make it public. + +The modifier can be applied to methods and fields, but also to classes. This leads +to an interesting effect, however. If there is a class defined with a lower visibility +than a method which returns or has parameters of a type that has a lower visibility, then +it means that the method cannot in fact be called by code that is not of the appropriate +visibility. If this occurs, then a compiler error will be issued. To fix it, you may either +make the class the same visibility as the method, or you can reduce the visibility of the +method. === Explicit namespacing === -In an instance, we can always using explicit namespacing to reference a parent's -methods. If we are in a static context, it will be a compiler error to reference -instance methods in this manner, but static references are always valid. For +In an instance, we can always using explicit namespacing to reference a parent's +methods. If we are in a static context, it will be a compiler error to reference +instance methods in this manner, but static references are always valid. For instance, assume class B had been defined this way: %%CODE| @@ -247,33 +500,34 @@ class B extends A { } %% -Note that we are explicitely calling A's constructor with the call to super, -and A's method2. If there had been several parents, this could be used to -"un-override" a call, or otherwise explicitely selected a method that had been -overriden by multiple children. This is useful outside of diamond inheritance, -but is more often used when dealing with multiple inheritance. Note that in the -case of super() constructors, it is always an error to cause a child's +Note that we are explicitely calling A's constructor with the call to super, +and A's method2. If there had been several parents, this could be used to +"un-override" a call, or otherwise explicitely selected a method that had been +overridden by multiple children. This is useful outside of diamond inheritance, +but is more often used when dealing with multiple inheritance. Note that in the +case of super() constructors, it is always an error to cause a child's constructor to be invoked before the parent's constructor. Calling a parent's method is only valid inside the class, an overridden method is not callable from outside of the instance. + === Interfaces === +Interfaces are a way to define the methods and constructors in an object, without actually implementing them. Thus, an +interface is not allowed to have code, but merely provides the definitions. Since all methods in an interface are only +useful as publicly accessible from outside classes, no access modifiers are allowed in the "instance" and unimplemented +static methods in the interface definition, everything is implied to be public. The exception is that static elements +are allowed to be defined and implemented within an interface. + +<%CODE| +interface MyInterface { + /** + * It is extremely useful to be sure to add comments in interfaces, since they are the ideal thing to program + * against. + */ + void method(); + + void method2(); + + /** + * This method being static and unimplemented means that it is an error to call MyInterface::method3(), but + * it is allowed to call @a::method3() where @a is an instance of MyInterface, because in reality, @a would + * be some concrete class, as interfaces cannot be instantiated. + */ + static void method3(); + + /** + * Since this is implemented, it is allowed to call MyInterface::method4(); + */ + static void method4() { + msg(); + } + + /** + * This is a constructor definition. This enforces that all implementing classes must provide a constructor + * with this signature (though it can have others as well). + */ + MyInterface(int @a); +} +%> + +It is not allowed to construct a new instance of an interface. Instead, you must instantiate an instance of a +'''concrete class''', or an object that is defined with the '''class''' keyword. However, if an interface was +defined with a constructor, it is allowed to dynamically construct classes that implement this interface, for +instance: + +<%CODE| +// One may often use which can refer to any interface, class, enum, etc, but +// restricting it further to class means that only concrete classes can be provided. This is useful +// when implementing a factory pattern, though consider dependency injection instead. +ClassType @c = MyClass; +MyInterface @a; +//@a = new @c(); // Compile error, because MyInterface does not define a no-arg constructor +@a = new @c(1); // Correct, as MyInterface defined a constructor that takes 1 int argument. +%> + === Abstract Classes === === Multiple Inheritance === @@ -331,19 +638,19 @@ delegate behavior to the correct super class's method, for instance: %%CODE| class A { - public mixed method(){ - return(1) + public mixed method(int @a){ + return(@a + 1); } } class B { - public mixed method(){ - return(2) + public mixed method(int @a){ + return(@a); } } class C extends A, B { - public mixed method(){ - return(B::method()) + public mixed method(int @a){ + return(B::method(@a)); } } %% @@ -355,13 +662,32 @@ defining it as such: %%CODE| class C extends A, B { - inherit B::method() + inherit B::method(int @a); } %% By default, methods with multiple overrides in parent classes inherit from the primary parent, in this case, A, though you can also be explicit and inherit a -method individually. +method individually. If no access modifier is provided, the method is inherited with +the same access level as the parent method. However, the visibility of the method +may also be increased: + +<%CODE| +class A { + protected void method(int @a) { + //... + } +} + +class B extends A { + /** + * This increases the visibility of the method, but changes nothing else. Note that in general, + * it is not allowed to decrease the visibility of any method, whether or not it is inherited using + * the inherit keyword. + */ + inherit public method(int @a); +} +%> === Type Unions === @@ -380,11 +706,11 @@ type B or A. In this case, the nearest parent is type A, so we could accept type do a runtime check to ensure that it is of type D or C only, but this is a runtime check, and it would be better if we could simply declare that we only want those two types. In that case, we can use a '''type union''' to signal to the compiler -the various types we will accept. To specify a type union, use the -'''|''' character. +the various types we will accept. To specify a type union, use the +'''%%NOWIKI||%%''' character. %%CODE| -D | C @var +D | C @var; %% As many types as required can be chained this way. As far as the inferred type of @@ -394,4 +720,510 @@ does an explicit cast first. So, as far as code that ''uses'' the variable, it w work as if it were defined as A @var, but with the guarantee that only a D or C type instance will have been assigned to it. -{{LearningTrail}} \ No newline at end of file +=== use statements === + +If classes were not arranged in a hierarchical structure, we would quickly find +ourselves being overwhelmed with naming conflicts, if two classes had the same +name. Therefore, MethodScript supports a hierarchical naming system. To define +a class within this +hierarchy, simply add the package name to the class when defining it. + +<%CODE| +// Instead of this: +class A {} +// do this: +class organization.project.B {} +%> + +In general, the naming convention should follow the convention of +organizationIdentifier.projectName, which, if followed correctly, will prevent +overlapping class names, but if the organization represents a single +project, it can be ok to leave off the project name. In general, the +organization name should be based on a domain you control, in reverse +domain order. For instance, if you own me.example.com, your organization +name should be "com.example.me". This is not required, but substantially +reduces confusion, especially when distributing your code. + +When creating a class like this, references to that class must be the '''fully qualifed \ +class name''', for instance, following the above class definitions: + +<%CODE| +A @a = new A(); +organization.project.B @b = new organization.project.B(); +%> + +All classes should be within a hierarchy, but having to type the fully qualified class +name every time is very cumbersome. Instead, you can use the '''use''' keyword to tell +the compiler to imply a package name. + +<%CODE| +// This tells the compiler to look in the organization.project package if it can't find a class +use organization2.project; +use organization.project; + +// So far so good, we have a class named A +A @a = new A(); + +// Uh oh, we don't actually have a class named B, but we do have one named organization.project.B. +// The compiler will see that we don't have a class named B, but will not give up yet, it will see +// if there is a class named "organization2.project.B" and "organization.project.B". There is one +// named organization.project.B, so it will internally change this line to +// organization.project.B @b = new organization.project.B(); +// for us, then get rid of the use statements. +B @b = new B(); +%> + +Since this is just syntax sugar, rather than a runtime setting, this is why you will always +see references to a class's fully qualified class name when reading error messages and things. +So, are system level classes in a hierarchy? Yes, they are! So why do you not need use +statements for them? The system automatically adds some use statements for you. Particularly, +it always adds ms.lang, as well as the current package, +but others may be added as well. Regardless, the ones +that are added automatically are added as if they are below all of your use statements. This +allows you to define classes with the same name as system classes, without having to fully +qualify them. On the flip side, if you have duplicate class names, and you use both within +a file, at least one of them would have to be fully qualified anyhow, but the consolation is +that at least '''ms.lang.''' is relatively little to type. Otherwise, the search pattern for +classes always goes from top to bottom in the use statements, if there are duplicate class +names. + +You don't have to provide the full package for the class names either. If you have a class +named com.project.util.MyClass, you can use com.project;, and then refer to the +class as util.MyClass, though it's usually just as well to +use com.project.util;. + +Additionally, you can use the asterisk notation in a use statement, to ''use'' all +the subdirectories, for instance, if we have 3 classes, named com.org.util.class1, +com.org.class2, com.org.folder.class3, we can do: + +<%CODE| +// This line is equivalent +use com.org.*; + +// to these lines: +use com.org; +use com.org.util; +use com.org.folder; +%> + +In general, this is not advised, as this may cause problems down the road if, +within the * structure, any class names are duplicated, and you're using one +of them, it will become a compile error. Therefore, it is still recommended that +you fully qualify the package name. Nevertheless, this functionality is available. + +=== Inner Classes === +An inner class is a class that is defined within another class, for instance: + +<%CODE| +class my.project.A { + static class B {} +} +%> + +There are two types of inner classes, however, unlike an outer class, an instance based +class, and a static class. The above example shows an static inner class being defined. + +A static inner class is easier to explain, as it is essentially just a way of further +containing other classes, and is nearly purely an organizational mechanism. (Though +there are a few access differences.) The inner class is +defined as if the outer class is simply a package, so the fully qualified class name of +B in this example is my.project.A.B and creating a new instance of class B +works the same as other usual cases: new my.project.A.B();. You can also +'''use''' the outer class, same as if it were a package, and then no longer need to +fully qualify the inner class. + +<%CODE| +// If we simply use the actual package, we can do A.B to fully qualify it +use my.project; + +new A.B(); + +// Or we can use the outer class, and just use the simple name +use my.project.A; + +new B(); +%> + +On the other hand, an instance based class is one that can only exist in the context of an instance of the +parent class. You cannot create a new instance of class B without creating an instance of +class A, then using that to create the new class B. To define the instance based class, +leave off the static keyword. + +<%CODE| +class A { + class B{} +} +%> + +In this example, we have to first create a new instance of the outer class, in order to create +an instance of the inner class. We can dereference the instance of A, and use the new keyword, +such as this: + +<%CODE| +new A()->new B(); +// Or create it later +A @a = new A(); +@a->new B(); +%> + +Within the class A, in non-static methods, we can create a new instance of the class B +in the usual manner, since @this is implied. This is the most common use +of instance classes. + +<%CODE| +class A { + class B {} + + public B newInstanceOfB() { + return(new B()); + } +} + +B @b = new A()->newInstanceOfB(); +%> + +Within the inner class, we have access to all elements of the outer class, and vice-versa, +including private elements, though an inner static class only has access to the static +context, and therefore cannot call instance (non-static) elements. +However, if it has an instance of the outer class, it may access private elements on the instance. + +Inner classes are allowed to create elements named the same as the outer class, therefore it +is sometimes useful to differentiate between what exactly @this refers to. + +<%CODE| +class A { + string @myProperty = "A->myProperty"; + string @a = "A->a"; + + class B { + string @myProperty = "B->myProperty"; + string @b = "B->b"; + + public void method() { + msg(@a); // Definitely refers to A->a + msg(@b); // Definitely refers to B->b + // Overload! In general, accessing any + // class element is the same as using an + // implied @this, so both @myProperty + // and @this->myProperty refer to the + // same thing, B->myProperty. + msg(@myProperty); // B->myProperty + msg(@this->myProperty); // B->myProperty + msg(A::this->myProperty); // A->myProperty + } + } +} +%> + +=== Immutable classes and immutable variables === +Classes may be marked as immutable, which means that none of the fields in the class may be changed after construction +is complete. + +<%CODE| +immutable class A { + string @myProperty; + // This is effectively constant due to the fact that we don't modify the value in the constructor + string @constant = "const"; + public A(string @value) { + @myProperty = @value; + } + + public void m() { + // Can't do this, would be a compile error + // @myProperty = "test"; + } +} +%> + +Immutable classes may be useful from a business logic standpoint, as it allows enforcement of immutability by the +compiler. However, the compiler/runtime can also do some optimizations when immutable classes are used, for instance, +classes that are immutable can be passed either by value or reference, and the effective result will be the same. So +the runtime may choose the most performant method if a class is marked as immutable. + +Individual fields may be marked as immutable as well. This has the effect of making the class immutable, but only +through that particular object reference. + +<%CODE| +// Note that MyObject is not immutable +class MyObject { + public string @value; + + public void setValue(string @v) { + @value = @v; + } +} + +class A { + MyObject @a; + immutable MyObject @b; + + public A(MyObject @a, MyObject @b) { + this->@a = @a; + this->@b = @b; + // This is valid. Immutable values are not "frozen" until construction is complete. + this->@b->@value = "initial value"; + this->setup(); + } + + public void m() { + // Valid + this->@a->@value = "test"; + this->@a->setValue("test"); + + // Compile error, fields in @b may not be modified, either directly or indirectly + // this->@b->@value = "test2"; + // this->@b->setValue("test2"); + // this->setup(); + } + + private void setup() { + // This is valid too, but only because the setup method is only called from the constructor. Note that this + // method is actually not available to the reflection mechanism either, as it is inlined in the constructor. + this->@b->@value = "called from setup"; + } + + public void publicSetup() { + // This isn't valid, because the method is public. Methods that modify immutable values are only allowed + // to be private, and are only allowed to be called from the constructor. + // this->@b->@value = "invalid"; + } +} +%> + +Immutable fields are a sort of overloaded type. That is, ''MyObject'' is different +than ''immutable MyObject'', and it is required to declare them differently in each use. Mutable fields may safely +be cast to the immutable variant, but not vice versa. + +<%CODE| +class A { + immutable MyObject @immutable; + MyObject @mutable; + + public void set(immutable MyObject @v) { + this->@immutable = @v; + } + + public void set(MyObject @v) { + this->@mutable = @v; + } +} + +A @a = new A(); +@a->set(new immutable MyObject()); // sets the immutable object +@a->set(new MyObject()); // sets the mutable object +@mo = new MyObject(); +@a->set((immutable MyObject) @mo); // sets the immutable object, despite the @mo value being mutable +%> + +It's also worth pointing out that an immutable field is not constant, and the referenced object may be changed, +(unless the field is marked as ''final'' in addition). The previous example shows this, when we set ''@immutable'' +in the set method, this is allowed. What we cannot do, however, is set some field within the MyObject instance, +even if MyObject normally does declare some publicly accessible methods that set fields. + +If a class itself is marked as immutable, it is not an error to also mark an instance of the field as immutable. This +can be desirable anyways, in cases where it's possible that the class itself becomes mutable in the future, but +regardless, you wish this field to always be immutable. In general, however, it's bad practice to change a class's +mutability after release. + +=== Class Library === +In general, MethodScript prefers to work with classes that are defined in the '''Class \ +Library'''. When using the class library, this prevents complex management of include +statements, and also provides an easier and more straightforward mechanism of managing +the classes within the class library, as well as enabling certain features in the compiler +that expect to know about all classes beforehand. '''Dynamic Classes''' are discussed below, +but are not expected to be used in normal use. Multiple class libraries may be defined, +but they must all be known at compile time, and may not be added to later. + +So what is the Class Library? It is simply a folder in your project directory that contains +several .ms files, which define classes. Your "default" class library is defined within +the classLibrary/ folder in your installation, but each LocalPackage may have their own +classLibrary folder as well. MSLP files will have their own class library bundled in +as well. When an .ms file is defined within a class library, the rest of your code does +not have to do anything extra to load them in; at compile time, they will all be loaded +automatically, and added to the object definition table that is usable at runtime +throughout the rest of your code. There can be no directly executable code within these +file, however, and this is enforced by the compiler. There may be only one outer class +definition per file, and the fully qualified class name will be the path of the class, +starting at the root of the class library folder. For instance, if we have the following +file structure: + +
+classLibrary/
+----organization/
+--------project/
+------------MyClass1.ms
+------------MyClass2.ms
+
+ +Then we would have 2 classes defined, '''organization.project.MyClass1''' and '''organization.project.MyClass2'''. +The namespace for both of these classes must be defined as "organization.project", since they are within the +'''organization/project''' folder structure. +The name of the file must correspond to the name of the class, this will be a compile +error otherwise. + +If we looked at MyClass1.ms, we might see the following: + +<%CODE| +use my.project; + +class organization.project.MyClass1 { + // OtherClass is defined in my.project + public OtherClass @a; +} +%> + +==== classLibrary Manifest ==== +In addition to providing a project structure, a class library may contain one or more '''manifest''' files, +(named simply "manifest.json"). There must be one manifest at the root of the classLibrary, but there may be more within +subfolders, if it makes sense to organize multiple projects within the same classLibrary. The manifest defines +certain information about the class library as a whole, and turns it into a "project". The manifest includes +information such as project versions, author information, changelogs, etc. The manifest.json file has a +specific format, which is described [Manifests|here]. One of the most useful features of the manifest is +the version number, and dependency information. The project should define what dependencies and versions +of dependencies are needed, but not where to find them. Meanwhile, as a system level configuration, the +system should define where to look for dependencies. This may be on the local system, or it may be in a +package manager. Regardless, at minimum, every classLibrary must have at least one manifest file within +the file structure that defines the organization name, project name, and project version. This ensures +that the dependency fulfillment mechanisms are always able to uniquely identify a project. A default +manifest.json file is created for you when running the system for the first time, but you must +change the values in it, if you intend on shipping your code, or re-using it in a more modular fashion. + +=== Dynamic Classes === +Dynamic classes are classes that are defined not through the class library, but dynamically +at runtime. These classes are defined more or less the same, but are not eligible for many +compiler checks. Things like ExhaustiveVisitors cannot be made to work with these classes, +they cannot be pre-compiled, and they cannot be used in the Dependency Injection system, among +other things. Having said all that, if you are ok with the downsides, these classes can be +defined anywhere, and then that file {{function|include}}'d (or dynamically loaded from other +sources, such as eval, or even the interpreter). Once loaded, they will work mostly just +as if they had been loaded at compile time. Existing, static code cannot reference the +classes, of course, as they would have already triggered a compile error for not being able +to find the class, but code that works generically with objects, or if the virtual class +extends/implements existing classes, this can be useful when doing dynamic things. Defining +a class outside of a class library, then causing it to be loaded will do this. + +=== Naming Conventions === + +Class, field, and method names are case sensitive, and for consistency sake, should generally conform to +standard rules, though this is only a convention, and not enforced (except in ultra strict mode, which is +not a recommended mode for normal code). MethodScript follows these conventions, and you are strongly +encouraged to follow the same conventions in your own code, even if you never distribute it. For reference, +here are the names of various casing conventions: + + * snake_case + * UPPER_SNAKE_CASE + * lowerCamelCase + * UpperCamelCase + +{| +| Class Names +| In general, class names should be upper camel case. For classes that contain common acronymns or abbreviations, +such as "HTTP" or "URL", then the acronym should be fully capitalized, for instance, "HTTPUtility" or "URLUtils". +There are also exceptions for core internal classes, and classes that extend them (other than those that extend mixed), +which use snake case, but this list is fixed and finite: mixed, array, closure, and primitive. Classes which +extend one of these (other than mixed) should use snake case as well. The words in a class name should generally be +only adjectives and nouns. +|- +| Method Names +| Method names should use lower camel case, and the first word should generally be a verb. If the first word is anyways +an abbreviation or acronym, it should be fully lowercase, though this is discouraged, because acronyms are always nouns. +|- +| Field Names +| Fields should use lower camel case, except for constants, which should be upper snake case. +|} + + +{{LearningTrail}} diff --git a/src/main/resources/docs/Numbers b/src/main/resources/docs/Numbers index 892477c851..f6c0e1b424 100644 --- a/src/main/resources/docs/Numbers +++ b/src/main/resources/docs/Numbers @@ -7,11 +7,12 @@ For floating point values, that is, numbers with a decimal, the max value is %%CONST|java.lang.Long.MIN_VALUE%%. Undefined numbers and Infinite values are not supported. -Integral numbers may be written in source as binary or hexadecimal values. +Integral numbers may be written in source as binary or hexadecimal or octal values. For instance, 15 may be written as either 15, 0b1111, -or 0xF. Binary values must start with "0b" and may only contain -the numbers "1" or "0". Hexadecimal numbers start with "0x" and may only contain -the numbers 0-9, and a-f, either lowercase or uppercase. Binary and hexadecimal -values may only be integral values. +0xF, or 0o17. Binary values must start with "0b" and may only contain +the numbers "1" or "0" (base 2). Hexadecimal numbers start with "0x" and may only contain +the numbers 0-9, and a-f, either lowercase or uppercase (base 16). Octal numbers must start +with "0o" (zero, lowercase letter o), and may only contain 0-7 (base 8). Binary, hexadecimal, +and octal values may only be integral values. {{LearningTrail}} \ No newline at end of file diff --git a/src/main/resources/docs/ObjectUsage b/src/main/resources/docs/ObjectUsage new file mode 100644 index 0000000000..53d4e731db --- /dev/null +++ b/src/main/resources/docs/ObjectUsage @@ -0,0 +1,129 @@ +{{unimplemented}} + +Creating an object and using an existing one are two different topics, and are thus given separate +treatments. This article discusses the usage of objects, rather than how they are created, for that +discussion, see [Classes]. + +== Using instances == + +An instance of an object is some concrete instance of a Class definition. You can think of a Class as +a blueprint, and an instance as the building itself. Many buildings can be built from the same blueprint, +and each will have their own slightly different properties, of which can also change over time. Manipulation +of one of these instances will not affect the others. A variable contains an instance of some class, though +it can change as well. A variable with a type, for instance <%CODE|int @a;%> can only contain an int, though +that int can start out as 5, and be changed to 8 later. While this is a simple example, it is important to +understand that there is a difference between what is currently stored in a variable, and what an instance +is. Normally, when using an instance, you will not need to understand static members, which are elements +(fields or methods) that are shared across all instances of that particular object, as so in this section, +we will only discuss objects that do not have static methods. In order to access an instance method or field, +we use the dot operator (period, '.'). This is a binary operator, meaning that there must be something on both +the right and left of the operator. The thing on the left is the instance that we are wanting to operate on, +and the right is the name of the method or field that we want to access. The left hand side may be either an +object itself, or a variable containing some instance. + +<%CODE| +int @value = 10; +string @s = 5.toString(); // "5" +string @r = @value.toString(); // "10" +%> + +Both of these examples call the method "toString", which is defined in the int class. The first example calls +it directly on an instance of the object, the second calls it on the variable, which in this case contains +an instance of the int value 10. + +<%NOTE|It is important to note that the dot operator also means concatenation, so you should be careful to note +that there is no @ in front of toString in the above examples. ''@v.@f'' means something different +than ''@v.f''. In the first case, that means to concatenate the values of @v and @f, and in the second, it means +to get the field named f from the value in @v.%> + +Fields are accessed the same way, but without the () at the end. Methods can be chained as well, the return value +on the first is used in the second, and so on. + +<%CODE| +array @value = array('VALUE', 'value'); +string @lowercased = @value.toString().toLowerCase(); +%> + +In this example, the intermediate string is directly used again. This is the same as writing: + +<%CODE| +array @value = array('VALUE', 'value'); +string @valueString = @value.toString(); +string @lowercased = @valueString.toLowerCase(); +%> + +== Static Elements == +Static elements of a class are things that are associated with the class itself, not the instances (if any) of +the class. The general principals work more or less the same, but there is slightly different syntax to access +the methods or fields, <%PRE|::%> is used instead, and the value on the left hand side must be a ClassType value. + +<%CODE| +int @max = int::GetMax(); // This returns the maximum allowed integer value +%> + +Just like with instances, the value on the left may be a variable, though the variable must be holding a ClassType +for this to work. + +<%CODE| +auto @i = int; +int @max = @i::GetMax(); +%> + +This feature is not strictly useful though, and for more strict programming styles, this is not recommended as such. +However, the ClassType definition can accept a constrained type, which is discussed in the next section. + +== ClassType == + +Using [Generics] and ClassType variables, it is possible to do more interesting things with static methods. Taking +the previous example, using ClassType instead, let's take a look at a different example. + +<%CODE| +ClassType @i = int; +int @max = @i::GetMax(); +%> + +In this example, we specifically define ''@i'' as ''ClassType<int>'', which is the precise type of ''int''. This +is the equivalent of the previous example, without auto typing. While this is possible, there are not many cases where +this is useful, because the only thing that can be assigned in this value is ''int'', so it would be easier to use +that value directly. However, the generic parameter can be less restricted and still correct, which may lead to more +useful scenarios. + +<%CODE| +ClassType @i = MySubclass; +@i::StaticMethod(); +%> + +In this example, @i could be any subclass of MySuperclass (or MySuperclass itself). Assuming MySuperclass defines +a method named StaticMethod, then this becomes useful if MySubclass may or may not override that static method. + +<%TAKENOTE|Overriding static methods is beyond the scope of this article, see the article about [Classes] for +a further discussion.%> + +It is worth pointing out that ClassTypes are themselves instances, and have instance methods, thus the need for +different operators for accessing instance methods vs static methods. To access the instance methods defined in +ClassType itself, use the . operator. + +<%CODE| +msg(int.getSuperclasses()); // Accesses the instance method getSuperclasses defined in ClassType +msg(int::GetMax()); // Access the static method GetMax defined in int +%> + +== Reflective access == + +It is possible to access elements in a class using reflection. This mechanism bypasses the compiler, however, and +should generally not be used, unless working with objects more generally. To access a method or field reflectively, +use the -> operator. + +<%CODE| +string @method = "toString"; +msg(@myObject->@method()); // Equivalent to @myObject.toString() +%> + +When using ->, this works similarly to using the dot operator, but the value in the string @method is resolved at +runtime. The value returned by ''@myObject->@method'' is typed as auto, thus bypassing any compile time checks +that could otherwise have been done, so in general, this should be avoided where possible. The value on the right +hand side of -> must be a string, or this is a compile error. Since the return value is auto, any further chaining +would also be auto, i.e. ''@myObject->@method().toLowerCase()'' would work if @method were "toString", but could +not be caught at compile time if there were an error. In cases where the method or field can't be found, an +ElementNotFoundException is thrown. Using this mechanism also disregards the access restrictions on the element, +though the security manager is first consulted, and a SecurityException is thrown. \ No newline at end of file diff --git a/src/main/resources/docs/OldExceptions b/src/main/resources/docs/OldExceptions new file mode 100644 index 0000000000..e46b93f69b --- /dev/null +++ b/src/main/resources/docs/OldExceptions @@ -0,0 +1,92 @@ + +Exceptions are thrown by various functions at runtime. Since MScript is not yet strongly typed, +it is not possible to catch all errors at compile time. Due to this, it is useful to be able +to programmatically determine if a function failed. To this end, since version 3.1.2, +MScript has the {{function|try}} function. This function takes 3 or 4 arguments: +%%CODE| +try(try_code, ivar, catch_code, [interested_types]) +%% +If you are familiar with other programming languages' try-catch mechanisms, then this +construct will be familiar. The code at try_code is run normally. If any +function in the code causes an exception to be thrown, execution will halt, and program +flow will start at catch_code. The exception thrown will be stored in +ivar, so that you can programmatically examine the cause of the exception. +Many times, you are only interested in a certain type of exception. This is where the optional +interested_types variable comes in. If omitted, all exceptions are caught. +If provided, it may be a single string, or an array of strings, where the provided values +are one or more of the exception types listed below. There are a select few errors that can +be caused by runtime issues which cannot be caught, but by far, most runtime issues can be +caught in a try function. In addition, you can trigger an exception being thrown with the +{{function|throw}} function. +%%CODE| +throw(exception_type, msg) +%% +throw accepts any valid exception type listed below, as well as a message. +The line number will automatically be added. This exception is then passed up the chain, +just as if any other function had thrown the exception. If the exception type is null, +this exception is uncatchable, however, it is best practice to use the {{function|die}} +function if you intend on killing a script. + +If an exception is passed all the way to the root of the script, and the interpreter has to +catch the exception, the script will terminate, and the default message will be logged to +console, and displayed to the user. In most cases, this may be enough. Also, in general, +exceptions have been massively improved, all exceptions give a much more detailed error +message, and also provide a line number to assist in debugging your scripts. Also note +that if debug-mode is on, ALL exceptions that are thrown will log to the console, even if +they are caught. This can help debug a potential problem with your script. The API has been +updated to include a list of possible exceptions that can be thrown by a function, and a +list of what the exception types are, and what might cause them to be thrown are listed below. +Please note that it is entirely possible that an exception being thrown was not noted in the +API -- this is a bug in the documentation. Please report it so that it can be corrected. Also +note that it is possible for the try function itself to throw an exception, if the arguments +are not of the proper type. Though it is possible to further catch those exceptions, it +probably means that your code is poorly designed. + +==Example== +When accepting user input, it is important to verify that their input is valid. Using +exceptions allows you to easily catch errors in their input. +%%CODE| +/test $index= >>> + # Assign an array to the variable @a + assign(@a, array('0', '1', '2')) + # array_get can possibly throw an IndexOverflowException, or a CastException + try( + msg(array_get(@a, $index)) + , @ex, + # The zeroth element in the exception tells us the exception type. + # That is important to get here, so we display proper error messages + if(equals(array_get(@ex, 0), IndexOverflowException) + , die('That index is not valid') + , die('Please enter an actual number') + ) + # We could have left this blank, but this allows us to more precisely filter our exceptions + , array(IndexOverflowException, CastException)) +<<< +%% + +==Non-Exception data validation== + +In addition to exception handling in 3.1.2, several functions have been added that allow you +to validate data without using the try-catch framework. Make note of the existence of the +following functions: +*{{function|array_index_exists}} +*{{function|is_array}} +*{{function|is_boolean}} +*{{function|is_double}} +*{{function|is_integer}} +*{{function|is_null}} +*{{function|is_string}} +*{{function|typeof}} + +among others. + +These functions will allow you to validate that the data entered is in fact of the specified +type, or can be cast to the specified type. + +==Exception Types== +Both the spelling and capitalization are important when using the name of an exception. The +proper format is displayed in the header of each section. + +%%EXCEPTION_TYPES%% + +{{LearningTrail}} diff --git a/src/main/resources/docs/Operators b/src/main/resources/docs/Operators index 4a40aa43a2..94e3a797db 100644 --- a/src/main/resources/docs/Operators +++ b/src/main/resources/docs/Operators @@ -1,117 +1,148 @@ -As of version 3.3.1, CommandHelper supports traditional operators, in addition to still allowing the pure functional approach. -(In fact, the operation functions are still used internally regardless). This allows you to write more readable code, by -using more standard symbols instead of only functions. Using operators instead of functions is highly recommended for all -new code, though the functional usage will continue to remain supported. +In addition to functional support, MethodScript supports traditional operators. +This allows you to write more readable code, by +using more standard symbols instead of only functions. Using operators instead of functions is highly recommended for +all code, though the functional usage will continue to remain supported, and may be more readable in rare cases. Consider the following perfectly valid code: -%%CODE| +<%CODE| if(and(equals(@var, 3), lte(2, @var2))){ msg('Something'); } -%% +%> -This is fairly hard to read, and could quickly get even more complicated and harder to read the more conditions you add. -Instead, you can use ''infix'' notation now, using standard C/Java operators. The same code as above, converted to the +This is fairly hard to read, and could quickly get even more complicated and harder to read the more conditions you add. +Instead, you can use ''infix'' notation now, using standard C/Java operators. The same code as above, converted to the infix notation looks like: -%%CODE| +<%CODE| if(@var == 3 && 2 <= @var2){ msg('Something'); } -%% +%> -Besides being less typing, it's much easier for a human to read, thanks to the operators. "@var equals 3 and 2 is less than or equal to +Besides being less typing, it's much easier for a human to read, thanks to the operators. "@var equals 3 and 2 is less than or equal to @var2" as opposed to "and equals @var 3 lte 2 @var2". Using parenthesis is also supported, to force an order of operations: -%%CODE| +<%CODE| if((@var == 3) && (2 <= @var2)){ msg('Something'); } -%% +%> -The following operators are supported, and their order of operations is from top to bottom. Note that all -operators are simply converted to the functional notation, so if your code is incorrect, the errors you -get will specify function names. +The following operators are supported, and their order of operations is from top to bottom. Note that all +operators are simply converted to the functional notation, so if your code is incorrect, the errors you +get will specify function names. Associativity tells in which order operators with equal priority are executed. This can be left (to right), right (to left) or non-assoc (not allowed to combine or nest). {| class="wikitable" |- ! Type ! Symbol ! Function Conversion +! Associativity ! Notes |- | ''Postfix'' | ++ -- | {{function|postinc}}/{{function|postdec}} +| Non-assoc | This is only considered postfix when it comes after an identifier: @i++ |- +| ''Array Sub-indices'' +| %%NOWIKI|[ ]%% +| {{function|array_get}} +| Left +| Using square braces allows for array accesses, and in combination with the = sign, setting sub-indices. +If the array set appears on the right hand side of an assignment, or in a general statement without an assign, it is an +array_get operation. If it appears on the left hand side of an assignment, it is an array_set operation. The brackets +apply to the element just preceding, for instance with @var['index'], it is assumed that @var +is an array or array like value. Empty braces, [], when on the left hand side works as an array push, and +when on the right hand side, or in a general statement without an assign, it is an array clone operation (which ultimately +still uses array_get). Sub-strings within strings may be pulled out using the bracket notation as well, and slices are supported. +array(1, 2, 3)[1..2] returns an array with 2 elements in it, namely 2 and 3. 'string'[0] returns +'s', and 'string'[0..1] returns 'st'. +|- +| ''Execute'' +| %%NOWIKI|( )%% +| {{function|execute}} +| Left +| Using parenthesis after a Callable allows for shorthand execution instead of using the execute function. Callables +are for instance closures and proc references, among a few others. These are first class values, and can be executed +in a number of ways, for instance if we define @c as closure @c = closure() { msg('hi'); }; then we can +execute it as @c();. +|- +| ''Soft cast'' +| %%NOWIKI|( )%% +| {{function|__cast__}} +| Right +| Soft (checking / non-converting) cast. Syntax (type) value. +|- | ''Unary'' | ! ++ -- | {{function|not}}/{{function|inc}}/{{function|dec}} +| Non-assoc | These are ''unary'' operators, they only operate on one identifier |- -|- | ''Exponential'' | ** | {{function|pow}} -| +| Left +| |- | ''Multiplicative'' | * / % | {{function|multiply}}/{{function|divide}}/{{function|mod}} +| Left | |- | ''Additive'' | + - . | {{function|add}}/{{function|subtract}}/{{function|concat}} +| Left | If a minus or plus sign is used to denote the sign of a number, it is handled slightly differently, for instance, ''2 + -1'' does not use any subtraction |- | ''Relational'' -| < > <= >= +| < > <= >= | {{function|lt}}/{{function|gt}}/{{function|lte}}/{{function|gte}} +| Left | |- | ''Equality'' | == != === !== | {{function|equals}}/{{function|nequals}}/{{function|sequals}}/{{function|snequals}} +| Left | There is no operational equivalent for equals_ic |- | ''Logical AND'' | && | {{function|and}} +| Left | |- | ''Logical OR'' -| || +| || | {{function|or}} +| Left | |- | ''Assignment'' -| = += -= *= /= .= -| {{function|assign}} -| There is no single functional equivalent except for = per se, @var += 1 is equivalent to -assign(@var, add(@var, 1)), etc. += uses {{function|add}}, --= uses {{function|subtract}}, *= uses {{function|multiply}}, /= uses {{function|divide}}, and .= uses {{function|concat}}. -|- -| ''Array Sub-indices'' -| [ ] -| {{function|array_get}}/{{function|array_set}}/{{function|array_push}} -| Using square braces allows for array accesses, and in combination with the = sign, setting sub-indices. -If the array set appears on the right hand side of an assignment, or in a general statement without an assign, it is an -array_get operation. If it appears on the left hand side of an assignment, it is an array_set operation. The brackets -apply to the element just preceeding, for instance with @var['index'], it is assumed that @var -is an array or array like value. Empty braces, [], when on the left hand side works as an array push, and -when on the right hand side, or in a general statement without an assign, it is an array clone operation (which ultimately -still uses array_get). Sub-strings within strings may be pulled out using the bracket notation as well, and slices are supported. -array(1, 2, 3)[1..2] returns an array with 2 elements in it, namely 2 and 3. 'string'[0] returns -'s', and 'string'[0..1] returns 'st'. +| = += -= *= /= .= []= []+= []-= []*= []/= [].= +| {{function|assign}}/{{function|array_set}}/{{function|array_push}} +| Right +| There is no single functional equivalent except for = per se, @var += 1 is equivalent to +assign(@var, add(@var, 1)), etc. += uses {{function|add}}, +-= uses {{function|subtract}}, *= uses {{function|multiply}}, /= uses {{function|divide}}, and .= uses {{function|concat}}. +Square brackets with an assign indicate that a value is being assigned to the array element at the index that is given between the square brackets. +If no index is given, the value is appended to the end of the array. |} -Note the lack of bitwise operators, which are usually standard in other languages. These are not provided, because the -operators are infrequently used, and may be used for other operations in the future. The functions themselves, +Note the lack of bitwise operators, which are usually standard in other languages. These are not provided, because the +operators are infrequently used, and may be used for other operations in the future. The functions themselves, {{function|bit_not}}, {{function|bit_and}}, and {{function|bit_or}} still exist, so no functionality has been removed. Also of note, auto-concatenation always takes lowest priority to all other operations. +When in strict mode, use of functional notation instead of operators triggers a compiler warning. This warning is +suppressible, however. + {{LearningTrail}} diff --git a/src/main/resources/docs/Optimizer b/src/main/resources/docs/Optimizer index a205735fc6..f55b3eaf5c 100644 --- a/src/main/resources/docs/Optimizer +++ b/src/main/resources/docs/Optimizer @@ -1,4 +1,4 @@ MethodScript has the ability to apply different optimization techniques, both at compile time and runtime. Due to the architecture of the engine, it is fairly straightforward to tag functions with various optimizations. -%%optimization_explanations%% \ No newline at end of file +%%OPTIMIZATION_EXPLANATIONS%% \ No newline at end of file diff --git a/src/main/resources/docs/Packet_Jumper b/src/main/resources/docs/Packet_Jumper new file mode 100644 index 0000000000..d833adda99 --- /dev/null +++ b/src/main/resources/docs/Packet_Jumper @@ -0,0 +1,418 @@ +Packet Jumper is an extremely advanced system that allows direct Minecraft protocol access via MethodScript. +In general, in order to successfully use this system, you must have a basic understanding of Java, and also +of the actual Minecraft protocol, though this tutorial can help bridge any gaps you might have in your understanding. +Code that uses this functionality is liable to break on every Minecraft version, and so should be avoided as best +as possible. Some tooling exists to help mitigate this churn, but it is always better to use other, existing API +methods if they exist, and this should only be used as a last resort. Scoreboard operations, for instance, are fully +covered by the API, and therefore should generally use packets to accomplish goals. + +Also note that this guide has been written for 1.20.4, and while the overall procedures outlined will likely be +relevant for all versions, the specifics (particularly packet ids and names) are likely to quickly become outdated. + +Packet Jumper heavily re-uses code from the original [https://github.com/steakteam/CHProtocol CHProtocol] extension, +but builds on additional features such as additional compile time type safety, and support for Mojang mapping names, +which are generally easier to read and understand. This obsoletes the CHProtocol extension. Big thanks to steakteam and +entrypointkr for creating the original CHProtocol. + +== Installation == + +Packet Jumper does not work out of the box. As this is an advanced feature that can cause your server to crash, +cause clients connected to your server to crash, and generally cause havok, this broken out into a separate extension. +You may find the extension for download from the [https://letsbuild.net/jenkins/job/PacketJumper/ community run Jenkins], +or if you have git, maven, and the jdk installed, build it yourself via +mscript -- build-extension -s https://github.com/LadyCailin/PacketJumper.git. + +On first run, the mappings file will be downloaded. This will take a moment, as it is a large file. + +== Version Differences == +Note that in general, the Minecraft versions makes changes to the raw protocol on a regular basis, though it tends +to be rarer that established packets get major incompatible overhauls. However, as the protocol itself is not +officially supported, there is nothing stopping Mojang from changing the protocol, and completely breaking your code. +PacketJumper exposes the protocol to you dynamically, which means that PacketJumper itself only needs minor updates +on each version, and in general the same PacketJumper jar will work across versions. Your code however, will not +necessarily. + +== Research Phase == + +At the base level, the entire Minecraft server simply exists to send and receive ''packets'' from the client. However, +the underlying APIs don't always expose direct access to do the things that you may wish to do. For instance, if you +wanted to cause an entity to have different movements depending on which player is seeing the entity, this is not +currently possible, as {{function|set_entity_loc}} actually moves the entity for all players. Under the hood, +however, the server is simply sending the "entity move" packet to each player individually. If we instead take over +on tracking the entity (or indeed, not even creating a real entity, and just having a "ghost" entity that we manually +track) we can instead send raw packets to whichever individual players we like. This has the effect of requiring us +to implement entity movement and interaction ourselves, in addition to using raw packets, but it is technically possible. + +Before we can even begin writing code however, we need to understand exactly what task we are trying to accomplish, +and which packets will need to be used to make the player see what we are wanting. The first step is to figure out +which packet to use. In general, you'll want to familiarize yourself with +[https://wiki.vg/Protocol https://wiki.vg/Protocol], which is the community's primary documentation on the Minecraft +protocol. The wiki here is hand written, so in general, this isn't guaranteed to be accurate, but the guide is good +enough to give you an idea of what fields do what. Additionally, you can use +[https://mappings.cephx.dev/ https://mappings.cephx.dev/] to cross check packet information across various mappings. +PacketJumper exclusively exposes the Mojang mappings, regardless of what server you are running, and what mappings +it uses (it is dynamically determined and translated into the appropriate mapping for you). + +In our example of having entities move differently for different players, we are want to find something that can +update an entity position. We're also wanting to run this during normal gameplay, and we want to send something +from the server to the client. Given that, looking at wiki.vg, we're needing to look in the Play and Clientbound +section. The "Play" section represents a "protocol", which is used at different phases of the client connection +lifecycle, but the Play category outlines most of the packets that are interesting once the client is connected and +playing the game. Since we're wanting to tell the client that the entity has moved, this is Clientbound, but when +listening for packets send from the client, we're interested in the Serverbound ones. Scanning through, we can +see a packet called "Update Entity Position". Reading the docs, it states that this is sent by the server when an +entity moves less than 8 blocks. + +{{TakeNote=The ids and packet names change from version to version, so don't rely on the specific names and ids in this +guide.}} + +The PacketId is 0x2C, the state is Play, and it is bound towards the client. There are 5 fields, entity id, delta x, +delta y, delta z, and on ground. The entity id is a VarInt, X, Y, and Z deltas are shorts, and on ground is a boolean. +The notes detail specific information about each field. + +This information tells us everything we need to know to properly create the packet, though we need to convert this to +specific data to use in code. + +Efforts have been made to standardize as much as possible across server versions, but that means that you need to use +naming conventions that PacketJumper recognizes. This full list can be obtained by calling {{function|all_packets}}, +which returns an array of data that we can use to program against. Create an alias or otherwise grab the output of this +code, and put it in a text editor, preferably one that supports json syntax formatting and highlighting. + +<%CODE| +console(json_encode(all_packets())); +%> + +This provides a huge list of packet information, which is specific to the currently running server, and is subject +to change across server types and versions. The first step is to cross check and find the packet we are interested in +in this list. On wiki.vg, we saw that the packet id is 0x2C. In decimal, this is 44. In the list of packets +returned by all_packets(), find the packet with id 44, where the protocol is "PLAY". Note that there may be +multiple packets with the same id across different protocols, so you need to ensure you've found the right one. + +There is a bunch of information in this object, which tells us specifics about the packet. Let's take a look at this: + +<%SYNTAX|json| +"REL_ENTITY_MOVE": { + "protocol": "PLAY", + "sender": "SERVER", + "superclass": "net.minecraft.network.protocol.game.PacketPlayOutEntity", + "deprecated": false, + "name": "REL_ENTITY_MOVE", + "comment": "", + "id": 44, + "fields": { + "onGround": { + "field": 6, + "name": "onGround", + "comment": "", + "type": { + "originalType": "boolean", + "type": "ms.lang.boolean" + } + }, + "yRot": { + "field": 4, + "name": "yRot", + "comment": "", + "type": { + "originalType": "byte", + "type": "ms.lang.int" + } + }, + "za": { + "field": 3, + "name": "za", + "comment": "", + "type": { + "originalType": "short", + "type": "ms.lang.int" + } + }, + "xRot": { + "field": 5, + "name": "xRot", + "comment": "", + "type": { + "originalType": "byte", + "type": "ms.lang.int" + } + }, + "ya": { + "field": 2, + "name": "ya", + "comment": "", + "type": { + "originalType": "short", + "type": "ms.lang.int" + } + }, + "hasRot": { + "field": 7, + "name": "hasRot", + "comment": "", + "type": { + "originalType": "boolean", + "type": "ms.lang.boolean" + } + }, + "xa": { + "field": 1, + "name": "xa", + "comment": "", + "type": { + "originalType": "short", + "type": "ms.lang.int" + } + }, + "entityId": { + "field": 0, + "name": "entityId", + "comment": "", + "type": { + "originalType": "int", + "type": "ms.lang.int" + } + }, + "hasPos": { + "field": 8, + "name": "hasPos", + "comment": "", + "type": { + "originalType": "boolean", + "type": "ms.lang.boolean" + } + } + }, + "class": "net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Pos" +} +%> + +The ''protocol'' field tells you which of the protocols this packet belongs to, and is needed both for the method +calls, and also for cross checking along with the id on wiki.vg. ''sender'' tells which direction the packet is going, +in this case it's clientbound because the server is sending it. ''superclass'' denotes the Java superclass of this +packet, and is only used for reference. If ''deprecated'' is true, you probably shouldn't be using this packet. Search +to find a better packet. Most often, these are packets which have just been replaced by another class which has +effectively the same functionality, but you may be forced to use the deprecated class anyways if there is no equivalent. +''name'' is the name of the packet that you will use in code, so make note of this. ''id'' is only for reference, and +should not be used except to compare with other references to the exact same version of the protocol. ''class'' denotes +the Java class of this packet, and is only used for reference. You'll also notice a blank ''comment'' field. This +is rarely populated, but when present, offers useful additional information about the field or class. + +''fields'' is the most interesting property. This defines the different fields that are available in the packet. +Each of the field objects contains the following properties: + +''name'' is the name of the field. This is the name you'll use in your code. ''type'' shows what type is expected. +The inner ''type'' shows the MethodScript type, which is the type you'll use, but the ''originalType'' shows the actual +Java type of the field. This is important to note, because it's up to you to ensure that for instance in the case of +a Java short, your value is in fact within -32768 and 32767, because otherwise you'll get a runtime error when +PacketJumper tries to shove the MethodScript integer (which is 32 bit) into the short (which is 16 bits). ''field'' +is the field position, and is just for reference. + +In this example, there are no generics, but let's look at a field where that is the case: + +<%SYNTAX|json| +"profileIds": { + "field": 0, + "name": "profileIds", + "comment": "", + "type": { + "originalType": "java.util.List", + "genericType": { + "originalType": "java.util.UUID", + "type": "ms.lang.string" + }, + "type": "ms.lang.array" + } +} +%> + +In this example, we also have a generic subtype. The top level object is an array, but it should be filled with +strings, which are in fact UUIDs. This information can't inherently be encoded in the MethodScript type system, +so it's up to you to ensure that you've populated it with the correct information. Generic types can have further +subtypes, so for instance you could have a list of lists of strings, in which case you would need an array of arrays +of strings in MethodScript. + +Enums are another type that has special support: + +<%SYNTAX|json| +"source": { + "field": 2, + "name": "source", + "comment": "", + "type": { + "originalType": "net.minecraft.sounds.SoundSource", + "enumType": "net.minecraft.sounds.SoundSource", + "type": "ms.lang.string", + "enumValues": [ + "MASTER", + "MUSIC", + "RECORDS", + "WEATHER", + "BLOCKS", + "HOSTILE", + "NEUTRAL", + "PLAYERS", + "AMBIENT", + "VOICE" + ] + } +} +%> + +The ''enumType'' is just a reference type, but the ''enumValues'' list the valid enum values that can be sent. The +type is still a string though, and it will automatically be converted into the correct enum type. + +Another thing to note is that some types are not (yet) supported. This could be due to the type only being supported +in newer versions of PacketJumper, or because support simply hasn't been coded in at all yet. +In those cases, the type will be "", which means that this field cannot actually be used. However, +the plan is that all types will eventually be supported, and PRs that add support for missing types will generally +be accepted. Even if the field is unsupported, it may be possible to write the packet anyways, if the field's default +value (usually null or nullish values) will work, and for incoming packets, for reading the other fields, if the +unsupported types don't matter to your use case. + +The list of unsupported types is added to a top level ''__UnsupportedTypes'' array. This is only for reference. + +You also generally need to understand how the entire server works together, in order to provide a complete solution +to the problem you're trying to solve. For instance, in our example, even if we successfully send the packet to +move an entity for the individual player, the server will occasionally try to reset the entity position. You may not +inherently know what packets the server is sending or receiving, so you may need to use a tool such as +[https://github.com/adepierre/SniffCraft SniffCraft] which works as a proxy between the client and server, and can +log packets in both directions, which can give you an idea of what kind of packets you need to emulate and/or modify. + +== Implementation == +Once all the necessary packet and field information has been determined, we can begin implementation. + +When sending packets out, the first step is to create a packet of the desired type. You'll need the protocol, direction, +and packet name. + +<%CODE| +@packet = create_packet('PLAY', 'OUT', 'REL_ENTITY_MOVE'); +%> + +The protocol for this packet is PLAY, we're sending it out (as opposed to in), and the packet name is +''REL_ENTITY_MOVE''. Next, we need to write to the relevant fields with packet_write. Say we want to move +the entity one block in the x direction. + +<%CODE| +packet_write(@packet, 'entityId', get_entity_transient_id(@entityUuid)); // Packets require the transient id, not uuid +packet_write(@packet, 'xa', 4096); // One block in the positive x direction, there are 128 * 32 steps in one block +packet_write(@packet, 'onGround', true); +%> + +Finally, we need to send the packet to the appropriate player. + +<%CODE| +send_packet(@player, @packet); +%> + +This will send this packet to the single player, which will cause the entity to move forward one block in the x +direction. + +However, after a few seconds, the entity will move back to its original position! + +This is because the server occasionally sends an ENTITY_TELEPORT packet to the client. We must intercept that packet, +and ideally overwrite the position in the packet to that of our "fake" entity position. In this case you could also +cancel the packet (all packet send and receive events are cancellable) but in this case it's probably a better idea to +overwrite the values, but for example's sake here's how simply cancelling it would look: + +<%CODE| +bind('packet_sent', null, array(protocol: 'PLAY', type: 'ENTITY_TELEPORT', values: + array(id: get_entity_transient_id(@entityUuid))), @event) { + cancel(); +} +// We also have to register for the events to start firing for these packet types. +register_packet('PLAY', 'ENTITY_TELEPORT'); +%> + +By default, PacketJumper doesn't actually register to listen to any packets, as this would be unnecessarily +inefficient. The {{function|register_packet}} function tells PacketJumper that it needs to start processing events +of the given type, and is required before the binds for these packets will start to be triggered. + +{{TakeNote=In the REL_ENTITY_MOVE event, the id was named "entityId" and in ENTITY_TELEPORT, it's just "id". Luckily, +PacketJumper offers some additional compile time checks on the values array, if you hardcode the protocol and type +parameters, which is highly recommended. This will make it easier to quickly see if you have errors in your +prefilters.}} + +We also need to handle the case where the player logs out and logs back in. In this case, the entity will be reset +back to the original location, because the server sends the SPAWN_ENTITY packet to cause the player to load in the +entities at first, but the trouble is, this includes the server-controlled position location, which is not where we +want the entity to be for this player. Also, in this case, we can't simply cancel the packet as we did for the +ENTITY_TELEPORT packet, if we did, the client wouldn't see the entity at all, we instead have to modify the +contents of the packet individually. In this case, we'll keep it simple, and only modify the x, y, and z parameters, +but there are others that you may need to modify as well, such as the velocity and angle, etc. + +Unlike most events, we can't use the {{function|modify_event}} function, but we can write directly to the packet with +{{function|packet_write}}. + +<%CODE| +bind('packet_sent', null, array(protocol: 'PLAY', type: 'SPAWN_ENTITY', + values: array(id: get_entity_transient_id(@entityId))), @event, @entityId) { + // This is up to whatever your logic is, to get the location for the entity on the per player basis + @pos = _getEntityPosition(@entityId, @event['player']); + packet_write(@event['packet'], 'x', @pos['x']); + packet_write(@event['packet'], 'y', @pos['y']); + packet_write(@event['packet'], 'z', @pos['z']); +} +// Don't forget, we have to register to get events for this packet type. +register_packet('PLAY', 'SPAWN_ENTITY'); +%> + +This is likely the best handling for the TELEPORT_ENTITY packet as well, rather than cancelling it, since this will +prevent desyncs from moving the entity without any way to recover. + +So, if you don't know which packet events you need to intercept, how can you find that? One of the easiest ways is to +use a packet sniffer, which will log incoming and outgoing packets to your client. This can be used to zero in on the +packets you need to care about, but will take a bit of sleuthing. Using +[https://github.com/adepierre/SniffCraft SniffCraft] for instance, we can intercept the packets, and print the packet +data for relevant looking packets, compare that to the wiki.vg and other docs, and see if that is the packet we're +interested in intercepting. A warning, this is not necessarily easy, and you may not find answers for what you're +looking to do, instead requiring reverse engineering to get there. Hopefully with the tools provided in this guide, +you will be able to get a head start on any detective work. + +== Conversions == + +For some types, conversions are obvious, a field with a Java int type accepts a MethodScript int type. However, +for more complex type conversions, it is not always obvious. This table lists the supported conversions. +Implicit conversions: All numeric types (floating point, integers, including bytes), Strings and UUIDs (to string), +List/Collection/Set/EnumSet/Map/arrays except byte arrays to array, and byte arrays to byte_array. + +Different servers have different class names, and so this list only gives you an indication of the specific class +name, the specific class name is obtained via ProtocolLib. More classes will be supported over time, see the +__UnsupportedTypes value in the return of {{function|all_packets}} for a list of types which PacketJumper does not +currently support. + +{| +|- +! scope="col" width="40%" | Java Type +! scope="col" width="60%" | Conversion +|- +| Optional +| NOT SUPPORTED YET. An array with 0 items if the item isn't present, otherwise an array with the item at index 0. When writing, \ +must be an array of length 0 or 1. +|- +| IChatBaseComponent +| A json string. +|- +| ItemStack +| The same item stack array used elsewhere +|- +| BlockPos +| An array containing 'x', 'y', and 'z' integers. +|} + +Also note that double comparisons are done with a delta of 0.0001. If you need something more or less precise, +you must do so inside the event handler, not inside the prefilter. + +== Unannounced Packets == +Actually, the packet creation and packet logic doesn't strictly need to use known packets. Servers can dynamically +create packets, as well as extensions and other packets that are unknown to PacketJumper. For these packet types, +you can't actually use the string field names, but you can instead pass an integer, based on the field index, and +an attempt will anyways be made to set the value. You can use arbitrary strings in create_packet, and it will fail +if the system can't find the packet, but it will attempt to find the packet even if it's not one of the ones returned +in all_packets(). + +<%CODE| +// Sends the input directly to ProtocolLib to try to find the packet. This should be the java class name of the +// server implementation you're running. +@packet = create_packet('PLAY', 'OUT', 'net.minecraft.packetname'); +packet_write(@packet, 3, 1.0); // Will send the input directly to ProtocolLib as is. +%> diff --git a/src/main/resources/docs/Permissions_Settings b/src/main/resources/docs/Permissions_Settings new file mode 100644 index 0000000000..18cb414c6e --- /dev/null +++ b/src/main/resources/docs/Permissions_Settings @@ -0,0 +1,136 @@ +It's usually not necessary to define permissions to use the basic alias/macro functionality. More advanced features +like scripting may require some permissions to be defined. + +== External Commands == +All permissions remain the same when commands in plugins or vanilla server are called by this plugin. + +''Example:'' Bob does not have permission to use the '''/give''' command. If Bob triggered an alias which calls +'''/give''', he still wouldn't be able to use it. + +The exception to this is the '''runas()''' function, which can override permissions in some cases. However, the function +itself is restricted and requires permission to use (see below). + +== Internal Commands == +These commands are provided by the plugin itself and are not aliases: + +{| class="wiki-table" width="100%" +|- +! Permission +! Command +! Description +|- +| ''No permission required'' +| '''/commandhelper ''<arguments>''''' +| Gain meta information about CommandHelper. +|- +| ''No permission required'' +| '''/runalias ''<alias>''''' +| Run an alias. Can be used by other plugins to call aliases. '''''<alias>''''' includes the forward slash. \ +''Example:'' '''/runalias /eat cake''' would always fire off the alias '''/eat cake''' +|- +| '''commandhelper.reloadaliases''' +| '''/reloadalias''' or '''/reloadaliases''' +| Reload all aliases from file. +|- +| '''commandhelper.interpreter''' +| '''/interpreter''' +| Puts your chat into [[Interpreter Mode|interpreter mode]]. Note that interpreter mode must also be enabled in the +preferences. +|} + +== Function Types == +Functions are divided into two groups: +=== Unrestricted Functions === +These are used for typical programming type tasks or retrieving basic information. They are considered to be harmless +on their own. + +''Example:'' '''if()''' can not be used to bypass protections or do harm. + +No permissions are required to use these. + +=== Restricted Functions === +These often tie into game functionality and have some potential for abuse. + +''Example:'' '''spawn_entity()''' can be used to spawn a large number of creepers. + +Permission is required to use these. The next section describes how to give access to these functions. + +== Scripting Permissions == +There are two ways to handle the permissions for restricted functions: +=== Function Based Permissions === +These permissions apply no matter where or how a function is used. + +{{TakeNote|text=These apply to ALL scripts everywhere, and so are usually not recommended for use except by advanced +users. See alias based permissions below for more granular control}} + +{| class="wiki-table" width="100%" +|- +! width=300 | Permission +! Description +|- +| '''commandhelper.func.use.''<function name>''''' +| Allow usage of the function ''<function name>'' in a script. +Basically, allow running created aliases containing this function. +|} + +It is not recommended to give these permissions to untrusted users unless you know exactly what you're doing. Instead, +carefully craft aliases for them to prevent abuse. Read on to learn how to create permissions for individual aliases. + +=== Alias Based Permissions === +Alias based permissions apply to all functions within an alias. Think of them like overrides. They allow for more fine +grained control since you can control the content of the alias. These permissions allow for quick, common sense default +handling of aliases. If, however, you need very fine grained control, see the section below with star aliases. Alias +based permissions only act to ''further unrestrict otherwise restricted functions''. That is, you can't use alias +permissions to directly deny access to a command in which the user would otherwise have permission for. The idea is +that if you trust a user to use all the functions in a script, then it doesn't matter so much how those functions are +arranged, they should still be able to use the command as a whole. This isn't always true though, so in those cases, +you'll need to do your own permission checking from within the alias (see the section on star permissions below). + +To create permissions for an alias, add a label in front of it: + ''<label>'':/command = ... + +There are two types of labels: +==== Permission Labels ==== +Put only letters, numbers and underscores in the label to assign the permission '''commandhelper.alias.''<label>''''' +to the entire alias. + +''Example:'' '''tasty:/eat cake = ...''' would cause people with '''commandhelper.alias.tasty''' to be given permission +to use '''/eat cake''' + +==== Group Labels ==== +Start your label with a tilde "'''~'''" to turn it into a group label. + +Add the group names separated by forward slashes "'''/'''" to give them permission. + +Add a hyphen "'''-'''" in front of a group name to remove permissions instead. + +Groups take priority from left to right. + +Example: '''~mods/-admin/default:/eat cake = ...''' would give permission to mods and the default group, but remove +permissions from the admins (causing restricted functions to always fail). A user in both the admin and default group +would lose permissions due to how priority works. + +==== Star Label ==== + +You can use the star label as an alternative to assigning all groups to an alias, or giving all groups a permission, +then tagging the alias with that label. This opens the alias up to all users. It essentially disables permission +checking entirely for that one alias. + +%%ALIAS| +*:/safeAlias = dangerous('careful') +%% + +This is useful in combination with the has_permission() function, in the case where you need super fine +grained control of the permissions for this alias. You can open up the alias to everyone, then more carefully select +the behavior from within the alias, based on the results of has_permission() + +== Extra Notes == +=== Shorthand Permissions === +'''commandhelper.''<permission>''''' can always be shortened into '''ch.''<permission>''''' if you feel like typing less. + +=== Console Commands === +Aliases can always be triggered from the console. However, some functions expecting a player will fail. + +External Commands will generally behave as if you typed the command from console yourself. + +{{LearningTrail}} diff --git a/src/main/resources/docs/Persistence_Network b/src/main/resources/docs/Persistence_Network index b7fe476445..5c4afee1f8 100644 --- a/src/main/resources/docs/Persistence_Network +++ b/src/main/resources/docs/Persistence_Network @@ -13,7 +13,7 @@ There are several supported formats, and there is the potential to add more in t In your configuration file, a connection can be aliased, to make re-specifying a connection easier, but the actual connection specification is a URI that maps to a specific data source. For instance, the default SQLite format is simply a pointer to a file: -
sqlite:///home/data/persistence.db
+%%PRE|sqlite://home/data/persistence.db%% There are several different connection types supported, and each has a slightly different requirement: @@ -23,12 +23,12 @@ There are several different connection types supported, and each has a slightly ! scope="col" width="50%" | Description ! scope="col" width="30%" | Example ! scope="col" width="10%" | Since -%%persistence_connections%% +<%PERSISTENCE_CONNECTIONS%> |} In addition, several modifier types can be specified, which modify the connection type. They are specified as extra protocols at the start of the URI. -
transient:readonly:yml://persistence.yml
+%%PRE|transient:readonly:yml://persistence.yml%% In the above example, the transient and read-only flags have been added to the connection. The specific meaning of each flag is as follows, and they aren't always @@ -38,13 +38,13 @@ applicable to all connection types. |- ! scope="col" width="20%" | Flag Name ! scope="col" width="80%" | Description -%%data_source_modifiers%% +%%DATA_SOURCE_MODIFIERS%% |} Invalid modifiers will cause a warning to be raised during startup, but will otherwise be ignored. A note on file based URIs: The file path is specified after two forward slashes, so an absolute -path on unix looks like this: yml:///path/to/file, and an absolute path on windows looks like +path on unix looks like this: yml://path/to/file, and an absolute path on windows looks like this: yml://C:/path/to/file (alternatively yml://C:\path\to\file will also work). On all platforms, a relative path would look like this: yml://path/to/file. Additionally, file based connections are '''usually''' going to be much faster, but less reliable than SQL based @@ -58,16 +58,16 @@ For a full rundown of the speed comparisons, see the chart below. There are special implementation considerations you must take into account if you are writing an external system that integrates with the persistence network, (including if you edit the -files by hand), so you should read up on the [[CommandHelper/Staged/Persistence_Network_Integration|Persistence Network Integration]] +files by hand), so you should read up on the [[Persistence_Network_Integration|Persistence Network Integration]] guide before you attempt to edit the output files, or otherwise care about the internal storage specifications. ===Connection Aliases=== Often times you will want to re-use a connection, but you don't want to have to re-specify the full connection details for each filter. In this case, you can use connection aliases. A connection alias looks just like a filter, but the filter name starts with a dollar sign. -
+%%PRE|
 $connection=mysql://username:password@host:3304/database/table
-
+%% Then, elsewhere, instead of rewriting the entire connection string, you may simply use $connection @@ -79,7 +79,7 @@ the namespace conventions followed by the filter system map to the REAL namespac the namespaces you use in code. For instance, if you were to make a call to store_value('name.of.key', 'value'), the value will actually be stored in storage.name.of.key. For a more detailed description of the namespaces, see -[[CommandHelper/Data_Manager#Namespaces|this wiki page]]. +[[Data_Manager#Namespaces|this wiki page]]. A filter is a simple regex style matcher; if a key matches this filter, it is stored via this connection. Filters are specified as such: filter=connection where @@ -96,10 +96,10 @@ following wildcards are supported: If we are attempting to store a value in "storage.key.name", and we have the following two filters defined: -
+%%PRE|
 storage.**.name=$connection1
 storage.**=$connection2
-
+%% Then it would be stored in $connection1, since that is a more specific match. It is defined as a more specific match, because, minus wildcards, more namespaces match. This mechanism of filter competition allows for very specific control over what data goes where, while also not @@ -132,7 +132,7 @@ to copy data, and not move it, you also want to use the merge tool. You can also data manager to show hidden data, that is, data that is stored in the data store somewhere, but isn't accessible due to bad mappings. -For more information on these tools and more, [[CommandHelper/Staged/Data_Manager|see this article]]. +For more information on these tools and more, [[Data_Manager|see this article]]. ==Usage== @@ -144,7 +144,7 @@ paths are relative to the configuration file itself. ===Example=== -
+%%SYNTAX|ini|
 #Lines starting with # are considered comments
 
 #These are our aliases
@@ -162,7 +162,7 @@ storage.players.**=$sqlite
 #that got server information, we might map that accordingly
 storage.server_info.**=$remote
 
-
+%% So, now, let's go over what would happen when we run our code. @@ -212,7 +212,7 @@ foreach(@iterations, @iteration){ using the following persistence.ini: -
+%%SYNTAX|ini|
 **=sqlite://persistence.db
 storage.ini.**=ini://persistence/persistence.ini
 storage.sqlite.**=sqlite://persistence/persistence.db
@@ -220,12 +220,12 @@ storage.mem.**=mem://persistence/persistence.db
 storage.json.**=json://persistence/persistence.json
 storage.ser.**=ser://persistence/persistence.ser
 storage.yml.**=yml://persistence/persistence.yml
-
+%% Take what information you will from the data, and feel free to run it on your system to get actual values relevant to your system, not just relative to each other on the test system. -
+%%PRE|
 +-------------+--------+-------+--------+-----------+--------------+----------------------------+
 | iterations: |    1   |   10  |   100  |    1000   |     5000     | File size with 5000 values |
 +-------------+--------+-------+--------+-----------+--------------+----------------------------+
@@ -245,7 +245,7 @@ not just relative to each other on the test system.
 +-------------+--------+-------+--------+-----------+--------------+----------------------------+
 |     ser     |  3 ms  |  0 ms |  4 ms  |   36 ms   |    117 ms    |           31.0 kB          |
 +-------------+--------+-------+--------+-----------+--------------+----------------------------+
-
+%% An important observation that could be made based on this data is that SQLite is considerably slower than many of the other protocols. This is because SQLite diff --git a/src/main/resources/docs/Persistence_Network_Integration b/src/main/resources/docs/Persistence_Network_Integration index 616990bac2..e19367c6d0 100644 --- a/src/main/resources/docs/Persistence_Network_Integration +++ b/src/main/resources/docs/Persistence_Network_Integration @@ -19,41 +19,41 @@ For instance, assume we want to store a value at "a.b" and "a.b.c". In a hierar data source, "a" would be an array, and so would "b", and "c" would be a single value. But doing this prevents the single value from being stored in "b". Consider the json: -
+%%PRE|
 With just the value in a.b:
 
 {
-	"a": {
-		"b": "value stored at a.b"
-	}
+    "a": {
+        "b": "value stored at a.b"
+    }
 }
 
 With just the value in a.b.c:
 
 {
-	"a": {
-		"b": {
-			"c": "value stored at a.b.c"
-		}
-	}
+    "a": {
+        "b": {
+            "c": "value stored at a.b.c"
+        }
+    }
 }
-
+%% As you can see, given this hierarchical data structure, it is impossible to merge the two structures as is. Instead, the special key "_" is reserved for "root" values, and should be taken to mean "the value stored here is the value for my parent namespace". Merging the data shown above yields this json: -
+%%PRE|
 {
-	"a": {
-		"b": {
-			"_": "value stored at a.b",
-			"c": "value stored at a.b.c"
-		}
-	}
+    "a": {
+        "b": {
+            "_": "value stored at a.b",
+            "c": "value stored at a.b.c"
+        }
+    }
 }
-
+%% This hierarchical structure only applies to Data Sources that actually have a hierarchy of data, not flat storage, for instance, ini. @@ -89,9 +89,9 @@ There are three columns in the table, and the table name is determined by the us The columns are defined by the query: - -%%MySQL_CREATE_TABLE_QUERY%% - +<%SYNTAX|sql| +<%MYSQL_CREATE_TABLE_QUERY%> +%> Selections, inserts, etc, must take the key and binary hash it with the UNHEX and MD5 functions: UNHEX(MD5('key')). This provides a way for the key to be unlimited length, yet still @@ -113,9 +113,9 @@ SQLite data is a flat data structure. Since TEXT columns can be indexed in SQLit the key column is the primary key. The database may contain multiple tables, but only the table created with the query: - -%%SQLite_CREATE_TABLE_QUERY%% - +<%SYNTAX|sql| +<%SQLITE_CREATE_TABLE_QUERY%> +%> will be used. @@ -134,11 +134,11 @@ in the data itself. Temporary memory is a flat data structure. Since this data is completely internal, no other processes can access the data. However, other code running in the same memory space can access the data via the MemoryDataSource static methods. The -data is stored in a Map. All the methods in the MemoryDataSource +data is stored in a Map<String, String>. All the methods in the MemoryDataSource class are thread safe, so multiple threads may concurrently access those databases with no issues. === YML === YML data is stored as a hierarchy. The format follows the standard YML format, which -is plain text. \ No newline at end of file +is plain text. diff --git a/src/main/resources/docs/Prefilters b/src/main/resources/docs/Prefilters new file mode 100644 index 0000000000..fcee1a5c94 --- /dev/null +++ b/src/main/resources/docs/Prefilters @@ -0,0 +1,11 @@ +Strictly speaking, prefilters are not needed. A series of checks in the event itself can be used to determine if an +event should run. However, using prefilters makes it much easier to control events, and allows for better optimization +on the back end of the code. Each event will list what prefilters it supports, and what type the prefilter is. +Prefilters add restrictions, by default if no prefilters are added, the event will run. There are multiple types of +prefilters, listed below, with examples. + +Not all filtering possibilities will be achievable with prefilters. For very complex filter rules, you may still need +to use if statements inside the code itself. However, whenever possible, you should use prefilters, both for their ease +of use, and their potential for code optimization. + +<%PREFILTER_DESCRIPTIONS%> diff --git a/src/main/resources/docs/Procedures b/src/main/resources/docs/Procedures new file mode 100644 index 0000000000..ee5b3a9a58 --- /dev/null +++ b/src/main/resources/docs/Procedures @@ -0,0 +1,285 @@ +Procedures (also referred to as procs) are a way of calling code multiple times, without having to copy +paste the code everywhere. Also commonly known as functions, they accept zero or more inputs, and have +zero or one output. Procs are named, and can be referenced elsewhere by name. + +[[Closures]] are similar to procs, but have different semantics, though both procs and closures are of the +''Callable'' type, meaning they can be executed via {{function|execute}} or with parenthesis based execution, +with the addition that procs can be called by name. + +== Defining a proc == + +The first step to using a proc is to define it. The general format of a proc definition is: + +<%PRE| +[ReturnType] proc ([arguments]) { + [Code] +} +%> + +For instance: + +<%CODE| +int proc _asdf(int @int, string @string = 'a default value') { + return 42; +} +%> + +The optional return type is some valid MethodScript type, followed by the mandatory ''proc'' keyword, the mandatory +proc name, which must start with a single underscore, a left parenthesis, zero or more arguments, with or without +types, followed by a right parenthesis, followed by the code block (which may be empty). Arguments may be considered +optional by using an assignment in the variable declarations. The proc may return a value (in this example the int 42) +or not (a void type proc). If untyped, the return type is considered auto. + +There is older, functional notation as well, see the API docs for information on this format, though using the +functional notation is not preferred. + +== Calling a proc == + +Once a proc is defined, it can be called such as this: + +<%CODE| +_asdf(10, 'a different string'); +_asdf(50); +%> + +In general, these work the same as native function calls, and have more or less the same semantics, with a few +differences related to scope (native functions are globally accessible, while procs are not necessarily). + +There are alternative ways of calling a proc, for instance the {{function|call_proc}} and {{function|call_proc_array}} +functions, as well as first class proc references (see below), but these are not the usual way of calling them. + +== Scope within procs == + +Within a proc, only variables that were defined in the argument list (plus the @arguments argument) are available. +All other values are not in scope. + +You can share data generally, with for instance {{function|import}} and {{function|export}}, but that requires +specific action to get a value. {{function|closure}} is a proc-like data structure which gets a copy of all currently +in scope values, which might be a good alternative to procs. + +== Scope of procs == + +In general, a proc is only available to be called after it is defined, but once defined, they are available from +within and without any variable scope. The only exception to this is procs defined within other procs, which leave +scope at the end of the proc, and work like "private procs" in a way. + +Conditional procs are possible, but their behavior is somewhat intentionally poorly defined. Rather than conditionally +defining a proc, consider other options, such as {{function|iclosure}} (isolated closure) which has the same internal +scoping rules as procs, but have variable scope, and can be defined with the same scope as other variables. + +Conditional procs can accidentally (or intentionally) occur when using the {{function|include}} function with a +non-hardcoded path. See the discussion below for dynamic procs. The solutions there can perhaps be useful in other +conditions where you think you might need a conditional proc definition. + +In general, the intention is for procs to be considered "global" and immutable, though in practice this is possible +to circumvent, though this is fragile and intentionally poorly defined, as changes may occur in the future that cause +patterns outside these conventions to break. + +=== Proc redefinition === + +A subset of conditional procs are proc redefinitions. Consider the following code: + +<%CODE| +proc _asdf() { + msg('first'); +} + +proc _asdf() { + msg('second'); +} + +_asdf(); +%> + +The output of this code will be "second", because the proc was redefined internally with the new declaration. This is +a side effect, and not necessarily intended behavior, though it is in general an officially sanctioned pattern, that +is only subject to minor changes in the future, with one large caveat: The return type and argument count/types should +remain the same from one declaration to the next, and only the internal implementation should vary. + +If you follow these rules, you may safely do proc redefinitions, and only have to make minor changes in the future. + +If the signature of the proc changes, that is, the return type or argument count/types changes, this will cause compile +errors in future versions. + +== Arguments and return type == + +It is optional though highly recommended (and required in future versions of strict mode) that the return type and +parameter types are specified. If missing, the return type is considered to be auto, as are argument types. + +<%CODE| +// Implied auto +proc _asdf1(@a, @b, @c) { + ... +} + +// Explicit version of the above +auto proc _asdf2(auto @a, auto @b, auto @c) { + ... +} + +// Specific types, strongly typed, so that a call such as `string @s = _asdf(4.5, array(), 'test');` would cause +// a compile error (when typechecking is enabled). +int proc _asdf3(int @a, string @b, array @c) { + ... +} +%> + +=== @arguments === + +The @arguments variable is a special, predefined value, which is available in every procedure. It is defined as + +<%PRE|array%> + +And contains all values passed to the proc, in order. + +This value should not generally be relied on, as it may cause compiler errors in calling code in the future. Instead, +proper support for variadic arguments will be provided, though it will continue to exist for within the proc for other +meta purposes. + +== First class references to procs == + +In general, a first class reference to a proc can be obtained by using the {{function|get_proc}} keyword, or preferrably, +using the keyword. The returned value is a ms.lang.Procedure object, which implements the +ms.lang.Callable type, and can be assigned to variables and generally passed around (including escaping +the general boundaries for private procs). Since closures also implement Callable, this means that references to the +two types can be mixed together. + +<%CODE| +proc _asdf() { + msg('Inside proc'); +} + +closure @closure = closure() { + msg('Inside closure'); +}; + +// "proc _asdf" returns a typesafe reference to the proc instance, and refers to whatever +// proc reference was in scope at the location it is called. +foreach(Callable @callable in array(proc _asdf, @closure)) { + @callable(); +} +%> + +This will print "Inside proc" and "Inside closure". + +== Proc Docs == + +Procs can use a special type of comment, called a smart comment, to add useful human readable information about the +procedure, its parameters, and return type, which are the available to be read directly when looking at the definition, +or when using the official MethodScriptVSC extension in Visual Studio Code, when hovering over references to the +proc. The following format is supported: + +<%CODE| +/** + * This is the body, where the main description is. + * @param a A description of the parameter a + * @param b A description of the parameter b + * @returns A description explaining what the proc returns + * @seeAlso https://url.com A clickable link to a page that perhaps contains more details + */ +int proc _asdf(int @a, int @b) { + ... +} +%> + +Using this feature is a handy and easy way to leave easy to access notes for future readers. + +For more details on the general format of smart comments see [[SmartComments|this article]]. + +== Dynamic procs == + +In general, procs are required to be defined before they are used, and they are additionally required to be known +during compilation, so they can be typechecked, even if they are fully auto. This leads to a problem when using dynamic +includes, for instance include(@pathToFile); and then calling a proc that is defined in +whatever file may or may not be included in that file. The typechecker does not allow these references in general, +and it will cause a compile error, preventing your code from execution. There are, however, good reasons for wanting +to have "dynamic includes", but in general, doing so makes your code less safe, and so isn't allowed by default. There +are 3 different ways around this though, going in order of preferred to least preferred: + +=== Forward declarations === + +A forward declaration is a declaration of the signature of the proc, without the implementation. This effectively tells +the compiler that at the point that this procedure is called, it will be defined, and it will have this signature. This +allows code to still be fully typechecked, but can conditionally or lazily include the actual proc definition, possibly +depending on runtime factors. To forward declare a proc, simply leave off the function body: + +<%CODE| +array proc _asdf(int @a, string @b); +%> + +This will cause the typechecker to register a declaration with the given types, and this is the signature that future +calls will be typechecked against. Missing procs at runtime will be a runtime error, however. + +Note, if the procedure is actually unconditionally implemented, a forward declaration is not necessary, as the proc +implementation also serves to declare the proc's signature. + +<%CODE| +array proc _asdf(int @a, string @b); + +// Different versions of _asdf are defined in test.ms and prod.ms, though they both have the same signature +string @path; +if(@test) { + @path = 'test.ms'; +} else { + @path = 'prod.ms'; +} + +include(@path); + +_asdf(10, 'string'); // This reference is fully typechecked, and a compile error will be issued if it does not match. +%> + +For now, forward declarations are only part of the typechecking framework, but in the future, it will cause a runtime +error at include site if an implementation of a forward declared method has the wrong signature, which will make tracing +back exceptions to the actual problem easier. For now, simply be careful to ensure that when implementing a forward +declaration that you implement the same signature.9 + +<%NOTE|To introduce this feature, backwards compatibility is broken, but only in obscure cases. +In previous versions, the last argument was always taken to be the body of the proc. However, +now it is considered a forward declaration if the last parameter is simply a parameter, and not +executable code. The impact of this change is expected to be minimal, since the code would have +been useless to begin with.%> + +=== @{DynamicProc} annotation === + +You can turn off typechecking for specific and single calls to dynamic procs, with the @{DynamicProc} annotation. + +<%CODE| +// Different versions of _asdf are defined in test.ms and prod.ms, though they both have the same signature. +// However, we are not forward declaring the signature. +if(@test) { + include('test.ms'); +} else { + include('prod.ms'); +} + +@{DynamicProc} +_asdf(10, 'string'); // No compile error +@{DynamicProc} +_asdf('string', 10); // No compile error, though there is a runtime exception + +_asdf(10, 'string'); // Compile error, could not find _asdf +%> + +This simply causes typechecking to be disabled for the single call to the proc, other calls to the same proc +will be typechecked if they also do not contain the annotation. + +=== File options === + +Missing declaration errors can also simply be turned off for a whole file, using the allDynamicProcs file option. + +<%CODE| + + +_asdf(10, 'string'); // No compile error + +void proc _fdsa() { + ... +} + +_fdsa(10, 'test'); // Compile error, known procs are still typechecked. +%> + +In general, it is preferred that procs are all statically linked, that is, they are defined once and only once, and +are included with hardcoded and non-conditional include references. In the future, the class library will be the +preferred and fully supported way of doing dynamic linking, through interfaces. \ No newline at end of file diff --git a/src/main/resources/docs/Profiler b/src/main/resources/docs/Profiler new file mode 100644 index 0000000000..316159c2f3 --- /dev/null +++ b/src/main/resources/docs/Profiler @@ -0,0 +1,61 @@ +The profiler is a built in mechanism to assist you in debugging troublesome scripts that take longer than expected. In +general, CH is designed to be fast; performance is a key concern, but being that you have very fine control over its +operation, it is always possible to make a poorly designed script that takes too long to complete. Various mechanisms +exist to help you fix a script that takes too long (namely the [[Execution_Queue|Execution Queue]]) but +''identifying'' a laggy script can be a challenge. This is where the profiler comes in. + +== Usage == + +The profiler is controlled with the profiler.config file, which is created in the CommandHelper directory. There are a +few settings of interest, each documented in the file. To turn profiling on, set the "profiler-on" switch to true. Note +that profiling can introduce up to a one millisecond lag '''per triggered profile point''', (untriggered profile points +take about .001ms) so turning profiling on and leaving it on during normal server operation is not recommended. + +Setting the granularity to a higher setting will cause more profile points to be triggered, so if debugging in a live +scenario, it is best to turn the granularity to a lower number. Typically, 1 is sufficient to identify general slow +points in your script, which can then be moved to a test server, with a high granularity. + + +== MethodScript Inefficiencies == +It is possible that you identify slow spots in MethodScript itself. That is great! Setting the granularity to 5 will +spit out how long each and every native function takes, and if you find that there are functions that take exceedingly +long, then file a bug report, and hopefully performance can be improved. + + +== What types of information is profiled? == +The profiler shows different information based on your granularity, but this chart shows the general types of +information shown. As you go to more verbose, more information is shown, as well as the lower levels' information. + +{| +|- +| 1 - Only high level information is shown about how long aliases, events, execution queue, and \ +set_timeout/set_interval tasks took. Compilation of all MethodScript files is logged. +|- +| 2 - Loop times are shown; for, foreach, while, and dowhile. The parameters passed to the loops will be displayed as \ +well, so you can differentiate between various loop sizes. Compilation of MethodScript files are individually logged. +|- +| 3 - Procedure execution times are shown. The parameters passed to the procedure will be displayed as well, so you \ +can differentiate between various procedure executions. +|- +| 4 - File IO times are shown. Sometimes, file IO will appear out of sequence, because IO is sometimes asynchronous, \ +however, the times will still be recorded. The following functions run times will be shown: read(), get_values(), \ +get_value(), store_value(), clear_value(), has_value(). +|- +| 5 - Every single function is individually profiled. The parameters passed to each function will be shown as well. \ +WARNING: This is extremely CPU intensive, and should only be used on a test server. Note that execution times may \ +appear out of order when using asynchronous tasks, such as set_timeout, etc. This is normal. Compilation times are \ +also logged, per file. +|} + +== Java Garbage Collection == +Sometimes, the java garbage collector introduces slowness, which can't be controlled at all. The profiler can't do \ +anything about this, but it will tell you when the GC was run on the individual profile points, which will allow you \ +to adjust your times accordingly. This will typically invalidate that set of results however, so it is good to know \ +what results are considered "calibrated". The Garbage Collector tends to add at LEAST 5 ms, though this will vary \ +greatly from machine to machine. + +== Comparing Results == +Results can't really be compared with each other, unless it came from the same server under similar loads. The results \ +are only meaningful when compared against benchmarks, which can be set by running a few simple commands that do almost \ +nothing, and comparing against that, or comparing against two different scripts run at the same time. + diff --git a/src/main/resources/docs/Profiles b/src/main/resources/docs/Profiles new file mode 100644 index 0000000000..b1d1df5e51 --- /dev/null +++ b/src/main/resources/docs/Profiles @@ -0,0 +1,23 @@ +Profiles allow you to keep sensitive credential information out of your code directly. Various methods +may ask for a "profile" instead of or in addition to an associative array with various configuration options. + +In general, it is best to take advantage of this, and ensure that your profiles.xml file is never exposed +or otherwise committed to a public repository. Meanwhile, your code may be freely shared, and the various +profiles can be re-created by other users, using their own connection information, without having to edit +your code directly. + +The profiles.xml file is located in the prefs/ folder, and has at minimum, a root +profiles tag. Different functions will require different types of profile information, +but in general, the bare minimum profile will look like this: + +%%SYNTAX|xml| + + profileType + +%% + +The profileName is what is referenced in code. For instance, in the {{function|query}} function, +the first parameter may be either the profile name, or an array specifying the same information as +the profile xml. Different functions will require different extra information in the profile, some +may be optional, some required. See the individual functions for more information about the parameters +in particular. diff --git a/src/main/resources/docs/Regex b/src/main/resources/docs/Regex new file mode 100644 index 0000000000..4201ced396 --- /dev/null +++ b/src/main/resources/docs/Regex @@ -0,0 +1,7 @@ +MethodScript uses Java style regexes, so in general, any regex guides you find online for Java will apply to +MethodScript as well, with the obvious caveat that the actual functions used in Java will vary. Regex is a non-trivial +subject, and entire websites have been dedicated to it, so it is not in the scope of this wiki to cover it. For further +reading, [http://www.regular-expressions.info/ see this website]. A "test harness" script is also provided with the +[[CommandLineTools#examples|installable Local Packages]]. The package to install is +"RegexTestHarness". + diff --git a/src/main/resources/docs/Roadmap b/src/main/resources/docs/Roadmap new file mode 100644 index 0000000000..db36609700 --- /dev/null +++ b/src/main/resources/docs/Roadmap @@ -0,0 +1,66 @@ +This page serves as a general guide for the features I intend on implementing. Note that I reserve the right to modify, +rearrange, add, remove, eat, or delete this list or any parts of it at any given time, with or without notice to you or +your lawyers. :D However, more than likely, if there is a feature on this list, I plan to add it at some point, but it +may get shuffled around some in the process. Items shown in '''bold''' are major tasks, and their completion would +likely bump the major or minor version number (and will take longer), while other features would probably only bump +the supplemental version number, unless several were released at once. Sub bullets must wait on their parent task being +completed before they could be done. Adding more raw functions to allow more hooks into the game itself or functions to +simplify common programming tasks are continually being added. Also note that these tasks are not in any particular +order, though the main bullet points are roughly in order. + +* File system functionality (This will only happen after completion of a "virtual filesystem", to allow for strict \ +sandboxing of scripts) +* '''Low Level Packet Handling''' +* '''External Task Manager''' (This will likely go along with the Netbeans plugin) +* [[NewObjects|'''Object Oriented Design''']] +** '''MethodScript STL''' (Also depends on the debugger and multithreading. I'm not gonna start writing complex \ +MScript code without a good way to find errors in it.) +* '''Serialization of compiled scripts''' (That is, the ability to write out the compiled scripts to file system, \ +allowing the scripts to bypass compilation at startup. This would speed up startup time significantly, but this \ +feature must be done right, too many other build systems get this wrong, and it causes more problems than it's worth \ +sometimes) +* '''Mob Control''' +* '''Networking''' +** Raw sockets +*** '''Full blown debugger''' (VSC plugin is the end goal, though cmdline tools (gdb style) will be implemented \ +first, to secure the raw functionality. This depends on Networking, because the debugger must support remotes first, \ +which will also cover local debugging) +* '''Javadoc for Procedures''' (Smart comments. There now exists a class in the pure utilities that is capable of \ +parsing this into a usable format) +** Self creating documentation +*** '''Web Server that serves up the automatically created documentation''' (with options for limiting access) +* '''Framework to allow remote programs to connect to the server and execute scripts''' (with security built in) +* '''SSP version of CommandHelper''' + + +'''Long Term''' + +Eventually, it is the hope that MethodScript be made into a generic framework that allows for any application to easily +implement it's own functions that can be applied to whatever domain is being used. Many of the functions are completely +independent of Minecraft, and could be included in a "core" that is distributed, and each application can define their +own API. With the built in documentation features, it would be easy to expand (and keep the documentation up-to-date and +useful). + +''Why choose MethodScript over Javascript? Or PHP? Or AwesomeScript? Or ThatOtherScript?'' + +This is a [http://xkcd.com/927/ valid question]. What does MethodScript offer that other scripting languages don't? +Simplicity. This has several benefits, and drawbacks that we must consider. In general, MethodScript is much easier to +learn than other languages. In essence, everything is a function, including common control structures. This makes many +concepts easier to learn, and makes for easily embeddable scripts. This is demonstrated by MethodScripts being embedded +in the aliases.msa file. Each script is run separately from each other, and is embedded inside a simpler format, which +facilitates easier integration into existing paradigms. Secondly, as a scripting language, it is easy to transmit in +plain text, which makes it perfect for simple configuration and customization. Finally, it has many built in features +that simplify development, such as the include() function. With everything, there are drawbacks though. Since it is not +strongly typed, this makes it easier to use, but harder to maintain. It is not possible to easily refactor dynamic +elements. Though it will be pseudo object oriented, it is somewhat added on top, it wasn't initially designed with this +in mind. Though the design of the object oriented features should be properly designed, they are being designed inside +the existing constraints of the non-object oriented features. + +In addition, MethodScript borrows strong points from other languages, which in addition to making it easier to learn for +people who have exposure to other languages, also helps it to stand on the shoulders of giants, and is able to "start +from scratch" and get rid of features that aren't desirable with other languages, while not having to worry about losing +backwards compatibility. + +Finally, unlike JavaScript, MethodScript's documentation is a priority, and documentation is created from the +authoritative source. The website's goal is to provide comprehensive documentation and examples, which helps promote +understanding. diff --git a/src/main/resources/docs/Routines b/src/main/resources/docs/Routines index f305372dc3..0c45be5b8b 100644 --- a/src/main/resources/docs/Routines +++ b/src/main/resources/docs/Routines @@ -165,13 +165,13 @@ for(@i = 0, @i < @maxConsumers, @i++){ In this example, after the producer loops through the data, the lock is closed. At this point, any future calls to rwait (or rsignal, for that matter) will cause a LockClosedException -to be thrown. FIXME: I'm not sure about this feature ->All exception types except this will cause the normal error handling process +to be thrown. FIXME: I'm not sure about this feature ->All exception types except this will cause the normal error handling process to occur, but in this case, the routine knows to catch it and silently terminate, though -it can be caught and handled separately.<- +it can be caught and handled separately.<- This will cause the routines that are currently blocked to throw the exception, and future calls to rwait will immediately throw as well. This provides a convenient -"out" for the routines, if an indeterminant amount of data is being processed. +"out" for the routines, if an indeterminate amount of data is being processed. === Blocking calls === @@ -246,4 +246,4 @@ but this code isn't an atomic operation, so if the lock were closed between the to _is_lock_open() and rlock(), an exception would be thrown, which is not what we want in this case. If you can guarantee that either the lock will never be closed, or you want an exception to be thrown if the lock is closed, rlock() is an appropriate -choice. \ No newline at end of file +choice. diff --git a/src/main/resources/docs/SQL b/src/main/resources/docs/SQL index d325369630..f67a8e1858 100644 --- a/src/main/resources/docs/SQL +++ b/src/main/resources/docs/SQL @@ -12,19 +12,13 @@ To simplify connection information to various databases, MethodScript allows two ways of connecting to a server. Either via ''profiles'' or via in code connection information. When connecting statically, connection via profiles is the preferred solution, since it makes coding easier, and makes it harder to accidentally leak -database credentials when sharing code. To create a profile, create a file in the -profiles directory. The name of the file should be the profile name. -If any directories are in the profiles folder, they are ignored. The profile file -is an xml style file, which should contain the same information that is used during -the connection if you were using the query method that takes a connection object, -however, if the profile doesn't exist at script startup time, it will immediately -cause an error, instead of waiting until runtime, if possible. If the connection information -is incorrect, that is a runtime error. Connections to different SQL server types +database credentials when sharing code. See [[Profiles]] for more information +about setting up profiles in general. Connections to different SQL server types may require different connection information, so you'll need to see the connection -configuration information for each supported server type. In general, the configuration format +configuration information below for each supported server type. In general, the configuration format is as follows: -
+%%SYNTAX|xml|
 
 
 	
@@ -36,7 +30,7 @@ is as follows:
 		password
 	
 
-
+%% Essentially, the connection information is specified via xml tags per profile, and connections can be referenced by id. In the following tables, the Tag column @@ -70,10 +64,9 @@ specified. ! scope="col" width="20%" | Status |- | file -| The path to the sql file. If the path is relative, it is considered relative to this file, however +| The path to the sql file. If the path is relative, it is considered relative to this file, however \ absolute paths are recommended, to prevent ambiguity. | Required - |} @@ -108,6 +101,10 @@ on the MySQL website: http://www.mysql.com/ | port | The port you are connecting to. If not specified, 3306 is assumed. | Optional +|- +| useSSL +| If set: "useSSL=false". If value "true": "useSSL=true". Default: useSSL will not be an argument. +| Optional |} === PostgreSQL === @@ -147,6 +144,62 @@ on the PostgreSQL website: http://www.postgresql.org/ | Optional |} +=== MSSQL (SQL Server) === + +MSSQL requires a separate SQL Server to be running, either remotely or locally. +For more information about setting up SQL Server, look for more information +on the Microsoft website: https://www.microsoft.com/en-us/sql-server/sql-server-2019 + +The SQL Server must be configured to allow TCP/IP connections, which is disabled by default. The JDBC driver +cannot connect using Shared Memory, which is the only transport enabled by default. To enable TCP/IP connections, +open the "SQL Server Configuration Manager" which is installed along with SQL Server. Expand the "SQL Server +Network Configuration" tab on the left, and select the SQL Server instance you're trying to connect. Enable +the "TCP/IP" protocol. You need to then restart the SQL Server. Click on the "SQL Server Services" tab, and then right +click and restart on the server. + +{| width="100%" cellspacing="1" cellpadding="1" border="1" class="wikitable" +|- +! scope="col" width="20%" | Tag +! scope="col" width="60%" | Description +! scope="col" width="20%" | Status +|- +| database +| The name of the database you are connecting to +| Required +|- +| username +| The username you are connecting with. +| Optional +|- +| password +| The password you are connecting with. +| Optional +|- +| host +| The host you are connecting to. If not specified, "localhost" is assumed. If the database is in Azure, you \ +may instead use "azureHost", and simply provide the host name (.database.windows.net will be appended for you). +| Optional +|- +| instance +| The instance of SQL Server on the host. By default, empty. +| Optional +|- +| port +| The port you are connecting to. If not specified, 1433 is assumed. +| Optional +|} + +In addition to these specially handled parameters, any of the valid parameters listed +[https://docs.microsoft.com/en-us/sql/connect/jdbc/setting-the-connection-properties?view=sql-server-ver15 here] can +also be added, and they are simply passed on as is. + +Username and password are optional in on premise SQL Server on Windows (as opposed to databases in Azure, +where it is mandatory). Windows Authentication can be used instead, however, it requires extra server setup. To do so, +simply run the install-mssql-auth from the command line. + +Then, to use this authentication, simply add "integratedSecurity": true to your code, or +<integratedSecurity>true</integratedSecurity> to your profile. + == Making a query == All queries use a standardized form of SQL provided by Java, though vendor specific @@ -165,16 +218,15 @@ the connection information (either a profile name or a connection array) and the query itself (and any statement parameters). All queries use a "prepared statement" format, which ensures that SQL injections are not possible. -{{TakeNote|text=Though you can bypass prepared queries by doing concatenation, this is +%%NOTE|Though you can bypass prepared queries by doing concatenation, this is extremely bad practice, and will cause a warning to be issued. Query strings should be hardcoded and the prepared statement engine will insert the parameters automatically -and safely.}} +and safely.%% The query function returns an array (in the case of a select) or various other return types, depending on the SQL statement. For selects, an array of associative arrays with the results is returned. For inserts, null is returned, unless the statement used an auto-increment, in which -case, that value is returned. Deletes and updates return the number of rows affected. All -other operations return null. +case, that value is returned. Deletes and updates return the number of rows affected. All other operations return null. Some common examples follow, though the full SQL language is available. @@ -187,12 +239,12 @@ A simple SELECT: /* * The returned result would look something like this: * array( - * array(columnName: 'value1'), - * array(columnName: 'value2') + * array(columnName: 'value1'), + * array(columnName: 'value2') * ) */ foreach(@result, @row){ - msg(@row['columnName']) + msg(@row['columnName']) } %% @@ -204,8 +256,7 @@ if @id were a string, this would translate into the following code: query('profileName', 'SELECT * FROM `table` WHERE id=\''._escape_this_parameter(@id).'\'') %% -which as you can see could much more likely lead to errors or unsafe usage, and is much -harder to read. +which as you can see could much more likely lead to errors or unsafe usage, and is much harder to read. A more complex SELECT: @@ -221,17 +272,20 @@ This statement is used for inserting rows into your table. You can use this in t The returned result would be the first rownumber that was added (with auto increment) or null. %%CODE| -@result = query('profileName', 'INSERT INTO `table` ' - .'VALUES(?, ?, ?), (?, ?, ?)' - , 'CommandHelper', 'descCH', 9001, - 'WorldEdit', 'descWE', 8999 - ) - -@result = query('profileName', 'INSERT INTO `table`(?, ?) ' - .'VALUES(?, ?), (?, ?)' - , 'Plugin', 'Description', - 'WorldGuard', 'descWG', 'Craftbook', 'descCB' - ) +@result = query('profileName', + 'INSERT INTO `table` ' + .'VALUES(?, ?, ?), (?, ?, ?)', + 'CommandHelper', 'descCH', 9001, + 'WorldEdit', 'descWE', 8999 +) + +@result = query('profileName', + 'INSERT INTO `table`(?, ?) ' + .'VALUES(?, ?), (?, ?)', + 'Plugin', 'Description', + 'WorldGuard', 'descWG', + 'Craftbook', 'descCB' +) /* * Notice there are no column names in the first example, this means that these are not necessary. @@ -241,12 +295,13 @@ The returned result would be the first rownumber that was added (with auto incre * Going by this the first query is can be changed by this one. */ -@result = query('profileName', 'INSERT INTO `table`(?, ?, ?)' - .'VALUES(?, ?, ?), (?, ?, ?)' - , 'Plugin', 'Description', 'Downloads', - 'CommandHelper', 'descCH', 9001, - 'WorldEdit', 'descWE', 8999 - ) +@result = query('profileName', + 'INSERT INTO `table`(?, ?, ?)' + .'VALUES(?, ?, ?), (?, ?, ?)', + 'Plugin', 'Description', 'Downloads', + 'CommandHelper', 'descCH', 9001, + 'WorldEdit', 'descWE', 8999 +) %% @@ -281,19 +336,20 @@ This statement is used for updating existing rows in your table. For example, we may want to add 250 downloads to each plugin. %%CODE| -@result = query('profileName', 'UPDATE `table`' - .' SET Downloads=Downloads+250' - ) +@result = query('profileName', + 'UPDATE `table`' + .' SET Downloads=Downloads+250' +) /* * The UPDATE statement also allows for using conditions, as shown below. */ -@result = query('profileName', 'UPDATE `table`' - .' SET Downloads=Downloads+250' - .' WHERE Plugin=?' - , 'CommandHelper' - ) +@result = query('profileName', + 'UPDATE `table`' + .' SET Downloads=Downloads+250' + .' WHERE Plugin=?', 'CommandHelper' +) %% === DELETE === diff --git a/src/main/resources/docs/Server_Profiler-Debugger b/src/main/resources/docs/Server_Profiler-Debugger new file mode 100644 index 0000000000..7d77d46c5c --- /dev/null +++ b/src/main/resources/docs/Server_Profiler-Debugger @@ -0,0 +1,163 @@ + +CommandHelper comes bundled with a server wide event debugger and profiler. In fact, this can be used by itself, even if +you don't use the rest of the plugin at all. To explain this feature fully, it should first be explained what a profiler +and debugger actually are, a bit about Bukkit itself, and who the intended users of this feature are. + +==What is a profiler?== +A profiler allows you to get statistics about the performance of a particular portion of software. For instance, let's +say you write some software, and it writes out information to a file. In general, to see how well this performs, you use +this pseudo code: + +<%SYNTAX|java| +int start = System.currentTimeInMillis(); //Get the current time, as a Unix timestamp. This is our "start time" +myFunctionThatWritesOutAFile(); //Call the function we are wishing to profile +int timeToRun = System.currentTimeInMillis() - start; //This is now how long it took our function to run +%> + +Ok, so now we know how long it took to run. We could echo this out or log it to a file, or do any number of things with +this information. But now, what if there were other factors involved? What if maybe sometimes we ended up writing a +large file out, or maybe we're trying to test it out on different computers, and some of them are slower than others? +Maybe we want to get an average case for this. In order to calculate an average, we have to get several runs of the +function to test it. This is where perf4j comes in. Perf4j is a framework that allows for easy calculations of +statistical information about a process. We simply have to log the information we are interested in to file, and perf4j +converts it into an easy to digest summary of the data. We will discuss this more below, when we talk about how to +actually activate the profiler in CommandHelper. + +==What is a debugger?== +In general, a debugger is a program that hooks into another program, and allows us to see information about that program +at a given snapshot in time. More complex debuggers will allow us to pause program execution, and dynamically examine +parts of the program, but in general, the simplest debugger is a print statement. If we were writing a program, and we +wanted to see what the following value was: + +<%SYNTAX|java| +String fileContents = SomeClass.ReadInFile("/path/to/file.txt"); //Read in a file +%> + +then the simplest thing we can do is: + +<%SYNTAX|java| +System.out.println(fileContents); //Print the file contents to the screen +%> + +This is a simplistic form of debugging. How CommandHelper fits in with all of this is discussed below. + +==Who is this feature meant for?== +===Profiler=== +The profiler is meant for anyone that wants to dig down further and analyze the performance of their server. Let's say +that you've recently installed several plugins, and now your server performance has slowed to a crawl. Instead of +uninstalling these plugins one at a time, we can log performance, and quickly determine which plugin is the problem. + +Also, developers of other plugins can take advantage of this, by installing CommandHelper next to their plugin, and +having CommandHelper log performance of their plugin. This will allow them to figure out what parts of the plugin need +to be focused on for improvement. + +===Debugger=== +The debugger is designed to help server owners diagnose problems for event related issues. Say that we have two plugins +that aren't behaving well together. We can turn on the debugger, target that particular event, and print out information +about what plugins did what, and in what order as well. + +In addition to server owners, the debugger can provide more information for plugin authors. It's decently difficult to +set up a proper debugging environment for your plugin, so instead, it may be easy enough to install CommandHelper, and +have it log information about events as your plugin is running. + +==A word on Bukkit and Events== +Ok, so now we know what a Profiler and Debugger are, but what exactly can be done with CommandHelper specifically? +CommandHelper has a play-dirty mode already. This mode allows for CommandHelper to hook deep in to Bukkit's event +system. By default, play-dirty mode is off, because I break many rules to get it to work properly. Essentially what +happens is that CommandHelper ''injects'' it's own event firing system into Bukkit, essentially replacing Bukkit's own +system. With play-dirty mode, all events actually fire off through CommandHelper. The original purpose of this was to be +able to fully manage commands. Since CommandHelper doesn't do anything initially when you install it, any action taken +by the plugin is a direct command from the server administrator. Because of this, I take certain liberties to overwrite +and manipulate other plugin's behavior, which would normally be unacceptable. + +With this framework in place however, it is very easy for us to see and respond to any event on the server, even events +that are being delivered to other plugins. So, for the profiler, we simply start a timer right before we fire the event, +and stop it right after it finishes. For the debugger, we spit out information about information in the event, which +plugin it's going to, and what listener is responding to it. With the addition of the perf4j framework, we can also then +aggregate performance data. + +==Usage== +Both the performance logger and debugger can be enabled on-the-fly, through standard CommandHelper functions. These +functions are both restricted by default, and can be completely disabled (and are by default) in the preferences file. +Set allow-debug-logging to true and allow-profiling to true to enable the respective functions. To properly use the +profiler, the debugger must also be enabled. To enable the entire operation to occur, CommandHelper must be allowed to +inject into the event system, so play-dirty mode must be on as well. There are also preferences to establish where data +is logged to, the debug-log-file, and profiling-file settings. These both support date and timestamp variables, so you +can automatically segment your log files by time. +===Debugger=== +The debugger has several levels of verbosity. The lower the verbosity level, the less information is displayed. This is +a double edged sword. On one hand, less information is easier to understand, but may not contain the information you +actually want. On the other hand, too much information can cause data blindness, the information we want is there, but +it's buried deep within noise, and so isn't useful. This is why there are three mechanisms for focusing the information +that is displayed. Verbosity, event filters, and plugin filters. + +The main function to enable logging is the debug_log_events function. + +<%CODE| +debug_log_events(toEnable, [level, [logToScreen]]) +%> + +toEnable is a boolean, and turns debugging on or off. level is an integer from 1-5, and sets the verbosity level of the +output. logToScreen is a boolean, which defaults to false. If set to true, in addition to logging to a file, it will log +to the server console. This can be useful for plugin developers, but if you are running a server, then logging to +console will cause the same information to log to to two places. If you a server owner, it is better to use the tail -f +command on a unix machine to view the debug data live. + +This will enable the debugger, but we haven't set any filters yet. Output won't actually occur until we set filters +though, because the event filter is a whitelist filter. + +<%CODE| +set_debug_event_filter(arrayOfEventsToDisplay) +%> + +This takes an array of the event types we are interested in. Calling this function will replace the old filter. The list +of all the valid event types can be displayed by calling dump_listeners(null), which will display all the event types. +Provide an array of event types you are interested, and they will be logged. If you '''really''' are interested in every +single event, you can call set_debug_event_filter('*'), which will add all the events. + +In addition, you can add filters to narrow which plugins you are interested in with the set_debug_plugin_filter. This is +a semi-whitelist. If the list is empty, all plugins are logged, but if you add 1+ plugins to the list, only those +plugins are shown. The name of the plugin may not be it's commonly referred to name. To determine a plugin's name, you +can use the dump_listeners() function. + +<%CODE| +set_debug_plugin_filter(arrayOfEventsToDisplay) +%> + +===Profiler=== +The profiler is activated using the enable_performance_logging function. Events are filtered using the +set_debug_event_filter and set_debug_plugin_filter, though debugging and performance logging can be activated separately +from each other. + +If an event is logged, it is written out to the performance logging file, and then CH itself is done. So how do we get +performance data from this? First, you must download the +[http://repository.codehaus.org/org/perf4j/perf4j/0.9.16/perf4j-0.9.16.jar perf4j library]. Place this file in the +folder that your logs are being created in, then open up a terminal, and run java -jar perf4j.jar --help. +This will display all the actions you can do with perf4j. Once of the more useful features is to create an aggregate +summary of all the data. java -jar perf4j.jar nameOfLogFile.log -o aggregate.csv -f csv will create a csv +file that can be opened in excel. There are other options as well. (I have not quite yet figured out how to generate +graphs.) + +==Example Scripts== +These tools can be controlled on the fly from within a script. Here are example scripts that will start and stop the +profiler/debugger, and can be modified to suit your needs. +<%ALIAS| +/dump = console(dump_listeners(player_command_preprocess, 5)) msg(concat(color(red), 'Dumped.')) + +/go = >>> + set_debug_event_filter('*') + debug_log_events(true, 1, true) + set_debug_plugin_filter(array()) + enable_performance_logging(true) + msg(concat(color(gold), 'Performance logging started')) +<<< + +/halt = >>> + debug_log_events(false, 1) + enable_performance_logging(false) + msg(concat(color(gold), 'Performance logging stopped')) +<<< +%> + +{{LearningTrail}} + diff --git a/src/main/resources/docs/SiteDeployTool b/src/main/resources/docs/SiteDeployTool new file mode 100644 index 0000000000..48da55ec91 --- /dev/null +++ b/src/main/resources/docs/SiteDeployTool @@ -0,0 +1,152 @@ +This page explains the templating system, and how it works. There are a number of systems that +interact together to produce these web pages, and if you wish to contribute, it is helpful to understand +how they work together. There are 3 main systems to be aware of. Initial template replacement, wiki replacement, +and page rendering. + +== Initial template replacement and deployment == + +There are three processing stages. First, when the site is deployed, there is some template replacement done then. +The java uses <% and %> or for simple templates where arguments need not +also be parsed, just %% and %%. These allow the static pages to replace +information in them at deploy time, using the jar itself to do the templating work. This is useful for pages that +need information from the codebase, or it would in general be easier to parse the template in java instead of javascript. +The template replacement has the template name, and can also accept arguments, for instance +<%TEMPLATE_NAME%> and for templates with arguments: +<%TEMPLATE_NAME|argument%> where TEMPLATE_NAME is the name of the template, and arguments +are pipe separated. If the arguments have nested templates, they must use <% %>, the double percent +variety cannot be nested. Depending on where exactly the page contents were generated from, the templates that are +available may vary, and you'll need to trace the code for that specific page to see what templates are available. By and +large however, all templates defined in DocGenTemplates.java are available everywhere. + +There are a few notable templates worth discussing. + +* CODE|code - This syntax highlights pure mscript code. +* ALIAS|code - This syntax highlights mscript code that is defined in an msa file +* PRE|text - Creates a <pre> code block, escaping all html special characters in the text +* SYNTAX|type|code - Given the language or code type, will apply the proper html to make sure the text \ +is syntax highlighted (for languages other than mscript) +* NOWIKI|text - Escapes the text, ensuring that no wiki markup will be processed. + +There are several others worth looking at, see the DocGenTemplate.java source code for each. + +After the templates are replaced, for pages, the file is inserted into the frame.html wrapper, which provides the +basic structure of the html document. + +Different pages are generated from different sources, and may follow slightly different deployment rules, to see +the specific rules that are followed, see the SiteDeploy.java file. Note that several file types are automatically +scanned for, and their mere existence in the resources is enough to cause them to be processed. Other types of files +are manually listed, and may be handled differently. Consult the SiteDeploy file to see how each page is exactly generated. + +Once the page is deployed, for html pages, they are also validated, so make sure that the page content will validate. + +Several steps of the rendering stage are (currently) handled in javascript, including the wiki parsing, and syntax +highlighting for non-mscript code blocks. + +== Client side rendering == + +The client renders the wiki formatting, as well as the syntax highlighting for non-mscript code blocks. Most simple +wiki markup is supported, including tables. See the [https://en.wikipedia.org/wiki/Help:Cheatsheet page here] for a +cheat sheet for this markup. Wiki templates (double left curly brace/double right curly brace) do not work, except for +a very limited subset, including function, object, keyword, and TakeNote. Raw HTML is also supported, including script +tags, however, html should be avoided whenever possible. + +If a page needs to run javascript, it can, by way of normal script tags. Do be aware that the typical jquery onload +mechanism does not work, because there is page rendering that needs to happen first. In order to properly hook into +the code after the page really is finished loading, a special promise object is defined. All your code should go in +a block that looks like this: + +%%SYNTAX|html| + +%% + +== Testing Locally == + +When making changes to the documentation, it may be worth running locally to see your changes before they go to the +live site. + +There are multiple approaches here, this just provides one approach. The key is that you need a local web server +running, it's not sufficient to simply open the raw html files in your browser. These directions are the minimal +instructions, but you may use an existing server for viewing, or modify this approach to suit your needs. + +Following these directions will help you view the site locally: + +=== One time setup === + +[https://docs.npmjs.com/downloading-and-installing-node-js-and-npm Install node and npm.] + +Create a configuration file somewhere on your file system, call it site-deploy-local.ini + +<%SYNTAX|ini| +# This file is generated automatically. Changes made to the values of this file +# will persist, but changes to comments will not. + +# The root location of the remote web server. This must be an absolute path. Note that this is the location of the *docs* +# folder. Files may be created one directory above this folder, in this folder, and in lower folders that are created by +# the site deploy tool. So if /var/www is your web root, then you should put /var/www/docs here. It will create an index +# file in /var/www, as well as in /var/www/docs, but the majority of files will be put in /var/www/docs/3.3.4. If you +# are on Windows, use / as the directory separator, not \. +directory=REPLACEME + +# The base url of the docs. This should begin with http:// or https:// +docs-base=http://127.0.0.1:8080/docs/ + +# The hostname to scp to (not the hostname of the site). If the hostname is "localhost", or "127.0.0.1" +# this triggers special handling, +# which skips the upload, and simply saves to the specified location on local disk. This should work with all OSes, +# otherwise the host that this connects to must support ssh, though it does not necessarily have to be a unix based +# system. If the value is localhost, the values username, port, and use-password are irrelevant, and not used. This +# should NOT begin with a protocol, i.e. http:// +hostname=127.0.0.1 + + +# The base url of the site (where "home" should be). This should begin with http:// or https:// +site-base=http://127.0.0.1:8080/ + +use-password=false +username= +validator-url= +post-script=null +port=22 +install-url= +install-pub-keys= +install-pem-file= +show-template-credit=false +github-base-url= + + +%> + +Change the ''directory'' value to wherever you intend on installing this locally. Make sure this directory path +ends in ''/docs''. + +Next, we need to do the initial generation: + +<%PRE|java -jar MethodScript.jar site-deploy -c site-deploy-local.ini%> + +On the first run, this may take a while, so just sit back and relax. + +=== Each dev session === + +From the command prompt, ''cd'' to one folder above where you specified in the ''directory'' value in the config (that +is, the root of the website) and run + +<%PRE|npx http-server -p 8080 -c-1 -o "index.html"%> + +Alternatively, you can install the http-server package, and leave off the ''npx'' command. + +Leave this command window open while you develop. You may also wish to disable the cache in your web browser, so that +old versions of the pages won't be cached. + +=== After making a change === + +Rebuild the project normally, using maven. (Note that it seems that maven doesn't always pick up the changes to resource +files, so you may need to do clean and build.) Then, from the cmdline, run + +<%PRE|java -jar MethodScript.jar site-deploy -c site-deploy-local.ini --use-local-cache%> + +Adding the ''--use-local-cache'' flag will potentially speed up the deployment. Refresh the webpage that was launched +by the http-server command. diff --git a/src/main/resources/docs/SmartComments b/src/main/resources/docs/SmartComments new file mode 100644 index 0000000000..37f6ff90c2 --- /dev/null +++ b/src/main/resources/docs/SmartComments @@ -0,0 +1,126 @@ +A Smart Comment is a comment field that contains somewhat structured data that can be used +both by your code and by the compiler, as well as being in an easy to read format, so that +readers of your code can get additional context. + +In general, MethodScript supports four comment types, though two do the same thing. +Both # and // are line comments, and are completely removed from the source code during +compilation. The comment begins with one of those symbols, and ends with the start of a new line. +Comments that start with /* and end with */ are block comments, and can contain newlines within +them, though not currently other nested comment blocks. Like line comments, these are also +removed during compilation. Both of these comments are meant solely as context for the future +reader of the code, and have no functional purpose as far as the compiler is concerned, and +are thus discarded as part of the compilation process, and so the runtime has no knowledge that +these types of comments ever existed in the source code. + +Comment blocks that start with /** and end with */ however, are smart comments, and the subject of this article. +These comments are not lost with compilation, and are able to be used in later stages of compilation, +to provide additional meta functionality to code. + +<%NOTE|Note that this page outlines some expected functionality, rather than functionality that currently exists. +Where this is true, it is noted, but the format of comments can go ahead and begin using these conventions, +and as they become implemented, they will suddenly start working in your code. Thus, the standard is set in stone +now, and can go ahead and be counted on.%> + +Given the following example, we can see the general format. + +== General Format == + +<%CODE| +/** + * This is the body. + * This is part of the body, and contains an {@embedded annotation}. + * - This is the first item in a list + * - This is the second item in a list. + * @standaloneAnnotation with comments + * @standaloneAnnotation can repeat + * @standaloneAnnotationWithNoComment + */ +%> + +There are a few things to point out. The general rule is that the comment block must start with /** and end with */. +On each line within the comment, the line may start with 0 or more spaces, a star, and then a space. This text is +stripped from the comment. The body text is the first part of the comment. Within the body, newlines and spaces are +preserved, other that the initial alignment spacing, as described above. Both body text and standalone annotation +comments may contain embedded annotations, which are the annnotations that begin with {@ and end with }. Embedded +annotations may also have additional text following the annotation name. + +Standalone annotations are those that start with @. These are ended by a newline, and cannot contain newlines within +them. Standalone annotations may repeat, or they may contain no additional text. + +The general body of the comment, as well as text based portions of annotations (where relevant) are rendered in +markdown where supported. + +== Usage in the compiler == + +The primary use of these smart comments is for providing formal documentation for the various commented elements. +By and large, the compiler itself ignores these values, though that's not always the case, depending on which +annotations are used. For instance, the @deprecated annotation will trigger a compiler warning when references to +the element are used elsewhere in code (not implemented yet). The primary use case is for generating automatic +documentation in various places, including the IDE (also not implemented yet). + +Depending on where the comment is placed, the rules may be slightly different as well, and particularly which +annotations will be active, so check the documentation below for specific details, as well as documentation about +the various elements that support smart comments. + +Smart comments that are placed on elements which do not directly support smart comments will not break - however +these will simply function as ordinary block comments, with the exception that the compiler may or may not retain +the comment in the runtime, unused. + +== Usage in code == + +The larger advantage is being able to dynamically access these comment blocks within your code. This can be used +to power an automatically generating documentation system for instance, along with easily generated help text to +be provided to users upon incorrect usage. Currently, support for obtaining these comments is rather limited, with +support only existing for aliases. Additional usage will appear over time. + + +== Embedded Annotations == + +Embedded annotations are generally speaking meant to function almost like visual text modifiers, such as adding links, +emphasis, and other formatting such as this. This isn't enforced however, and custom code can use these however they +like. However, there are a few predefined annotations. + +=== {@link} === + +The link annotation provides a link to another element in the code. + +This is currently unimplemented and undefined. + +=== {@url} === + +The url annotation provides an inline link to a web address. In general, the annotation should +follow the format {@url https://url.com replacementText} where the replacement text is the text that +is displayed, and the url is where the link goes. If replacementText is empty, the url is used as the +text. + +=== {@code} === + +This is meant for references to literal code elements, for instance variable names, etc. The common use case +is to simply format the text with a monospace font, but could be used for syntax highlighting, etc. + +== Annotations == + +Depending on the context, annotations can be ignored, or have different functionality. Ignored annotations are not +an error, they simply won't have any programmatic effects within MethodScript itself. You are free to use these +annotations in your custom systems in any way you see fit, though it is highly encouraged to ensure you are not +trying to fight the built in systems. Additional contexts may define additional annotations, with either server to +further document the element, or can even provide some functionality. + +=== @seeAlso === + +The general format is: + +@seeAlso https://url.com Description +or +@seeAlso element Description + +This provides a link to either another element in the codebase, or an external web url. Note that the element variety +of this is not implemented or defined yet, but will have the same semantics as the embedded {@link} annotation. + +=== @param === + +Documents a parameter. This is used for inputs to the element. The general format is +@param value description, where value is the name of the parameter without the at sign, and description +is free form. + + diff --git a/src/main/resources/docs/Statements b/src/main/resources/docs/Statements new file mode 100644 index 0000000000..a2edf5033d --- /dev/null +++ b/src/main/resources/docs/Statements @@ -0,0 +1,190 @@ +A ''statement'' is a single executable instruction, which has no "top level" return value (or the return +value is unused). In many cases, MethodScript can figure out what the end of a statement is, based solely +on the fact that individual function calls end with a right parenthesis. For instance, take this code: + +<%CODE| +msg('Random number: ' . rand()) +%> + +In this case, the end of the statement is the right most right parenthesis. The second to last right parenthesis +belongs to the rand function, and is not a statement, it is an argument to the msg function. While +MethodScript can tell that this is in fact the end of a statement, we can also write this to be +explicit, using the semicolon character. + +<%CODE| +msg('Random number: ' . rand()); +%> + +This unambiguously denotes the end of the statement. In this case, it wasn't strictly necessary, and so in +non-strict mode, is optional. However, there are some cases where the semicolon is required, and depending +on its presence or absence, causes different behavior. There are primarily three cases where this occurs: +prefix and postfix operators, callable execution, and keywords which have optional right hand types. + +=== Prefix and Postfix Operators === + +For instance, take the following code: + +<%CODE| +@a ++ @b +%> + +In this case, it's not clear if this code is meant to increment the @a value or the @b value. Because order of +operations are well defined, it will in fact actually increment @a, not @b, but it's not particularly obvious +just from reading the code. Further, if the code were @a ++@b a future reader may be forgiven if +they assume that @b will be incremented. This example is somewhat contrived, but consider a more practical example: + +<%CODE| +int @b = 0 +int @a = 0 +++@b +%> + +In this case, @a will actually be the one that is incremented, because the increment operator actually applies to +the 0 in the statement int @a = 0! Thus the practical result of this code is that @a is 1, and @b is +0, which is obviously not the desired behavior. + +We could wrap ++@b in parenthesis, such as (++@b) (though this has downsides, as discussed +below), or restructure the code, but ideally we would use +semicolons to denote the end of statement for each of these lines. This code performs as intended, that is, after +running, @a is 0, and @b is 1: + +<%CODE| +int @b = 0; +int @a = 0; +++@b; +%> + +=== Parenthesis and Callables === + +Another example is parenthesis. In general, closures (and other callables) are first class data structures, that is, +they can be stored in variables, arrays, and used just like any other data type. Execution of the closures can also +be accomplished much the same way as any other function execution, using parenthesis. + +<%CODE| +closure @c = closure(@arg) { } +@c(123) +%> + +This code stores the closure in @c, and then we execute the closure, sending it the argument 123. This code works +as intended, but consider that we combine it with our sub-par fix in the code from above: + +<%CODE| +int @b = 0 +auto @a = closure(@arg) { return(@arg) } +(++@b) +%> + +Now, the parenthesis surrounding ++@b will actually be taken to mean that we want to execute the value before it, +so we will execute the closure, and then store the returned value in @a, rather than storing the closure! + +Again, we can disambiguate using semicolons. + +<%CODE| +int @b = 0; +auto @a = closure(@arg) { return(@arg); }; +++@b; +%> + +=== Keywords === + +Finally, there are some keywords that have an optional associated value, for instance, the return keyword. Simply calling +return by itself causes the callable to return void, but we can also do for instance return 5 +to return the value 5 from the callable. However, consider a more real example. Say we have some code, and for test +purposes, we want to return early from the code. + +<%CODE| +closure() { + msg('Start') + int @a = 0 + return + // Dead code, temporarily left for testing + @a = 2 + // More code +} +%> + +Based on the formatting, you would expect this to return nothing, but in fact, it will return 2! Actually, the compiler +will build the code as if it were return(@a = 2). Instead, we should write this code as: + +<%CODE| +closure() { + msg('Start'); + int @a = 0; + return; + @a = 2; + // ... +} +%> + +=== Autoconcat === + +There is one other less obvious benefit to using semicolons that is specific to MethodScript in non-strict mode. In +general, MethodScript supports a concept called auto concatenation, that is, values that are next to each other, with +no separator of any sort, get automatically added to the sconcat function. The following two lines of code are +identical: + +<%CODE| +msg('Parts' 'of' 'the' 'string') +msg(sconcat('Parts', 'of', 'the', 'string')) +%> + +The problem is that in general, we cannot tell when it is intended to be autoconcatenated or not, and so the sconcat +wrapper has to be generated for ALL code that doesn't use statements. Further, the sconcat function is actually +run for every script, which causes an additional, often wasted runtime hit. +However, if all the code in the code base were to properly use semicolons, this runtime hit can be avoided, as +statements have special support to say that they do not have a return type, and so should not be added to the synthetic +sconcat function. + +=== Self-Statements === + +Some functions are considered "self-statements", that is, they are naturally terminating, cannot return a value, are +very common, usually with somewhat special keyword or compiler support, and therefore do not require semicolon. The +full list is below, but let's take one example, {{function|while}}. + +<%CODE| +while(true) { + msg('Hello, World!'); +} // No semicolon needed +%> + +This is true whether or not you use the functional notation, though using the curly braces in cases like this is +the preferred and modern format. + +There are a few functions, namely {{function|if}}, {{function|switch}}, and {{function|switch_ic}}, +which have special handling. These functions each have two forms, +the regular one, and the tertiary form. When using the tertiary form, they are not considered a self-statement, and must +be terminated with a semicolon (though if it is an argument to another function, which is the usual case for such a +usage, it does not require termination, though the containing statement would). In the normal form, when using it +as an if/else or switch statement, where each branch contains its own statements, they are also +considered a self-statement. + +The full list of functions that are unconditionally considered self-statements is: + +<%SELF_STATEMENT_FUNCTIONS%> + +=== Unexpected Statements === + +Statements are not always allowed. In general, statements cannot be sent as arguments to other functions, unless +those functions contain ''Branch Statements'', or are at the top level of the script. Consider the following code: + +<%CODE| +array @a = array( + _procCall();, + _procCall();, + _procCall();, +); +%> + +In non-strict mode, this will cause a compiler warning for each line where we have _procCall, because, while in other +contexts, you might need a semicolon behind the _procCall(), since we are using this as input to the array function, +we do not. In non-strict mode, the semicolon is simply ignored, and the value is used as if it were missing, though +a compiler warning is issued, and should be fixed regardless. In strict mode, this is a compiler error, and compilation +will halt. + +=== Strict Mode === + +In non-strict mode, semicolons are optional, though highly recommended anyways, for all the above reasons. In +strict mode, they are always required, even when the code isn't necessary ambiguous. The purpose for this is to +ensure that you consistently use them, to ensure that when it would be ambiguous, you are well trained to use +them in those cases, to ensure that the statements are not in fact ambiguous. Thus, in strict mode, if a statement +should have ended with a semicolon, but it didn't, a compile error is issued. \ No newline at end of file diff --git a/src/main/resources/docs/Strict_Mode b/src/main/resources/docs/Strict_Mode new file mode 100644 index 0000000000..4bf72ef718 --- /dev/null +++ b/src/main/resources/docs/Strict_Mode @@ -0,0 +1,109 @@ + +Strict mode places the compiler in an much more restrictive mode, where backwards compatibility is not as much of a +concern, and where things that would have been compiler warnings previously will be turned into compiler errors, and +generally causes the compiler to be more pedantic. + +{{Warning|text=Note, putting your files in strict mode may require more work when upgrading, as your scripts may no +longer compile. Future additions to strict mode will be implemented, and if your scripts do not conform, you will +have to upgrade them immediately.}} + +Conforming to strict mode has its advantages, however. Errors that would have gone unnoticed are highlighted much more +quickly. Strict mode enforces better programming practices and clearer and cleaner code, which the compiler has +the opportunity to do more optimizations on. Strict mode will be expanded +in the future, and those expansions will be listed below, so that you can begin to write conformant code, and it will +not break once strict mode expands to include those options. However, unlisted modes may be added in the future with no +notice, however, they will then be added to this documentation. + +One guarantee is that code that is written to conform to strict mode will always run the same in non-strict mode. + +Strict mode can be enabled per file with the appropriate [[File_Options|file option]] or globally with a config setting. +It can also be set as a runtime setting, but this will only benefit files that have not been compiled yet (includes, +eval'd code, etc). To enable this runtime setting, use: + +<%CODE| +set_runtime_setting('system.strict_mode.enabled', true); +%> + +== No Bare Strings == + +A bare string is a string without quotes. + +<%CODE| +string s = bareString; +string s = "notABareString"; +string s = 'alsoNotABareString'; +%> + +Using bare strings runs the risk that an identifier (such as a keyword) will be introduced in the future, which has +the same name as the bare string. In this case, the compiler will use the functionality of the identifier, rather +than the string, and your code could suddenly change without you knowing, causing hard to diagnose bugs. Thus, in strict +mode, bare strings are not allowed, you must quote all strings. + +== No Auto Concatenation == + +Auto concatenation is when two objects with no operator between them are taken to be concatenated, with a space added +between. + +<%CODE| +msg('this' 'is' 'concatenated'); // auto concatenation +msg('this' . ' is' . ' also' . ' concatenated'); // explicit concatenation +%> + +While less code to write, this has the unfortunate side effect that the whole script must be wrapped in a concatenation +block, which means that for each line of code, the concatenation effect must also run, even in cases where the +concatenation doesn't make sense. This adds overhead to the code for no extra value in 99% of cases, and so strict +mode disallows this feature, requiring explicit concatenation operators, at the benefit of decreased code runtime. + +Note that even in non-strict mode, 100% proper use of semicolons will cause the code to run in the performant mode, +however, missing a semicolon will not cause any sort of warning, and so is difficult to enforce. + +== Statement semicolons == + +Semicolons are used to denote end of statements. In a future strict mode update, they will be required, though +the specification for them is not fully complete. Where used, they currently do create statements, and this works in +non-strict mode as well. + +See the article on [[Statements]] for a fuller treatment of statements. + +Strict mode simply requires this on every statement, which can help ensure you as a programmer have the correct habit, +so that the possibly ambiguous cases are less likely to happen. + +Some languages solve this same problem with newlines, instead of semicolons. However, MethodScript has a design +principal which states that whitespace should never be used as code. + +== Object typing == + +(not implemented yet) + +In strict mode, all newly defined variables (and procedure definitions) must be typed. For instance: + +<%CODE| +string @s; +string @a = 'a'; + +int @i = 5; + +int proc _myproc(string @m) { + return 5; +} +%> + +Adding type safety to your code is a well documented advantage, as it allows the compiler to more easily and quickly +detect invalid code, as you cannot in the future assign a value of a different type to a variable that was defined with +another type. In non-strict mode, types are still allowed, and have the same behavior, but untyped values are assumed +to have the type ''auto''. This type is still allowed in strict mode, but the declaration must be explicit. + +<%CODE| +auto @x = 'string'; +@x = 5; +@x = array(); +%> + +Please see the page on [[Cross_Casting|cross casting]] for a further discussion on the auto keyword. + +== Compiler Warnings == + +Compiler warnings, such as deprecations and others are normally warned about, but compilation continues. In strict mode, +these warnings instead trigger a compile error, and must be fixed immediately. However, in combination with the +suppressWarnings file option, individual warnings can be ignored, and these will trigger neither a compiler warning or +a compiler error. \ No newline at end of file diff --git a/src/main/resources/docs/Strings b/src/main/resources/docs/Strings index f136cad86b..cb9d8c9abd 100644 --- a/src/main/resources/docs/Strings +++ b/src/main/resources/docs/Strings @@ -45,7 +45,7 @@ msg('Literal backslash: \\'); Both double quotes and single quotes may be escaped, though double quote characters aren't necessarily needed to be escaped. Both of the following lines are the same: -%%CODE| +%%PRE| msg('A \"string\"'); msg('A "string"'); %% @@ -54,7 +54,7 @@ An arbitrary unicode character may be inserted directly via the \u escape sequen be \u, followed by the four hex digit code for that symbol. Since UTF-8 is supported directly, however, you can add the symbol directly. All the following are equivalent: -%%CODE| +%%PRE| msg('\u2665'); msg('♥'); @value = 2665; @@ -72,6 +72,15 @@ Other escape sequences are supported: ! Escape Sequence ! Description |- +| \' +| Inserts a literal single quote character +|- +| \" +| Inserts a literal double quote character +|- +| \\  +| Inserts a literal backslash character +|- | \t | Insert a tab character |- @@ -80,6 +89,30 @@ Other escape sequences are supported: |- | \r | Insert a carriage return character +|- +| \@ +| (Only in double quoted strings) A literal @ symbol, for when this could be confused with a variable +|- +| \0 +| Inserts the null character +|- +| \uxxxx +| Inserts the specified UTF-8 character +|- +| \Uxxxxxxxx +| Inserts the specified UTF-16 character +|- +| \f +| Inserts a form feed character +|- +| \v +| Inserts a vertical tab character +|- +| \a +| Inserts an alarm (bell) character +|- +| \b +| Inserts a backspace character |} All other escapes are invalid, and will cause a compile error. diff --git a/src/main/resources/docs/Structs b/src/main/resources/docs/Structs new file mode 100644 index 0000000000..d7ff3a14df --- /dev/null +++ b/src/main/resources/docs/Structs @@ -0,0 +1,111 @@ +{{unimplemented}} + +A Struct is a special type of class, which allows for more precise associative array definitions, while not quite allowing for the full +power of objects. A struct may only be declared with public members, and no methods. Any associative array can be cross cast to a +struct, and vice versa. + +== Using a struct == +To create a new struct, you use it the same as if you were constructing a new object, +using new. All structs work as if they have exactly one no-arg constructor. +Assuming we have a struct named "Struct", then this code would create a new one: + +
+Struct @s = new Struct();
+
+ +This creates a new struct, with all the properties initialized to their defaults. +Additionally, since cross casting is available, the following works as well: + +
+Struct @s = array();
+
+ +Members in a struct are accessed the same as members in classes, with the -> operator. +Assuming our example struct has the int member @i, we can get and set it like this: + +
+Struct @s = new Struct();
+@s->i = 1;
+msg(@s->i); # Msgs 1
+
+# We can also set it directly via an array constructor
+Struct @s2 = array(i: 2);
+msg(@s2->i); # Msgs 2
+
+# Also, we can use the reflection/array access methods as well
+@s2['i'] = 3;
+msg(@s2['i']); # Msgs 3
+
+ +When using a struct, you gain the advantage of type safety in associative arrays, +assuming they aren't dynamic. Usually however, it may be a better idea to use full +Objects, so you can also add methods later. However, a configurable factory is a good use of structs +in combination with objects, which is demonstrated below. + +== Defining a struct == + +A struct is defined in exactly the same way as a class, except it may ONLY have a members block, and is +declared with the struct keyword. If we set the parameters with a value, that becomes their +default, which itself defaults to null. + +
+struct A {
+	members {
+		int @a = 1;
+		double @b = 2.5;
+		public string @c = 'String'; # Don't strictly need public here
+	}
+}
+
+ +Adding access modifiers is optional, though if specified, must be public. + +== Example == + +A good use of structs is when you have lots of configuration for an object. Instead +of using a constructor with lots of optional parameters, or having separate setter +methods for each field, you can use a configuration struct to simplify the configuration. + +
+class Car {
+
+	members {
+		private string @make;
+		private string @model;
+		private int @year;
+		private CarOptions @options;
+	}
+
+	public Car(string @make, string @model, int @year, CarOptions @options = array()){
+		this->make = @make;
+		this->model = @model;
+		this->year = @year;
+		this->options = @options[]; # Clone the array, so they can't change options on us later
+	}
+
+	struct CarOptions {
+		members {
+			boolean @GPS = false;
+			boolean @leatherSeats = false;
+			boolean @XMRadio = false;
+			Color @color = 0x000000;
+			int @rentalMonths = 1;
+		}
+	}
+}
+
+# Creating a new Car object:
+
+# This constructs a new car with the default optional options, but allows
+# for us to still specify the required parameters as part of the constructor
+Car @c1 = new Car('Make', 'Model', 2014);
+
+# This constructs a new car with only some of the options selected
+Car @c2 = new Car('Make', 'Model', 2014, array(color: 0xFFFFFF));
+
+# This would cause an error at compile time, since @rentalMonths is an int
+Car @c3 = new Car('Make', 'Model', 2014, array(rentalMonths: 'string'));
+
+ +If used properly, structs can work well in cases where a "named argument list" is +desirable. diff --git a/src/main/resources/docs/Style_Guide b/src/main/resources/docs/Style_Guide new file mode 100644 index 0000000000..e5fb3201e3 --- /dev/null +++ b/src/main/resources/docs/Style_Guide @@ -0,0 +1,241 @@ +Writing readable code is a huge part of easily debugging potential problems. While technically the compiler can read your +code no matter how it's formatted (so long as it is syntactically correct), the compiler isn't the only thing that needs +to read it, humans do too. So making your code "pretty" is a big step in making maintainable, debuggable, and shareable +code. Many code styles are equally readable, and so in many cases, it is pure preference how you choose to format your +code, however, this style guide is what will be used by any standard compliant code formatting tools that may be written +in the future, and so following these examples is good practice. + +== Whitespace == + +Technically, most all whitespace is optional. For technical reasons, it is possible to fit all scripts on one line, and no +tabs, spaces, or newlines are required by the lexer. However, this does not lend itself to human readability, so proper +whitespace is vital to making code easily readable. + +=== Tabs === + +Many text editors change tabs to spaces, and this is generally undesirable. The general rational behind this is that a tab +is generally more flexible in text editors, it is usually possible to define "tab width," which allows programmers to +customize this to their tastes, without actually having to change the code. + +=== Indentation === + +"Code branches", that is conditional blocks, should be indented one tab further than their parent, except in the case of +tertiary usage of an if(). For example: + +%%CODE| +if(@condition){ + trueCode(); +} else { + falseCode(); +} + +proc(_my_proc){ + if(someCondition()){ + return(1); + } else { + if(otherCondition()){ + return(2); + } else if(otherCondition2()){ + return(3); + } else { + return(4); + } + } +} +%% + +The exception is when if is being used as a tertiary statement (that is, the return value of if is not being ignored): + +%%CODE| +msg('You wrote ' . if($arg > 0, 'a positive number!', 'a negative number! (or zero!)')); +%% + +In general, code should only be nested up to a maximum of 5-7 levels, if you begin to nest deeper than this, consider breaking +code off into a procedure, and calling that procedure. Indentation is one of the most important metrics for making code readable, +and in general, poorly indented code will be far less readable, all other formatting issues aside. + +== Ending blocks == + +In cases where you are using multiple blocks, at some point, all the blocks must end. In this case, do not put all the +ending parenthesis on the same line, but instead, match the end parenthesis with the start of the block + +%%CODE| +// BAD: +@a = array( + array(1, 2, 3), + array(4, 5, 6)); + +if(@condition){ + code(); } + +msg(@a[ + someValue()]); + +// Good: +@a = array( + array(1, 2, 3), + array(4, 5, 6) +); + +if(@condition){ + code(); +} + +msg(@a[ + someValue() +]); +%% + +Using this syntax, should extra code need to be added to the end of the block, it is much easier to locate the +corresponding ending parenthesis/brace/bracket. + +== else, and else if == + +In the case of using if or ifelse, it is preferred that brace syntax is used, however, code that still uses the pure +functional approach should follow these guidelines: + +The code inside the blocks should be indented one more than the condition statements, but comma that represents the else +should be on a line of its own, and aligned with the parent if. Additionally, a # else comment is helpful. + +%%CODE| +if(@condition, + trueCode() +, // else + falseCode() +) +%% + +Ifelses should follow the same general guidelines, though + +%%CODE| +ifelse(@condition1, + condition1Code() +, @condition2, + condition2Code() +, // else + falseCode() +) +%% + +{{TakeNote|text=Brace syntax is much preferred in both of these cases}} + +== Switch == + +{{function|switch}} statements should use the following format: + +%%CODE| +switch(@value){ + case 1: + codeForCase1(); + case 2: + case 3: + codeForCase2(); + default: + codeForDefault(); +} +%% + +Note that there is one level of indention for each case, and two levels of indentation for the code +inside the case. The functional usage of switch should never be used, as it is exceedingly difficult +to read. + +== Naming Convention == + +Variables should be named using camel case. + +%%CODE| +@thisIsAVariable = null; // Good +@this_is_a_variable = null; // Bad +%% + +Global procedures should be named using snake case, however. + +%%CODE| +/** + * Good. + */ +proc _this_is_a_proc(){ + return(0); +} + +/** + * Bad. + */ +proc _thisIsAProc(){ + return(1); +} +%% + +This aligns with the function naming convention of MethodScript itself: use snake case for globals methods, and camel case +for instance methods. + + +== Brace Syntax == + +For a full discussion on brace syntax, please see this article: [[Brace_Syntax|Brace Syntax]]. Brace +syntax is preferred in all cases over functional syntax where possible, unless otherwise noted. (Most notably tertiary if +statements.) + +== Comments == +Generally, the double slash (//) is preferred to the number sign (#) for line comments, with the notable exception of +the hashbang at the top of the file, if present. A space should be added after the line comment operator, and before the +first line of the comment text (//Bad) (// Good), the exception being IDE hint comments (i.e. #region) + +Block comments that are informational (rather than commenting out code) should follow these conventions: use /* for non +documentation purposes, or for general comment blocks where the comment does not directly correspond with the next +element, for instance, if the comment applies to the whole block of code, or more generally within blocks of code, +rather than in element definitions. Comments that correspond with definitions of elements (classes, variables, procs, +etc) should start with /**, which indicates and enables documentation help for that element in IDEs and reflection. +These types of comments are known as "smart comments" and are treated specially by the compiler, and are not removed at +compile time (though the compiler does otherwise ignore them). In any case, when using either /* or /**, comment blocks +should line up the first star in a column. For blocks of code where the comment is not flush with the left side of the +document, tabs should be used to push the comment block out, and except in the case of the first line, should add a +single space before and after the star. A newline should be added after the begin comment operator, and before the end +comment. The smart comment parser will ignore the first star in a line, and all the beginning whitespace. + +<%CODE| +/** Bad comment */ +proc _badProcComment(){} + +/** +* Bad comment 2, the stars don't line up. +*/ +proc _badProcComment2(){} + +/** + * Good comment. + */ +proc _goodProcComment(){} +%> + +Divider line comments are generally undesirable (i.e. //----------------------------------------), instead use region +comments, which are hints to IDEs that support the feature, to provide a visual section for you. + +<%CODE| +// In IDEs that support this notation, this will create a section for you, and in the editor, should surround the region +// with divider lines. +#region procDefinitions +proc _a(){} +proc _b(){} +#endregion + +#region code +_a(); +_b(); +#endregion +%> + +The descriptions in a smart comment should end with a punctuation mark, and should be complete sentences. + +<%CODE| +/** + * This is a complete sentence, and is good practice for smart comments. + * @param a This describes what valid and invalid inputs are for this parameter. + * @return This describes what the return value of the procedure is. + * @throws IOException This describes in what cases the IOException will be thrown in. + * @throws IllegalArgumentException This describes in what cases the IllegalArgumentException is thrown in. + */ +int proc _p(string @a) throws IOException, IllegalArgumentException { + // ... +} +%> \ No newline at end of file diff --git a/src/main/resources/docs/SyntaxHighlighting b/src/main/resources/docs/SyntaxHighlighting new file mode 100644 index 0000000000..7eeaeaa2f0 --- /dev/null +++ b/src/main/resources/docs/SyntaxHighlighting @@ -0,0 +1,21 @@ +The MethodScript jar comes packaged with a syntax highlighter generator for several different text editors. +The syntax files can be updated with data from your specific jar, so it is guaranteed to be up to date. +To run the tool, run + +java -jar CommandHelper.jar syntax + +from the command line. You will see a list of supported text editors. Select your text editor, and run, for instance: + +java -jar CommandHelper.jar syntax npp obsidian + +This will print out the syntax file to the console. You can pipe the output to a file with: + +java -jar CommandHelper.jar syntax npp obsidian > syntaxFile.xml + +This will save the file in the current directory. Follow the procedure for your specific text editor to update the +syntax files using the freshly generated syntax file. (This varies depending on your text editor.) + +In general, it is recommended to simply use the MethodScriptVSC extension for Visual Studio Code, as that is the only +officially supported syntax highlighter. These self-generated syntax highlighters are supported on a community basis. + +See the page about the [[Development_Environment|MethodScriptVSC]]. \ No newline at end of file diff --git a/src/main/resources/docs/Typing b/src/main/resources/docs/Typing new file mode 100644 index 0000000000..91ab7d2b1f --- /dev/null +++ b/src/main/resources/docs/Typing @@ -0,0 +1,349 @@ + +MethodScript is an optionally strongly typed language. This means that variables and procedure/closure input and outputs +declare the '''type''' that they are, and only values of that type are allowed to be assigned to that variable, or +returned by the procedure/closure. It is optionally strongly typed, because - outside of strict mode - it is not +required that you provide a type. + +<%NOTE|Currently, the implementation is in the runtime, meaning that you have to run the code before you'll see errors. +Work is ongoing to make this part of the compiler itself, so the errors can be caught earlier in the process.%> + +== Variable Typing == + +The simplest example is this: + +<%CODE| +string @s = "string"; +%> + +We are actually doing two separate operations in this one line, we are ''declaring'' a new variable, @s, +and stating that it is of type string. Secondly, we are assigning the string "string" to the +variable. This seems a bit silly at first, since it seems like we're duplicating information, after all, we can clearly +see that it's a string being assigned. However, consider later code that then uses @s, and perhaps tries to re-assign +the value. + +<%CODE| +@s = array(); +%> + +Now, we would get the error: "ms.lang.CastException: @s is of type ms.lang.string, but a value of type ms.lang.array +was assigned to it." + +This can help prevent future errors in your code, by preventing use of a value that isn't intended. Sometimes this can +help catch bugs that would otherwise be extremely difficult to catch otherwise. A clearer example of this will be shown +later. + +== Procedure and Closure Typing == + +Both the inputs and output of both procedures and closures can be typed. + +<%CODE| +string proc _myProc(int @a) { + return(string(@a)); +} + +closure @c = int closure(string @in) { + return(integer(@in)); +}; +%> + +In both these cases, we have declared the type on the input and output. We have stated that the procedure _myProc must +return a value of type string, and accept an input parameter of type int. Likewise, we have stated that the closure must +return a value of type int, and takes an input of type string. (Incidentally, we have also defined the variable @c, +which must be of type closure.) + +=== void Type === + +Specifically for procedures and closures, it may be that they do not return a value at all. These are said to +''return void'', meaning that they don't have a return type. You can explicitely declare this with the void +type. + +<%CODE| +void proc _myProc() { + msg('Hello World!'); + return(); // This is optional, procs and closures that don't have a return are implied to return void. +} +%> + +== Putting it together == + +This becomes much clearer when we start adding types everywhere. Consider the following example: + +<%CODE| +string proc _getUsername() { + return(import('user')); +} + +string @username = _getUsername(); +msg("Hello @username!"); +%> + +In this example, if the value exported to 'username' had been set as something other than a string, we would fail on +line 2. Perhaps accidentally, we had stored an array to the value, in that case, the user would be messaged the whole +array. This can prevent us from continuing with the code after we have gotten into an unexpected state. Once these type +errors are moved into the compiler, it makes even more sense, because then we would be able to catch these errors even +faster, and not require the code to ever be run before we found the error. + +== Subclasses == + +Each defined type is actually a subclass of one or more ''parent types'', (known as ''superclasses'') +with the exception of the root type, mixed, which has no superclasses. For any given type, +it is always allowed to replace it with a subtype of that class with no error. Consider the following: + +<%CODE| +int @i = 1; +number @n = @i; +%> + +This is valid because int is a subtype of number. We can discover the superclasses for a +given type a number of ways, but we can use {{function|reflect_type}} to get this information. +We can get the full superclass chain with the following code: + +<%CODE| + +ClassType @type = int; // We could use class_type('ms.lang.int') if we wanted to grab the type dynamically + +msg("Finding superclasses for @type"); + +while(true) { + @super = reflect_type(@type)['superclasses']; + if(length(@super) == 0) { + break(); + } + msg(@super); + @type = @super[0]; // Just grab the first one, there can be multiple though. +} + +%> + +== auto Type and mixed Type == + +There is a special type called auto, which essentially means "don't use the typing system on this value." +In fact, values that are declared without a type are actually declared as type auto! These two declarations +are exactly the same: + +<%CODE| +@b = 'test'; +auto @c = 'test'; +%> + +We can prove this by using the {{function|always_trace}} function: + +<%CODE| +always_trace(@b); // <
>:Interpreter:1.1: auto (actual type ms.lang.string, length: 4) @b: test +always_trace(@c); // <
>:Interpreter:1.1: auto (actual type ms.lang.string, length: 4) @c: test +%> + +Values that are of type auto bypass the compilation checks, though they may still cause errors at runtime. + +<%CODE| +auto @s = 'string'; +array @a = @s; // Not a compile error but will be a runtime error. +%> + +Initially, one might wonder the difference between using auto and mixed. Using mixed still +follows the type system, and requires manual casting to convert values. + +<%CODE| +mixed @m = 'string'; // Valid, string is a subtype of mixed +string @s = @m; // Currently valid, but will become invalid with the compiler type system. (See caveats below.) +string @r = string(@m); // Will always be valid. + +auto @a = 'string'; +string @t = @a; // Will always be valid. +%> + +Effectively, auto types are assumed to be the correct type no matter how they're being used. Types stored +in mixed must first be cast to the correct type. Both approaches have merit, and so it depends on what +you're doing as to what mechanism you should use. + +== null == + +Null is a special value which can be assigned to all types. Also, note that ''forward declarations'', that is, where +you define a value but don't set it, default to null. + +<%CODE| +string @a; +msg(@a); // null + +string @b = null; +msg(@b); // null +%> + +== Helper Functions == + +When dealing with types, it's useful to note some various helper functions. + +=== instanceof === + +{{function|instanceof}} tells you if the ''concrete type'' of a value is of a certain type. +This is a runtime operation, so it has nothing to do with the type that the variable was declared with, but the type +that the value itself is right now. + +<%CODE| +mixed @m = 'string'; +if(@m instanceof string) { + msg("It's a string"); +} else if(@m instanceof int) { + msg("It's an int"); +} +%> + +=== typeof === + +Similar to {{function|instanceof}} we can simply find out the current concrete type. + +<%CODE| +mixed @m = 'string'; +msg(typeof(@m)); // ms.lang.string +%> + +{{function|typeof}} returns the ClassType object, which we can then use to gather data about a particular type: + +<%CODE| +mixed @m = 'string'; +msg(reflect_type(typeof(@m))); +/*{ + fqcn: ms.lang.string, + interfaces: {ms.lang.Iterable}, + isNative: true, + name: string, + package: ms.lang, + superclasses: {ms.lang.primitive}, + typeDocs: {{ + docs: A string is a value that contains character data. The character encoding is stored with the string as well., + since: 3.0.1 + }} +}*/ + +// We can also get other information about it with always_trace: + +msg(always_trace(@m)); +// <
>:Interpreter:1.26: ms.lang.mixed (actual type ms.lang.string, length: 6) @m: string +%> + +== Caveats == +There are unfortunately a few loose ends that are caveats at this stage of implementation, which are planned to be +implemented, but aren't complete yet. + +<%NOTE|Not all of the code listed below actually works, or is an example of bad code, and is for demonstration purposes +only. Read the text carefully before applying any examples here.%> + +=== Casting === + +Casting is the act of bypassing various compiler checks when dealing with subclasses. This bypasses some of the type +safety, but is required in some cases. Consider the following code from one of the examples above. + +<%CODE| +mixed @m = 'string'; // Valid, string is a subtype of mixed +string @s = @m; // Currently valid, but will become invalid with the compiler type system. +%> + +The reason this works is because currently, the type system is ''type hinting'', not strong typing. Since the type +system is implemented at runtime, rather than at compile time, we don't look at the second line of code until after +the first line is run. In this case, we see that @m is in fact a string, and so we allow it to be assigned to @s. + +Eventually, this will be corrected, and will be an error. However, @m actually does contain a string, so how would we +make this work? Through an act called ''casting''. When we ''cast'' a value, we tell the compiler that we are positive +that the current value is of the given type. We may have determined this through clever use of instanceof or simply +hardcoded a particular value, but we're essentially telling the compiler to trust us, and don't cause an error. +The syntax will be: + +<%CODE| +mixed @m = 'string'; +string @s = @m as string; +%> + +Casting upward is not allowed, because subclasses are always a valid instance of a superclass. + +<%CODE| +string @s = 'string'; +mixed @m = @s; // No cast necessary, string is a mixed. +%> + +Note that some casts will be invalid anyways. If there's simply no way for a value to be an instance of the particular +type, that cast won't be allowed. + +<%CODE| +string @s = 'string'; +array @a = @s as array; // Compile error, string cannot be cast to array, since string is not a subtype of array. +%> + +=== Cross Casting === + +Cross casting is a runtime conversion of values from one type to another. Currently, cross casting is implemented +opaquely in primitive objects, but is not "exposed" to the end user in a formal way. Consider the following code: + +<%CODE| +msg('1' == 1); // true +%> + +In this case, we are comparing a string and an integer to each other, and find that they are in fact equal. Why is this? +This is due to cross casting. We effectively convert the string value to an integer, then compare it. Formally speaking, +this is due to ''cross casting''. Eventually, string will be formally declared as cross castable to integers, and vice +versa. This means that when we try to cast, we will make a runtime conversion to the expected value. + +<%CODE| +string @s = '1'; +int @i = @s as int; // Cross cast, since int does not decend from string, but string declares it can cross cast to int +%> + +This casting is not automatic for typed variables, you must explicitely provide the cast to do the conversion. However, +this is not true for values of type auto, whether explicitely typed as such or implicitely typed due to no +type being provided. Consider the same code as above but with @s being auto: + +<%CODE| +auto @s = '1'; +int @i = @s; // At runtime, we see that @s is a string, but we are expecting an int. Since @s is auto, we automatically + // cross cast it, making this effectively the same as the above example. +%> + +However, this functionality is not yet implemented, meaning the code will cause an error, despite it intended to be +valid code. We currently get ms.lang.CastException: @i is of type ms.lang.int, but a value of +type ms.lang.string was assigned to it. + +In the meantime, to work around this, you must explicitely cast the value using one of the various manual cast methods +that each primitive has. In this case, {{function|integer}}. + +<%CODE| +auto @s = '1'; +int @i = integer(@s); +%> + +This will continue to work even after this feature is implemented, so there is no code migration risk by doing it this +way. It's also worth noting that literals in code will be considered auto for the purposes of cross casting, so things +like this will also eventually work: + +<%CODE| +int @i = '1'; + +// This will work even further in the future, once effectively final calculations are implemented +string @s = '1'; +int @i = @s; // We know that @s is equal to '1' at this point, because it hasn't been changed anywhere above here + // and the declaration, so we can treat it as if we had typed int @i = '1'; +%> + +If you would like to read more about how this feature is intended to work, see the design document +[[Cross_Casting|here]]. + +=== Declarations within Expressions === + +Currently, the following is possible, but is officially considered "undefined behavior", meaning that it is subject to +change or removal in future versions: + +<%CODE| +int @a = 1 + (int @b = 3); +msg(@a); // 4 +msg(@b); // 3 +%> + +Do not rely on this behavior, as assignments are intended to be separate statements. Instead, rewrite the code to the +following: + +<%CODE| +int @b = 3; +int @a = 1 + @b; +msg(@a); // 4 +msg(@b); // 3 +%> + +This is easier to read in the future anyways. \ No newline at end of file diff --git a/src/main/resources/docs/UnitTesting b/src/main/resources/docs/UnitTesting new file mode 100644 index 0000000000..172cb0c5db --- /dev/null +++ b/src/main/resources/docs/UnitTesting @@ -0,0 +1,290 @@ +{{unimplemented}} + +Unit testing is an important part of writing maintainable and correct code. In most programming languages, unit testing +is bolted on after the fact, but MethodScript elevates the concept of unit testing to a first class part of the language +specification itself. + +At the lowest level, MethodScript provides the core resources needed to properly do unit testing, using special language +support for things like mocks, private member access, and other common unit test paradigms, that require unwieldy and +cumbersome third party libraries to support. + +== What is Unit Testing? == + +In general, unit testing is a mechanism to test individual units of code (hence the name). This is opposed to +integration testing or functional testing, though the unit testing framework can be used to do integration testing as +well. + +In a unit test, the block of code that is under test is the only thing that should actually be run. Functions often have +external side effects though, (such as when calling a method that makes an http call, the server would actually be hit +with an http call) which is usually undesirable. There are a number of solutions to this, and the correct one depends +heavily on what type of test you're writing, and what your desired outcome is. In general, there are three main types +of ''test doubles'': + +* '''Fake objects''' (or ''fakes'') actually have working implementations, but usually take some shortcut which makes \ +them not suitable for production (an in memory database is a good example). +* '''Stubs''' provide canned answers to calls made during the test, usually not responding at all to anything outside \ +what's programmed in for the test. Stubs may also record information about calls, such as an email gateway stub that \ +remembers the messages it 'sent', or maybe only how many messages it 'sent'. +* '''Mocks''' are objects pre-programmed with expectations which form a specification of the calls they are expected \ +to receive. + +Fakes and stubs are already possible to achieve if your code is written in such a way as to take full advantage of +interfaces and dependency injection. For instance: + +<%CODE| + public interface MyInterface {} + + public class MyImplementation implements MyInterface {} + + public class MyStub implements MyInterface {} + + public class MyFake implements MyInterface {} + + MyInterface @mi = getNewImplementation(); +%> + +Assuming that during unit testing, you provide a new instance of MyStub or MyFake, then the rest of the code in the real +methods will work as intended. This pattern is often times useful when you have more complex objects, or for integration +tests, where creating mocks over and over is tiresome. However, the downside of this approach is that you have to write +your code in such a way as to support this, and anyways, if you are writing integrations with third party code which you +cannot change, this might be impossible anyways. + +=== In come mocks === + +In general, a mock framework is a framework that allows easy yet powerful stubbing/faking, but instead of using real +objects such as MyStub or MyFake in the above example, the framework generates what can be thought of as a stub or a +fake on the fly. In addition, a mock framework usually contains mechanisms for verifying that the mocked object was +called in a certain way, with certain parameters. Additionally, the "code" in the mock can easily be modified at runtime +by the test code, so that the behavior can mimic realistic scenarios, with relatively little effort. + +For existing mock frameworks in other languages, they are usually quite good, but due to various limitations in the +language, there are some key drawbacks. For instance, final classes cannot be mocked, because mocks in these +languages are just classes that are created on the fly, and extend a given class, so if the class cannot be extended, it +requires even more cumbersome and more complex frameworks to do the mocking. Private methods and fields cannot be +accessed by the mocks, and static methods cannot be mocked. These limitations make mocks useless in some cases, and +makes it impossible to do a proper unit test. Often, this code is tested in integration or functional tests, but it +can be quite a large process to do so, and in theory, if a mock would work, it would be ideal. Given that, MethodScript +provides built in support for mocks, including support for mocking final classes, private methods, and static methods. + +In addition, global functions can be easily mocked as well. + +== Unit Test Framework (MSUnit) == +In addition to support for mocks, MethodScript provides a mechanism for running unit tests ''MSUnit''. Most importantly, +MethodScript defines some generic annotations, which can be used by all test code, but it also provides a basic runner +for the tests. Third party libraries could provide alternative runners, and in fact, when integrating with an IDE, the +default runner is not used. Much of the unit test framework is inspired by xUnit, so if you are familiar with unit +testing in general, these concepts should not be much of a leap, though there are some extremely notable improvements. + +=== Example === + +Let us suppose we are writing a Calculator class. The following code could be used to unit test the calculator. Let us +first take a look at the abbreviated Calculator class. + +<%CODE| +public final class Calculator { + private double @memory = null; + private double @currentValue = null; + public double add(double @a, double @b) { + double @r = @a + @b; + this->setCurrentValue(@r); + return(@r); + } + + public double subtract(double @a, double @b) { + // ... + } + + public void clearMem() { + @memory = null; + } + + public void setMem() { + @memory = @currentValue; + } + + private void setCurrentValue(@currentValue) { + this->currentValue = @currentValue; + } + + private static void printMsg() { + msg('Hello from a static method!'); + } + + // ... +} +%> + +Our unit test code that tests the add method might look like this: + +<%CODE| +// Note the TestSuite annotation here, which adds the test class to the framework +@{TestSuite(allowSideEffects: true)} +public class CalculatorTest { + + // The Test annotation tells the framework that this method is a test method + @{Test} + public void testAdd() { + // assert([message], expected, actual) + assert("Add is broken!", 4.0, new Calculator()->add(2, 2)); + } +} +%> + +You may name the test method whatever you like, but the return value should be void, and the method should not take +any parameters. + +The testAdd method shows that we expect a return value of 4.0 when we add 2 and 2. If these values are not equal, then +the test method will throw an AssertException, which will signal to the unit test framework that this test has failed, +and the test will end. The other tests in the suite will continue to run, however. We can also mock methods, using +the mock framework, which may be useful to bypass some methods that are not under test. The when() function is used +to assign mock functionality for a particular method, and is a method with special runtime support, which does not +actually execute the parameter passed to it. verify() is special in the same way. + +<%CODE| + + @{Test} + public void testMock() { + // First, we need to create a mock of the calculator. mock() returns a new mock object of the appropriate type, + // using type inference, but it is possible to specify a subclass, if that is desired, i.e. mock(SubCalculator); + Calculator @c = mock(); + // By default, methods do nothing, and return a default value depending on the + // return type. In the case of a method with a double return type, 0.0 is returned. This line demonstrates that, + // but doesn't actually test the calculator class + assert(0.0, @c->add(2, 2)); + // Next, we set up the mock methods, so they return some different value when 2 and 2 are passed in. + when(@c->add(2, 2))->thenReturn(4.0); + assert(4.0, @c->add(2, 2)); + // For other inputs though, we still will get the default, because we haven't specified it. + assert(0.0, @c->add(100, 100)); + // We can make it not care about parameters with the any() type: + when(@c->add(any(), any()))->thenReturn(42.0); + // This is a shorter but functional equivalent: + when(@c->add)->thenReturn(42.0); + // Now 42.0 is returned for any combination + assert(42.0, @c->add(1, 1)); + assert(42.0, @c->add(12, 5)); + // We can also mock static methods + Calculator::printMsg(); // Hello from a static method! + when(Calculator::printMsg).thenDo(closure() { + msg('Hello from a mocked static method!'); + }); + Calculator::printMsg(); // Hello from a mocked static method! + } +%> + +This example is not useful, because we are not passing the Calculator object to another object, but merely demonstrates +how mocks work. In this case, it's more useful to use a ''spy'' instead. A ''spy'' works more or less like a mock, but +unless otherwise instructed, will execute the real method in the underlying object. Unlike a mock, however, creating +a spy does need a real instance of the class. If the class has a no arg constructor (even a private one), and this is +the appropriate constructor to call for the test, then nothing additional needs doing. However, if you wish the object +to be constructed with another constructor, you can provide the instance to the spy function. + +<%CODE| + @{Test} + public void testSpy() { + // Create the spy first + Calculator @c = spy(); + // If we wanted to construct our own custom instance + Calculator @c2 = spy(new Calculator('string', 1, 2)); + // Create a subclass of Calculator instead + Calculator @c3 = spy(SubCalculator); + // Here, unlike the mock, you can see that the method returns the valid value + assert(4.0, @c->add(2, 2)); + // We can still mock a method though, to override the functionality, in exactly the same way as a mock + when(@c->add(2, 2))->thenReturn(42.0); + assert(4.0, @c->add(3, 1)); + assert(42.0, @c->add(2, 2)); + // We can revert back to the default behavior both for mocks and spies using remove_when. + remove_when(@c->add(2, 2)); + // We can also verify that certain methods are called from the mock or spy, including private methods. + // Note that setCurrentValue is private, yet from within the test code, we can access it anyways. We set the + // number of times we expect this method to be invoked, with the given arguments, in this case, any() (We could + // leave that off, and just do verify(@c->setCurrentValue) as well. + verify(@c->setCurrentValue(any()))->times(3); + // Given that we called add 3 times in this test, this test should pass. 0 is a valid option, meaning that we + // don't expect to have called the given method at all during the course of the lifetime of the mock. + } +%> + +Global functions can also easily be mocked, using the same general syntax, with the exception that all arguments must +be mocked, there is no shortcut for leaving off the arguments. + +<%CODE| + @{Test} + public void test() { + when(read('file.txt')).thenReturn('fake file text'); + assert('fake file text', read('file.txt')); + // Bad: + // when(read).thenReturn(''); + } +%> + +In some cases, a more complex mechanism needs to be used, particularly when the item under test does not return a value, +but interacts with a value passed in, most generally a closure. In that case, you can use thenDo to give it an action to +do when it is called, rather than having it return a value. The closure sent to thenDo will receive the arguments that +were passed in to the original function. + +<%CODE| + @{Test} + public void test() { + // Assume that we expect MyObject->myMethod to call http_request, but we don't actually want to make a real web + // request. This is the pattern we can use. + MyObject @o = spy(); + // distinguish between http_request(string, closure) and http_request(string, array) by providing the type + WhenMeta @http_request = when(http_request(any(), any(closure))) + // The type of any() is mixed, so we must accept mixed as the url. Since we specified the second argument + // is a closure, however, we can define that as such here as well. + ->thenDo(closure(mixed @url, closure @callback) { + execute(array('body': 'http request body'), @callback); + }); + @o->myMethod(); + // We can verify that the method was called. when() actually returns a WhenMeta object, which we can use to + // verify interactions. + verify(@http_request)->times(1); + } +%> + +Private methods may be called directly from within the test code, and this is not a compile error. + +<%CODE| + @{Test} + public void test() { + Calculator @c = new Calculator(); + @c->setCurrentValue(10); // Not an error + assert(10, @c->currentValue); // Also not an error + } +%> + +== Scoping == +In general, tests should be isolated activities. This allows for tests to be run in parallel, prevents dependencies on +test run order, and makes writing tests easier to visualize. To support this notion, MethodScript enforces test +isolation at a low level. TestSuites are mostly just a logical grouping of tests, but if a method in the test suite +is marked as @{BeforeTest} or @{AfterTest}, then these methods will be called before and after each @{Test} method. +However, each test is run within its own environment, which is not shared with other test runs. Any changes made to +static values (including mocking the behavior of static methods) or other memory based structures are not saved. +Furthermore, the persistence network is backed by a +memory based implementation, the actual persistence.ini file is completely ignored. {{function|read}} and +{{function|write}} go to a memory backed VFS, and http_request is blocked from making actual http calls (the function +may be mocked to simulate a web request, however.) + + +However, there may be legitimate reasons to allow side effects. This is allowed by using the allowSideEffects parameter +of the @{TestSuite} annotation. This is false by default, but if set to true, then for that test suite only, none of +the external restrictions are enforced (persistence network, read/write, http_request). Additionally, within the test +suite, the state of the environment is not reset before each test, though @{BeforeTest} and @{AfterTest} are still run +before and after the test. There is also a guarantee that tests will run in order, from top to bottom, and they will +not run in parallel. In general, this has many drawbacks, however, and will considerably slow down your unit tests, so +should be used sparingly, if ever. Generally, the better solution is to refactor your actual code and tests such that +the tests can run independently of each other. + +== Integration Testing == +In general, the unit test framework can be used without mocking, and across larger sets of objects to accomplish what +is known as integration testing. The line between this and unit tests can be blurry anyways, but strictly speaking, +unit tests should not interact with external objects (other than through mocks). But often times, there is little value +in doing such a limited test, as the real test lies in the interaction between various objects. In this case, you can +use the unit test framework, minus perhaps the mocks, and this is a perfectly acceptable use of the framework. + +== Functional Testing == +Functional testing, on the other hand, works more like a black box test. The program as a whole is run, as if run by a +user. In some cases, this is the most desirable test type, but the unit testing framework does not provide support for +this. \ No newline at end of file diff --git a/src/main/resources/docs/Upgrade_Guide b/src/main/resources/docs/Upgrade_Guide new file mode 100644 index 0000000000..146fa948e3 --- /dev/null +++ b/src/main/resources/docs/Upgrade_Guide @@ -0,0 +1,220 @@ +Updating to new versions of Minecraft can sometimes require updates to your scripts. +Below is a reverse chronological guide to help you with the most notable of these changes to quickly update your code. +Where possible, any value changes are converted for backwards compatibility with saved data, but any script that checks output for equality will need to be updated. +---- + +== Minecraft 1.21.11 == +* GameRule names have been changed and are now snake_case. While old names are converted at runtime, this is not possible when using the array from get_gamerule(world) +* Old GameRules starting with "disable" have been inverted. These are inverted at runtime when using the old names. +* Old GameRules "doFireTick" and "allowFireTicksAwayFromPlayer" have been replaced with "fire_spread_radius_around_player". +* Several GameRules have new integer limits. (these appear to reset the next time the world attempts to save, but may case exceptions in the future) + +== Minecraft 1.21.9 == +* CHAIN renamed to IRON_CHAIN + +== Minecraft 1.21.5 == +* SPLASH_POTION entity type has been split into LINGERING_POTION and SPLASH_POTION again. +* Some vanilla keys passed to the tellraw functions and commands have changed. + * "clickEvent" is now "click_event" + * The actions "run_command" and "suggest_command" have had their "value" field renamed to "command" + * The action "open_url" has had their "value" field renamed to "url" + * The action "change_page" has had their "value" field renamed to "page" + * "hoverEvent" is now "hover_event" + * The actions "show_item" and "show_entity" have had their fields and value formats modified + +---- + +== Minecraft 1.21.2 == +* BOAT and CHEST_BOAT entity types have been split into wood types. ("OAK_BOAT", "ACACIA_CHEST_BOAT", etc.) + +---- + +== Minecraft 1.21 == +* Attribute modifiers' optional "name" and "uuid" keys have been replaced with a namespaced key under "id". +Old attribute modifiers will use the UUID to generate a namespaced key. + +---- + +== Minecraft 1.20.5 == +* SCUTE item has been renamed to TURTLE_SCUTE +* In potion item meta, the potion array under the "base" key was replaced by a "potiontype" string +* In enchantment item meta, the arrays under the "enchants" key have had the "etype" key removed +* HORSE_JUMP_STRENGTH attribute was changed to GENERIC_JUMP_STRENGTH + +---- + +== Minecraft 1.20.3 == +* GRASS changed to SHORT_GRASS + +---- + +== Minecraft 1.20 == +* Damage cause from the /kill command was changed from VOID to KILL, and damage cause from the world border was changed from SUFFOCATION to WORLD_BORDER. +* Signs are editable and can have text on the back side. (affects 'sign_changed' event) + +---- + +== Minecraft 1.18 == +* BARRIER and LIGHT particle types have been changed to the BLOCK_MARKER particle type with a 'block' parameter. +* There are many removals and name changes to biomes. + +---- + +== Minecraft 1.17 == +* GRASS_PATH changed to DIRT_PATH +* Cauldrons are split into 4 materials: CAULDRON, WATER_CAULDRON, LAVA_CAULDRON, POWDERED_SNOW_CAULDRON +* GLOW_ITEM_FRAME is a separate entity type that behaves exactly as item frames +* All ores now have a deepslate variant (eg. DEEPSLATE_COAL_ORE) + +---- + +== Minecraft 1.16.1 == +* PIG_ZOMBIE entity type was changed to ZOMBIFIED_PIGLIN. This matches the material change from ZOMBIE_PIGMAN_SPAWN_EGG to ZOMBIFIED_PIGLIN_SPAWN_EGG. +* NETHER biome type was changed to NETHER_WASTES. + +The above changes will automatically convert (and warn) when setting (eg. spawn_entity('PIG_ZOMBIE')), but will break +when reading in a comparison. (eg. entity_type() == 'PIG_ZOMBIE') + +---- + +== Minecraft 1.14.4 == +* Villager professions have completely changed. +* Ocelots can no longer be tamed or sit. +* The following materials are removed but will be converted in legacy item arrays or when using set_block(): CACTUS_GREEN, DANDELION_YELLOW, ROSE_RED, SIGN, WALL_SIGN. +* TIPPED_ARROW and LINGERING_POTION entity types no longer exist and will be converted to ARROW and SPLASH_POTION respectively. + +---- + +== Minecraft 1.13 == +Updating to 1.13 is a major overhaul, and there was no easy way around this. Fallback behavior was created where +reasonably possible to make the transition somewhat smoother, but this will still be a large undertaking. + +Block/item numeric ids are no longer guaranteed to be accurate and many material names are changed, though these will be +temporarily supported as inputs to functions for some time. This has several consequences that you'll need to address in +your scripts. + +=== Item Array Format Changes === +While you can still input old item arrays, the output of item functions and events has been changed to reflect changes +in Minecraft. + +Where previously you might get an item array object in this format: + +
+{name: BOW, type: 262, data: 81, qty: 1, enchants: {{elevel: 1, etype: DURABLITY}},
+meta: {display: null, lore: null, enchants: {{elevel: 1, etype: DURABILITY}},
+unbreakable: false, flags: {}, repair: 1}}
+
+ +Now it will return an array object in this format: + +
+{name: BOW, qty: 1, meta: {damage: 81, display: null, lore: null, unbreakable: false,
+enchants: {unbreaking: {elevel: 1, etype: DURABILITY}}, flags: {}, repair: 1}}
+
+ +Note that the keys "type", "data" and "enchants" were removed from the parent array. The old "type" numeric id has been +replaced by "name". The "data" value is only relevant for item durability now, so this was moved into the meta array +under the key "damage", and is only present if the item is damageable. (ie. if max durability is above 0) The "enchants" +key was redundant to the one in the meta array, and its format has been modified to reflect how you can't have more than +one enchantment of the same type. It now uses minecraft names for enchantment keys too, to be easier to use. (old names +and format are still accepted) + +Some specific item meta keys have changed as well. For banners, "basecolor" has been removed as it's defined by the +banner item name. (eg. "RED_BANNER") Similarly, spawn eggs no longer have the "spawntype" key because that's defined by +the material name too. (eg. "BAT_SPAWN_EGG") The map "data" key has been replaced with a meta key "mapid". + +Many material names under the key "name" have been changed, but will always be converted as long as they don't have a +name conflict. Most critically, you should probably address any item array with the "data" key, as these best indicate +possible conversion problems. For any hard-coded item arrays, delete the "type" and "data" keys (their presence +indicates a legacy item array and will be attempted to be converted) and rename the "name" key to the new material name. +The new materials names should be the same as in the Minecraft wiki and in-game. You can move the damage value into the +meta array if needed. A convenience function convert_legacy_item() will take an old item array and output a new item array. +This is useful for updating items that might be stored in a persistence database. The function data_name() now accepts +old material names, ids and string formats and converts them to modern material names. This is useful for conversion of +databases or maybe user input. + +=== Biome Names Changed === +These now better reflect their minecraft names. If you use get|set_biome() functions, you can look up these new enums +with reflect_pull('enum', 'BiomeType') or in the documentation. + +=== Painting Names Changed === +Two paintings just had a space added to them: BURNING_SKULL and DONKEY_KONG. + +=== Item/Block String Format Deprecated === +The "0:0" format is no longer guaranteed to be accurate, so it's deprecated wherever it's used. In most cases you will +be warned on compile time if you use these anywhere. If not, you will be warned in the log at runtime, but the +item/block will be attempted to be converted. Notably, get|set_block_at() have been replaced with get|set_block(). If +you need to read or write block data, the get|set_blockdata_string() functions are available until further functionality +is added. These use the same format as vanilla block commands. + +=== Changed DyeColor "SILVER" to "LIGHT_GRAY" === +This now matches vanilla and material names to be easier to use. SILVER is still accepted and will be converted, but is +discouraged. + +=== Recipe Keys Required === +Recipes now require a key to be added, where previously it was optional and randomly generated. This should just be a +unique name describing the recipe you're adding. (eg. 'key': 'ice_to_packed_ice') Recipe ingredients, of course, should +use item arrays instead of the string formats. + +=== CommandBlock Changes === +In addition the vanilla changes to commands, they no longer automatically process vanilla selectors for plugin commands. +You will have to implement selectors yourself if you need this behavior. If upgrading to 3.3.5, you can use the +select_entites() function. + +=== Event Removals === +The event tab_complete_chat is removed because clients no longer send these to the server. The event +player_prelogin_event is removed because it was deprecated in Spigot due to syncing issues, and it's mostly redundant to +player_login. + +=== Sound Changes === +Pre-1.9 sound names are all removed and some post 1.9 sounds have been updated to reflect 1.13. However, these will no +longer cause play_sound() to throw an exception. Instead, it will warn in the log and continue running. +play_named_sound() always behaved like this, but did so silently. play_sound() will now simply warn you if the sound +didn't play because the name is incorrect. + +=== Event Data Changes === +The following events' prefilters, event data, and/or mutable data were updated: piston_retract, piston_extend, +block_break, block_place, block_burn, block_ignite, block_from_to, block_dispense, block_grow, note_play, block_fade, +item_despawn, item_spawn, item_drop, item_pickup, entity_change_block, entity_interact, inventory_click, inventory_drag, +item_swap, player_interact, and vehicle_collide. + +While some of these are obvious or may not affect you, please refer to the event documentation to update your prefilters +and which keys you're reading from. A common theme is "item" prefilters are changed to "itemname", and like "block" keys +and prefilters, now use the material name instead of the item/block string format. (eg. "0:0") In a special case in +player_interact, if you want to check if they didn't click a block, check where "block" equals null. These are sensible +but extensive changes, so I may add a list here later. Searching for "data" in general or "item" in prefilters will help +fix a lot of them. + +=== Other Changes === +There are a number of other changes, but less critical to this guide. If you focus on the above changes, everything else +will be easier. + +---- + +== Minecraft 1.12.1 == +CommandBlocks now call aliases without needing /runalias. So if you have an alias for a vanilla command and there are +CommandBlocks that use the vanilla command, it may break. A manual fix is to add the "minecraft:" prefix to those +commands in the affected CommandBlocks. (eg. /fill to /minecraft:fill) To be clear, this does NOT affect CommandBlocks +that already use /runalias or vanilla commands that do not have aliases. + +---- + +== Minecraft 1.11.2 == +You can no longer set_exp() above 100. In 1.11.2, setting exp to exactly 100 will now give that player a new level and +reset exp to 0%. Set to 99 to avoid this. + +---- + +== Minecraft 1.11 == +Minecraft changed entity subtypes to be their own entity type. You may need to search for instances of "guardian", +"skeleton", "zombie", and "horse". Changing an entity's subtype using set_entity_spec() is no longer supported. Also if +you're checking for an entity type in an event bind, you'll need to update that to include a list of all subtypes. +(Example: instead of "HORSE" you'll need to check for "HORSE", "MULE", "DONKEY", "ZOMBIE_HORSE", "SKELETON_HORSE", +and/or "LLAMA".) + +---- + +== Minecraft 1.9 == +One or two player_interact events will fire now with the addition of the off_hand. If two, it's one for each hand. If +one, it might be the main_hand or off_hand. Some scripts need to be considered for double event firing. Any script that +looks at the item array will need to be updated for off hand support. \ No newline at end of file diff --git a/src/main/resources/docs/Varargs b/src/main/resources/docs/Varargs new file mode 100644 index 0000000000..a6c13d087c --- /dev/null +++ b/src/main/resources/docs/Varargs @@ -0,0 +1,60 @@ +Variadic arguments, or varargs for short, are a way of providing typesafe and variable numbers of parameters +to a given Callable. In general, many native functions use this, though they currently have support for varargs +in multiple positions, whereas user code currently only can place varargs as the last parameter. + +In order to use varargs, the Callable needs to be specially defined. + +<%CODE| +void proc _varargTest(string... @values) { + // Within the proc, @values is actually defined as an array of strings, not a single string object. + foreach(string @s in @values) { + msg(@s); + } +} +%> + +Now this allows us to send as many string parameters to the proc as we like. They will internally be converted into +an array and passed to the function. + +<%CODE| +_varargTest('a', 'b', 'c', 'd'); +%> + +Implicit auto values are not supported in this scenario, though you can explicitly provide the auto type, such as + +<%CODE| +void proc _autoTest(auto... @values) {} +%> + +== @arguments == + +The @arguments parameter will continue to exist, and will continue to be defined as an +array<auto> which contains all parameters sent to the Callable, but this should not be +relied on in the future for strict code, as eventually, parameters will be typechecked, and will also include +a check for argument counts. Take for instance this code: + +<%CODE| +proc _test() { + foreach(@arg in @arguments) { + msg(@arg); + } +} + +_test('a', 'b', 'c'); +%> + +To allow this code to work in strict mode, it should be upgraded to provide an auto vararg (and provide a return type). +The internal implementation doesn't need to change. + +<%CODE| +void proc _test(auto... @args) { + foreach(@arg in @arguments) { + msg(@arg); + } +} + +_test('a', 'b', 'c'); +%> + +This syntax works for all parameter types in Callables, but must be the last parameter. This notation cannot be used +as a return type, or general types, such as in assignments. \ No newline at end of file diff --git a/src/main/resources/docs/Variables b/src/main/resources/docs/Variables index 562e69b475..e84fe6d6ce 100644 --- a/src/main/resources/docs/Variables +++ b/src/main/resources/docs/Variables @@ -13,7 +13,7 @@ command definition. This is why it is referred to as the final var. It is treate array (for example, to parse the arguments individually), use parse_args($) to return an array of individual items. Here is a more complex, but valid scenario: -/cmd one $var1 const $var2 const $var3 $ = ... +/cmd one $var1 $var2 $var3 $ = ... In this case, if the user types "/cmd one two three four five six", it would match, and the defined variables would be as follows: {| diff --git a/src/main/resources/docs/WebServer b/src/main/resources/docs/WebServer new file mode 100644 index 0000000000..18900a8bba --- /dev/null +++ b/src/main/resources/docs/WebServer @@ -0,0 +1,82 @@ +{{unimplemented}} + +MethodScript supports running as a web server behind a web server frontend, such as Apache or Nginx. This functionality +is not installed, configured, or running by default, so a bit of setup is required to enable the feature. + +Before starting, you must choose a server frontend. This may be any server that supports reverse proxying, but Apache +and Nginx are the only tested server types. While possible, it is highly discouraged from exposing the MethodScript +web server directly to the internet, and further, since it does not support serving static files, it cannot be used +in a useful way otherwise. + +== Installing MSWS == + +The zeroth step is to install MethodScript to the command line. To do this, run + +<%PRE|java -jar MethodScript.jar install-cmdline%> + +as root/Administrator. If prompted, reboot your computer. Note that this step is not optional -- several portions +of the system assume there is an executable binary named "mscript" on your system. + +The next step is to install the webserver component. This creates the necessary config files, as well as integrating +with systemd on unix systems, if installed. To install, run + +<%PRE|mscript -- webserver --install%> + +as root/Administrator. This will create a configuration file in your prefs folder, named webserver.ini. Open this file +in a text editor, and configure it to your liking. Reasonable defaults have been selected, but you may wish to choose +different values for this, particularly the server root. + +== Using MSWS == + +On all systems, a basic frontend is included by default. This allows you to start and stop the webserver, as well +as recompile on demand. To increase performance, all server files are compiled once at startup, and then are no longer +scanned, so recompilation is necessary during active development. In any case, the first step is to start up the web +server. For test purposes, we will start the server in the foreground, so we can see any logging output. + +<%PRE|mscript -- webserver --start --foreground%> + +Ensure that the server starts up correctly. If so, kill the process with ctrl+c, so we can start it back up in +background mode (daemon mode). + +<%PRE|mscript -- webserver --start%> + +Next, we want to test to ensure that a connection can be made to the server. +Take note of the port listed in webserver.ini. By default, this is 16438, though if you changed this, use the new value +in place. On Windows, open a web browser, and go to "https://localhost:16438". +(On Linux, run curl "https://localhost:16438) If everything is working, you should get +a 404. Since we have not created any scripts, this is expected, but it means that the server is up and accepting +connections. If you get a connection refused error, restart the server in --foreground mode, and check the server +startup logs to see if there is an error. + +While developing, you can recompile scripts with + +<%PRE|mscript -- webserver --recompile%> + +If you want to stop the server when it's running in background mode, you can run + +<%PRE|mscript -- webserver --stop%> + +=== Systemd integration === + +On unix systems where systemd is installed (or more specifically, where a folder exists at /lib/systemd/system, msws +integrates with systemd. It supports starting, stopping, restarting, and reloading. Reloading maps to the --recompile +flag. The name of the service is "msws". + +If you would like the server to start on server boot, run + +<%PRE|systemctl enable msws%> + +== Integrating with a server frontend == + +Since MSWS is not expected to be directly exposed to the internet, we require a frontend server, such as Apache or +Nginx. MSWS communicates via standard http, and both Apache and Nginx have good support for "Reverse Proxying". In +general, however, you are not limited to Apache or Nginx, however, they are the only frontends documented here. Using +another web server should be possible if you adapt these instructions to that platform. + +MSWS can only serve scripts. Therefore, you need to configure your primary web server to serve static files, such as +css and javascript files, and only configure the frontend server to call to MSWS for .ms files. + +== Apache == + +== Nginx == + diff --git a/src/main/resources/docs/WebTemplating b/src/main/resources/docs/WebTemplating new file mode 100644 index 0000000000..602b3f42af --- /dev/null +++ b/src/main/resources/docs/WebTemplating @@ -0,0 +1,606 @@ +{{unimplemented}} + +MethodScript supports templating for web (or any other template needs) through template +language tags. In many ways, the support for templates is much like JSP and PHP, but +differs in a few key ways, which allow greater flexibility when creating text via +templates. + +== Overview == +Before delving into MethodScript's templating system, it is useful to discuss the +shortcomings of existing templating systems. PHP was originally designed as a template +language. In fact, there are still relics in PHP that support inline template syntax +for mostly HTML pages: + +<%PRE| + +inline html + +%> + +This approach is very useful, however due to the unrestricted nature of PHP, it can cause +problems with separating concerns. [http://www.smarty.net/ Smarty] was created to assist with +the [http://en.wikipedia.org/wiki/Separation_of_concerns separation of concerns], +but requires learning an all new templating language syntax (on +top of also needing to know PHP) and so adds a layer of complication that doesn't really +solve the underlying problem (because you can still mix too much logic into the UI code +anyways). MethodScript's templating system takes these concerns into consideration, and +while it by default promotes good separation of concerns, it does not actually restrict +a user from breaking these concerns, where flexibility (or minimal code) is desired. + +The most prominent architecture of a web page (or any UI for that matter) is the +[http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller Model-View-Controller (MVC)] +paradigm. This pattern allows for the best separation of concerns, by moving the +data driven portions of the application into separate code from the UI components. +A good template system would only allow access to the ''View'' portion of the paradigm, +which is what MethodScript does by default. MethodScript uses standard XHTML as its +display model, which easily translates to web applications. The UI library in MethodScript +mirrors all the XHTML tags with objects, and vice versa, and allows the creation +of more complex macro components that are made up of the native xhtml primitives, either +in pure MethodScript, or xhtml templates. +In addition, of the subset of functions that are compilable to javascript, those +scripts are compiled into javascript at compile time, using the standard xhtml +script tag, which allows for client side UI logic to be embedded or included the +same as pure javascript would preventing you from having to learn an all new language, +in addition to being able to fully optimize the logic at compile time. +It also allows for javascript code side by side, in standard script tags. All xhtml+MethodScript +template files are first class in pure MethodScript code, which allows for the business +logic to access the UI components as easily as pure MethodScript elements, while +still maintaining strong typing. In addition, most all standard CSS components have class based accessors +as well, allowing for type safety, inheritance, and variable based CSS templates +to be generated. + +== Usage == +There are two key concepts that you need to know to effectively write MethodScript +templates: variable inclusion, and dynamic scripting. All templates +that use only variable inclusion compile directly into equivalent pure +MethodScript classes, and dynamic scripting portions are compiled down to javascript +and virtual MethodScript code. + +Before we move on to the usage of these concepts, lets look at a perfectly +static page that doesn't use any MethodScript in the template itself. + +=== Barebones usage === + +A barebones example has potentially two parts, the root XHTML page, and the controller +code. In the simplest example though, it is simply a XHTML class that is accessed. We won't +consider that example yet, because there is no interaction with MethodScript in that case, +and the XHTML template would simply be passed through, virtually untouched. Instead, we +will assume that a pure MethodScript file is accessed, which uses MethodScript to display +the XHTML template instead. In the basic case, when a .ms file is accessed through the web server, it is run +once, and is expected to call the display method on a XHTML object, which will in turn output +the rendered xhtml to the client. (It can output any text in reality, which is used for instance, +in the course of ajax request responses.) HTTP headers may be buffered at any point before this, +and are sent just before the body of the HTTP response. Let's look at example code. + +<%CODE| +XHTML @page = new XHTML(); +@page->setTitle('Page title'); +@page->setDescription('This escapes special symbols like & when used this way'); +@page->addChild(new XHTMLDiv('Inner text')); +@page->display(); +%> + +This renders the following markup (assuming the output is set to be tidied and configured +to output xhtml): + +<%PRE| + + + + + + Page title + + +
Inner text
+ + +%> + +In fact, if you put the rendered XHTML into a template file, it would essentially compile +to equivalent MethodScript. + +Usually you won't use the XHTML object directly. If you have a standard page layout, it is +best to create a factory method that will create and set up an XHTML object for you, and +then return that, which can be further operated on for that page specifically. Now lets +consider the case where we ourselves are creating a template. Templates are defined by the +fact that they have the .mst extension. This puts the compiler into a "template compiling +mode" instead of assuming pure mscript. The component must extend an existing UIComponent +class (which all the XHTML objects extend). The class name is the name of the file it +is in, and it extends whatever type the root element is. For instance, if we had a simple +component that has static text inside a div (assuming it is saved in MyDiv.mst), we can write this: + +<%PRE| + +
+ Inner text +
+%> + +Since the root element is a div, the doctype is inherited from it, though +attributes and elements can be added as well. Because of this, all +elements have a doctype, even if it isn't directly specified. +Written in equivalent pure mscript, this class would look like this: + +<%CODE| +@{XHTML} +class MyDiv extends Div { + public MyDiv(){ + this->addChild('Inner text'); + } +} +%> + +and regardless of how we just defined that, we can use it via pure MethodScript like +this: + +<%CODE| +XHTML @page = new XHTML(); +@page->addChild(new MyDiv()); +@page->display(); +%> + +or like this in another template: + +<%PRE| + +
+ +
+%> + +The @{XHTML} annotation is used to tell the UI compiler that this class is available +in mst templates. More advanced usage is shown later in this tutorial. + +=== Variable Inclusion === +Variable inclusion is the most straightforward way to make templates dynamic, and this +principal is core to the understanding of the templating system. When a component defines +a property, it is eligible to be used as an attribute. Attributes can be set via the component +constructor, or as individual xml attributes in a template. When creating a custom component, +attributes are defined via the constructor as a struct, or in a DTD ATTLIST in a template. +Once defined, the variable is defined in the template as a $var, and are simply expanded out. +Note that only primitive values are available in attributes, if you have a more complex +layout, you'll need to use static scripting to properly layout a page. + +==== Declaring attributes in custom components ==== + + +To declare your own element with attributes in MethodScript is a two step +process. First, you must set the attributesClass value of the @{XHTML} annotation +to your struct class, which should extend the nearest parent class's attributesClass +struct. (The top level XHTML element, which everything must +extend from ultimately defines an empty struct, called Attributes.) Secondly, +you must implement the (Attributes) or (Attributes, UIComponents...) constructor. +Upon creation (either through pure MethodScript or via mst templates) this data +will be passed in then. All attribute values must extend the primitive type, +and cannot be objects. + +For example: + +<%CODE| +@{XHTML(attributesClass: MyDivAttributes)} +class MyDiv extends Div { + + public static struct MyDivAttributes { + public string @name; + public int @age = 21; + } + + public MyDiv(MyDivAttributes @options){ + this->addChild(new Div('Name: '.@options->name)); + this->addChild(new Div('Age: '.@options->age)); + } +} +%> + +The equivalent structure written in a MyDiv.mst file is slightly simpler, because +the attributes simply need to be declared with a type, and possibly a default value, +using the attr attribute in the root element. The availability of the attributes +is checked at compile time, so if you forget to declare one of them, then use it, +it will be a compile error. All attributes from the parent are also available to +be used as well inside of the template. + +<%PRE| + +
+
Name: $name
+
Age: $age
+
+%> + +If you have an attribute named $var, and you need a literal $var, you +can use the xml escape sequence &#36;var. + +=== Dynamic Scripting === +When writing UI code, especially HTML, for truly dynamic web pages, you need to +run some client side logic to do what you need. For instance, say you have a button +that when clicked makes a number count up. In this case, you need to (ultimately) +write javascript to do that. However, javascript is a cumbersome, unwieldy language +in many cases, so MethodScript offers the ability to compile to javascript in many +cases. Most of the core functionality is available in the cross compiler, and some +additional features are used specifically in these scripts. Most notably is the +ability to access the dom. Note that while this code does get compiled at the same +time as the rest of your script, it doesn't actually get run as MethodScript, it +is cross compiled to javascript, and included as part of the entire output html. +A very simple Hello World program would look like this: + +HelloWorld.mst: +<%PRE| + +
+ +
+%> + +We would deliver this to the browser using the following main code: + +<%CODE| +XHTML @page = new XHTML(); +@page->addChild(new HelloWorld({output: 'Hello World!'})); +@page->display(); +%> + +which would result in the the following html: + +<%PRE| + + + + + + + + + + +%> + +As you can see, MethodScript does most of the work for you, making it far simpler +to use than javascript. However, this does not stop you from using javascript yourself. +Any javascript you embed in the template will be included exactly as is, so existing +javascript libraries can be used. Unfortunately, you cannot interface MethodScript +with javascript directly, though MethodScript can call javascript functions indirectly. +Inside of the tag, the this keyword is available, and refers to that instance +of the html tag. + +The equivalent to doing this in pure MethodScript is equally straightforward, but +less clear. The important thing to remember is that this uses rclosure linking rules. +The XHTML->addScript method is used to add a script to an element. The same template +shown above would be written in pure MethodScript like this: + +<%CODE| +@{XHTML(attributeClass: HelloWorldAttributes)} +public class HelloWorld extends Div { + public static struct HelloWorldAttributes { + public string @output; + } + public HelloWorld(HelloWorldAttributes @options){ + this->addScript(rclosure(){ // returns void, no args + alert(@options->output); + }); + } +} +%> + +In the case of using pure MethodScript, you can even conditionally add scripts if needed. +In either case however, before the script is cross compiled to javascript, it is optimized +in the MethodScript compiler, which may significantly change the output script. This condenses +the output code so that only the elements that truly need to be client side are sent. +Any logic that can be determined at compile time will be resolved at compile time. +While it is possible to add multiple script blocks in a mst template, it doesn't +affect the output script (other than lexical correctness, if your scripts aren't +logically complete) because the script will be gathered up and placed in the appropriate +location anyways. + +External MethodScript-compiled-to-javascript can also be included, akin to how you +would include a script tag in the head of an html page. To do this, in the XHTML +object, call the useScript method, and pass it a reference to a .ms file, which will +be cross compiled and be made available in the javascript. Your server can be configured +to manage caching automatically, or if you have a volatile environment, to simply +turn off caching, and the compiled javascript will be re-rendered each time it is +requested as a part of the browser request process. + +==== DOM access ==== + +One of the most important things that javascript provides is the ability to access +the dom. MethodScript exposes this functionality in an easy to use way, so you can +take advantage of some of the type safety that the templating system uses, while still +being able to access core properties easily. The simplest and most straightforward +example would be for a div to hide itself after a few seconds. To do this, we can +use the methods in the XHTML class in a script block. + +FadeDiv.mst: +<%PRE| + +
+ +
+ Hide me! +
+
+%> + +This would compile to the following output: + +<%PRE| + + + + + + + +
+ Hide me! +
+ + + +%> + +As you can see, the dom access is generally easier, or just as easy in javascript, +but we get the advantage that in MethodScript, many invalid operations become +a compile error. For instance, the display property only recognizes some string +values, so that is an enum, so the following code would cause a compile error, +even though it would work (though it would be ignored) in javascript. + +<%CODE| +#Works +hideThis->style->display = "none"; +#Compile error! +hideThis->style->display = "invalid"; +%> + +Additionally, when working with specific elements with ids, we don't need +to use document.getElementById("id"), we can simply use id. The compile figures +out what type that is exactly, and adds it as a valid child, if it is statically +declared. For dynamically declared elements with ids, you would have to use +getElementById, but generally that's a code smell, since you shouldn't be generating +ids programmatically anyways (use class references instead). Further, doing it this +way helps to enforce decoupling, because any javascript using an id is by definition +tightly coupled with that element, and so the code specific to it should be logically +placed in the same file anyways, to make it easier to find. Generic library code +shouldn't be using the ids anyways, and so the ids simply won't be available in the +header code, making your dom access follow proper inheritance principals. + +All CSS and DOM elements are mapped to first class MethodScript data structures, +so the type safety is applicable to all values. For experimental CSS values, raw +string based styles can be added, but the standard values all have first class mappings. + +If a first class mapping doesn't exist, or you are trying to access experimental or non-standard values, +you can use the reflection mechanism to bypass the compiler, just as you would in normal MethodScript +code. + +<%CODE| +@hideThis->style['display'] = "experimental value"; +%> + +The dynamic scripting can also be used to make html changes at compile time. For instance, +given the following template: + +Optimized.mst: +<%PRE| + +
+ +
+ Hide me! +
+
+%> + +This would actually result in html code that had no javascript in it, because +the dom manipulation would happen at compile time, because all the components +are known to the compiler, and they don't require any javascript to be run by the +client. The output html would be simple: + +<%PRE| + + + + + + + + + + +%> + +As you can see, the html is already pre-rendered with the correct styling. If you actually +did intend on the code to remain javascript for whatever reason (perhaps you wanted it to +flash unstyled content??) then you would have to write the javascript in directly. + +Note that the output javascript may not look anything at all like you expected, as it +is run through several optimizations to ensure the least amount of code is actually +sent to the client, however, the behavior should be consistent. You can turn debug flags +on in the compiler, and it will output comments into the html explaining in more detail +where certain javascript came from, as well as how the compilation process happened, +so if you do find a bug in your code, it is easy to trace back to the root of the problem +by reverse engineering the javascript. Additionally, obfuscation, minification, and formatting +options can be set as well. + +Barring bugs in MethodScript itself, the output javascript is guaranteed to never throw any +(unintended) exceptions, or cause undefined behavior in the DOM. + +==== DOM Events ==== +Events are handled via individualized event handlers for each event type. Each element +has its own events available to it via javascript compiled MethodScript. A simple example +is a button that pops up an alert when it is clicked. + +Alert.mst: +<%PRE| + +
+ +
+%> + +This results in the following equivalent html (the actual html rendered will be +more complicated, but for this example, it has been simplified to equivalent javascript): + +<%PRE| + + + + + + + +

+ * According to the HTTP specification + * https://tools.ietf.org/html/rfc7230#section-3.2.2: + *

* * - * Characters in keys and elements can be represented in escape sequences - * similar to those used for character and string literals (see §3.3 * and §3.10.6 * of the Java Language Specification). * - * The differences from the character escape sequences and Unicode escapes - * used for characters and strings are: + * The differences from the character escape sequences and Unicode escapes used for characters and strings are: * *