From 157b10925d03593e4ab374fc9716b52dcc613ab2 Mon Sep 17 00:00:00 2001 From: ayunami2000 Date: Mon, 24 Jan 2022 00:04:50 -0500 Subject: [PATCH] xd --- lwjgl-rundir/_eagstorage.g.dat | Bin 1382 -> 1462 bytes .../auth/abstracts/AuthenticationToken.java | 7 + .../ratsiel/auth/abstracts/Authenticator.java | 23 + .../auth/abstracts/TextureVariable.java | 79 +++ .../exception/AuthenticationException.java | 17 + .../microsoft/MicrosoftAuthenticator.java | 331 +++++++++++ .../auth/model/microsoft/MicrosoftToken.java | 48 ++ .../auth/model/microsoft/XboxLiveToken.java | 49 ++ .../auth/model/microsoft/XboxToken.java | 24 + .../model/mojang/MinecraftAuthenticator.java | 188 +++++++ .../auth/model/mojang/MinecraftToken.java | 53 ++ .../model/mojang/profile/MinecraftCape.java | 31 + .../mojang/profile/MinecraftProfile.java | 82 +++ .../model/mojang/profile/MinecraftSkin.java | 61 ++ src/main/java/me/ratsiel/json/Json.java | 183 ++++++ .../java/me/ratsiel/json/JsonGenerator.java | 312 ++++++++++ src/main/java/me/ratsiel/json/JsonParser.java | 531 ++++++++++++++++++ .../ratsiel/json/abstracts/JsonHandler.java | 26 + .../me/ratsiel/json/abstracts/JsonValue.java | 80 +++ .../me/ratsiel/json/interfaces/IListable.java | 70 +++ .../java/me/ratsiel/json/model/JsonArray.java | 147 +++++ .../me/ratsiel/json/model/JsonBoolean.java | 77 +++ .../java/me/ratsiel/json/model/JsonNull.java | 53 ++ .../me/ratsiel/json/model/JsonNumber.java | 275 +++++++++ .../me/ratsiel/json/model/JsonObject.java | 215 +++++++ .../me/ratsiel/json/model/JsonString.java | 73 +++ .../eaglercraft/GuiScreenEditProfile.java | 42 +- .../java/net/minecraft/src/GameSettings.java | 1 + 28 files changed, 3071 insertions(+), 7 deletions(-) create mode 100644 src/main/java/me/ratsiel/auth/abstracts/AuthenticationToken.java create mode 100644 src/main/java/me/ratsiel/auth/abstracts/Authenticator.java create mode 100644 src/main/java/me/ratsiel/auth/abstracts/TextureVariable.java create mode 100644 src/main/java/me/ratsiel/auth/abstracts/exception/AuthenticationException.java create mode 100644 src/main/java/me/ratsiel/auth/model/microsoft/MicrosoftAuthenticator.java create mode 100644 src/main/java/me/ratsiel/auth/model/microsoft/MicrosoftToken.java create mode 100644 src/main/java/me/ratsiel/auth/model/microsoft/XboxLiveToken.java create mode 100644 src/main/java/me/ratsiel/auth/model/microsoft/XboxToken.java create mode 100644 src/main/java/me/ratsiel/auth/model/mojang/MinecraftAuthenticator.java create mode 100644 src/main/java/me/ratsiel/auth/model/mojang/MinecraftToken.java create mode 100644 src/main/java/me/ratsiel/auth/model/mojang/profile/MinecraftCape.java create mode 100644 src/main/java/me/ratsiel/auth/model/mojang/profile/MinecraftProfile.java create mode 100644 src/main/java/me/ratsiel/auth/model/mojang/profile/MinecraftSkin.java create mode 100644 src/main/java/me/ratsiel/json/Json.java create mode 100644 src/main/java/me/ratsiel/json/JsonGenerator.java create mode 100644 src/main/java/me/ratsiel/json/JsonParser.java create mode 100644 src/main/java/me/ratsiel/json/abstracts/JsonHandler.java create mode 100644 src/main/java/me/ratsiel/json/abstracts/JsonValue.java create mode 100644 src/main/java/me/ratsiel/json/interfaces/IListable.java create mode 100644 src/main/java/me/ratsiel/json/model/JsonArray.java create mode 100644 src/main/java/me/ratsiel/json/model/JsonBoolean.java create mode 100644 src/main/java/me/ratsiel/json/model/JsonNull.java create mode 100644 src/main/java/me/ratsiel/json/model/JsonNumber.java create mode 100644 src/main/java/me/ratsiel/json/model/JsonObject.java create mode 100644 src/main/java/me/ratsiel/json/model/JsonString.java diff --git a/lwjgl-rundir/_eagstorage.g.dat b/lwjgl-rundir/_eagstorage.g.dat index 7ed04232428416c3256aef22f5400621b6ace0ca..c8b9cb8d0b3597abda6b0a9e6baebdd70ef70802 100644 GIT binary patch delta 56 zcmaFHwT*kj1|~-4$s3s_O@7bBrl^pUpPZPJkzZV5Wn^k { + + protected final Json json = new Json(); + + /** + * Login string. + * + * @param email the email + * @param password the password + * @return the string + */ + public abstract T login(String email, String password); + + + +} diff --git a/src/main/java/me/ratsiel/auth/abstracts/TextureVariable.java b/src/main/java/me/ratsiel/auth/abstracts/TextureVariable.java new file mode 100644 index 0000000..54644ce --- /dev/null +++ b/src/main/java/me/ratsiel/auth/abstracts/TextureVariable.java @@ -0,0 +1,79 @@ +package me.ratsiel.auth.abstracts; + +/** + * The type Texture variable stores data from {@link me.ratsiel.auth.model.mojang.profile.MinecraftProfile} + */ +public abstract class TextureVariable { + + private String id; + private String state; + private String url; + private String alias; + + /** + * Instantiates a new Texture variable. + */ + public TextureVariable() { + } + + /** + * Instantiates a new Texture variable. + * + * @param id the id + * @param state the state + * @param url the url + * @param alias the alias + */ + public TextureVariable(String id, String state, String url, String alias) { + this.id = id; + this.state = state; + this.url = url; + this.alias = alias; + } + + /** + * Gets id. + * + * @return the id + */ + public String getId() { + return id; + } + + /** + * Gets state. + * + * @return the state + */ + public String getState() { + return state; + } + + /** + * Gets url. + * + * @return the url + */ + public String getUrl() { + return url; + } + + /** + * Gets alias. + * + * @return the alias + */ + public String getAlias() { + return alias; + } + + @Override + public String toString() { + return "TextureVariable{" + + "id='" + id + '\'' + + ", state='" + state + '\'' + + ", url='" + url + '\'' + + ", alias='" + alias + '\'' + + '}'; + } +} diff --git a/src/main/java/me/ratsiel/auth/abstracts/exception/AuthenticationException.java b/src/main/java/me/ratsiel/auth/abstracts/exception/AuthenticationException.java new file mode 100644 index 0000000..7a1592b --- /dev/null +++ b/src/main/java/me/ratsiel/auth/abstracts/exception/AuthenticationException.java @@ -0,0 +1,17 @@ +package me.ratsiel.auth.abstracts.exception; + +/** + * The class Authentication exception is thrown when something went wrong during authentication + */ +public class AuthenticationException extends RuntimeException { + + /** + * Instantiates a new Authentication exception. + * + * @param message the message + */ + public AuthenticationException(String message) { + super(message); + } + +} diff --git a/src/main/java/me/ratsiel/auth/model/microsoft/MicrosoftAuthenticator.java b/src/main/java/me/ratsiel/auth/model/microsoft/MicrosoftAuthenticator.java new file mode 100644 index 0000000..4210941 --- /dev/null +++ b/src/main/java/me/ratsiel/auth/model/microsoft/MicrosoftAuthenticator.java @@ -0,0 +1,331 @@ +package me.ratsiel.auth.model.microsoft; + +import me.ratsiel.auth.abstracts.Authenticator; +import me.ratsiel.auth.abstracts.exception.AuthenticationException; +import me.ratsiel.json.model.JsonArray; +import me.ratsiel.json.model.JsonObject; +import me.ratsiel.json.model.JsonString; + +import java.io.*; +import java.net.*; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.StringJoiner; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * The class Microsoft authenticator is used to log in into microsoft account and generate mojang JWT token + */ +public class MicrosoftAuthenticator extends Authenticator { + + protected final String clientId = "00000000402b5328"; + protected final String scopeUrl = "service::user.auth.xboxlive.com::MBI_SSL"; + + protected String loginUrl; + protected String loginCookie; + protected String loginPPFT; + + @Override + public XboxToken login(String email, String password) { + MicrosoftToken microsoftToken = generateTokenPair(generateLoginCode(email, password)); + XboxLiveToken xboxLiveToken = generateXboxTokenPair(microsoftToken); + return generateXboxTokenPair(xboxLiveToken); + } + + /** + * Generate start login code from email and password + * + * @param email microsoft email + * @param password microsoft password + * @return login code + */ + private String generateLoginCode(String email, String password) { + try { + URL url = new URL("https://login.live.com/oauth20_authorize.srf?redirect_uri=https://login.live.com/oauth20_desktop.srf&scope=" + scopeUrl + "&display=touch&response_type=code&locale=en&client_id=" + clientId); + HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection(); + InputStream inputStream = httpURLConnection.getResponseCode() == 200 ? httpURLConnection.getInputStream() : httpURLConnection.getErrorStream(); + + loginCookie = httpURLConnection.getHeaderField("set-cookie"); + + String responseData = new BufferedReader(new InputStreamReader(inputStream)).lines().collect(Collectors.joining()); + Matcher bodyMatcher = Pattern.compile("sFTTag:[ ]?'.*value=\"(.*)\"/>'").matcher(responseData); + if (bodyMatcher.find()) { + loginPPFT = bodyMatcher.group(1); + } else { + throw new AuthenticationException("Authentication error. Could not find 'LOGIN-PFTT' tag from response!"); + } + + bodyMatcher = Pattern.compile("urlPost:[ ]?'(.+?(?='))").matcher(responseData); + if (bodyMatcher.find()) { + loginUrl = bodyMatcher.group(1); + } else { + throw new AuthenticationException("Authentication error. Could not find 'LOGIN-URL' tag from response!"); + } + + if (loginCookie == null || loginPPFT == null || loginUrl == null) + throw new AuthenticationException("Authentication error. Error in authentication process!"); + + } catch (IOException exception) { + throw new AuthenticationException(String.format("Authentication error. Request could not be made! Cause: '%s'", exception.getMessage())); + } + + return sendCodeData(email, password); + } + + /** + * Send code data from email and password + * + * @param email microsoft email + * @param password microsoft password + * @return login code + */ + private String sendCodeData(String email, String password) { + String authToken; + + Map requestData = new HashMap<>(); + + requestData.put("login", email); + requestData.put("loginfmt", email); + requestData.put("passwd", password); + requestData.put("PPFT", loginPPFT); + + String postData = encodeURL(requestData); + + try { + byte[] data = postData.getBytes(StandardCharsets.UTF_8); + + HttpURLConnection connection = (HttpURLConnection) new URL(loginUrl).openConnection(); + connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=utf-8"); + connection.setRequestProperty("Content-Length", String.valueOf(data.length)); + connection.setRequestProperty("Cookie", loginCookie); + + connection.setDoInput(true); + connection.setDoOutput(true); + + try (OutputStream outputStream = connection.getOutputStream()) { + outputStream.write(data); + } + + if (connection.getResponseCode() != 200 || connection.getURL().toString().equals(loginUrl)) { + throw new AuthenticationException("Authentication error. Username or password is not valid."); + } + + Pattern pattern = Pattern.compile("[?|&]code=([\\w.-]+)"); + + Matcher tokenMatcher = pattern.matcher(URLDecoder.decode(connection.getURL().toString(), StandardCharsets.UTF_8.name())); + if (tokenMatcher.find()) { + authToken = tokenMatcher.group(1); + } else { + throw new AuthenticationException("Authentication error. Could not handle data from response."); + } + } catch (IOException exception) { + throw new AuthenticationException(String.format("Authentication error. Request could not be made! Cause: '%s'", exception.getMessage())); + } + + this.loginUrl = null; + this.loginCookie = null; + this.loginPPFT = null; + + return authToken; + } + + + /** + * Send xbox auth request + */ + private void sendXboxRequest(HttpURLConnection httpURLConnection, JsonObject request, JsonObject properties) throws IOException { + request.add("Properties", properties); + + String requestBody = request.toString(); + + httpURLConnection.setFixedLengthStreamingMode(requestBody.length()); + httpURLConnection.setRequestProperty("Content-Type", "application/json"); + httpURLConnection.setRequestProperty("Accept", "application/json"); + httpURLConnection.connect(); + try (OutputStream outputStream = httpURLConnection.getOutputStream()) { + outputStream.write(requestBody.getBytes(StandardCharsets.US_ASCII)); + } + } + + /** + * Generate {@link me.ratsiel.auth.model.mojang.MinecraftToken} + * + * @param authToken from {@link #generateLoginCode(String, String)} + * @return {@link me.ratsiel.auth.model.mojang.MinecraftToken} + */ + private MicrosoftToken generateTokenPair(String authToken) { + try { + Map arguments = new HashMap<>(); + arguments.put("client_id", clientId); + arguments.put("code", authToken); + arguments.put("grant_type", "authorization_code"); + arguments.put("redirect_uri", "https://login.live.com/oauth20_desktop.srf"); + arguments.put("scope", scopeUrl); + + StringJoiner argumentBuilder = new StringJoiner("&"); + for (Map.Entry entry : arguments.entrySet()) { + argumentBuilder.add(encodeURL(entry.getKey()) + "=" + encodeURL(entry.getValue())); + } + + byte[] data = argumentBuilder.toString().getBytes(StandardCharsets.UTF_8); + + URL url = new URL("https://login.live.com/oauth20_token.srf"); + URLConnection urlConnection = url.openConnection(); + HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection; + httpURLConnection.setRequestMethod("POST"); + httpURLConnection.setDoOutput(true); + httpURLConnection.setFixedLengthStreamingMode(data.length); + httpURLConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + httpURLConnection.connect(); + + try (OutputStream outputStream = httpURLConnection.getOutputStream()) { + outputStream.write(data); + } + + JsonObject jsonObject = parseResponseData(httpURLConnection); + + return new MicrosoftToken(jsonObject.get("access_token", JsonString.class).getValue(), jsonObject.get("refresh_token", JsonString.class).getValue()); + + } catch (IOException exception) { + throw new AuthenticationException(String.format("Authentication error. Request could not be made! Cause: '%s'", exception.getMessage())); + } + } + + /** + * Generate {@link XboxLiveToken} from {@link MicrosoftToken} + * + * @param microsoftToken the microsoft token + * @return the xbox live token + */ + public XboxLiveToken generateXboxTokenPair(MicrosoftToken microsoftToken) { + try { + URL url = new URL("https://user.auth.xboxlive.com/user/authenticate"); + URLConnection urlConnection = url.openConnection(); + HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection; + httpURLConnection.setDoOutput(true); + + JsonObject request = new JsonObject(); + request.add("RelyingParty", new JsonString("http://auth.xboxlive.com")); + request.add("TokenType", new JsonString("JWT")); + + JsonObject properties = new JsonObject(); + properties.add("AuthMethod", new JsonString("RPS")); + properties.add("SiteName", new JsonString("user.auth.xboxlive.com")); + properties.add("RpsTicket", new JsonString(microsoftToken.getToken())); + + sendXboxRequest(httpURLConnection, request, properties); + + JsonObject jsonObject = parseResponseData(httpURLConnection); + + String uhs = ((JsonObject) (jsonObject.get("DisplayClaims", JsonObject.class)).get("xui", JsonArray.class).get(0)).get("uhs", JsonString.class).getValue(); + return new XboxLiveToken(jsonObject.get("Token", JsonString.class).getValue(), uhs); + } catch (IOException exception) { + throw new AuthenticationException(String.format("Authentication error. Request could not be made! Cause: '%s'", exception.getMessage())); + } + } + + + /** + * Generate {@link XboxToken} from {@link XboxLiveToken} + * + * @param xboxLiveToken the xbox live token + * @return the xbox token + */ + public XboxToken generateXboxTokenPair(XboxLiveToken xboxLiveToken) { + try { + URL url = new URL("https://xsts.auth.xboxlive.com/xsts/authorize"); + URLConnection urlConnection = url.openConnection(); + HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection; + httpURLConnection.setRequestMethod("POST"); + httpURLConnection.setDoOutput(true); + + JsonObject request = new JsonObject(); + request.add("RelyingParty", new JsonString("rp://api.minecraftservices.com/")); + request.add("TokenType", new JsonString("JWT")); + + JsonObject properties = new JsonObject(); + properties.add("SandboxId", new JsonString("RETAIL")); + JsonArray userTokens = new JsonArray(); + userTokens.add(new JsonString(xboxLiveToken.getToken())); + properties.add("UserTokens", userTokens); + + sendXboxRequest(httpURLConnection, request, properties); + + if (httpURLConnection.getResponseCode() == 401) { + throw new AuthenticationException("No xbox account was found!"); + } + + JsonObject jsonObject = parseResponseData(httpURLConnection); + + String uhs = ((JsonObject) (jsonObject.get("DisplayClaims", JsonObject.class)).get("xui", JsonArray.class).get(0)).get("uhs", JsonString.class).getValue(); + return new XboxToken(jsonObject.get("Token", JsonString.class).getValue(), uhs); + } catch (IOException exception) { + throw new AuthenticationException(String.format("Authentication error. Request could not be made! Cause: '%s'", exception.getMessage())); + } + } + + /** + * Parse response data to {@link JsonObject} + * + * @param httpURLConnection the http url connection + * @return the json object + * @throws IOException the io exception + */ + public JsonObject parseResponseData(HttpURLConnection httpURLConnection) throws IOException { + BufferedReader bufferedReader; + + if (httpURLConnection.getResponseCode() != 200) { + bufferedReader = new BufferedReader(new InputStreamReader(httpURLConnection.getErrorStream())); + } else { + bufferedReader = new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream())); + } + String lines = bufferedReader.lines().collect(Collectors.joining()); + + JsonObject jsonObject = json.fromJsonString(lines, JsonObject.class); + if (jsonObject.has("error")) { + throw new AuthenticationException(jsonObject.get("error") + ": " + jsonObject.get("error_description")); + } + return jsonObject; + } + + + /** + * Encode url string. + * + * @param url the url + * @return the string + */ + private String encodeURL(String url) { + try { + return URLEncoder.encode(url, "UTF-8"); + } catch (UnsupportedEncodingException exception) { + throw new UnsupportedOperationException(exception); + } + } + + + /** + * Encode url string. + * + * @param map the map + * @return the string + */ + private String encodeURL(Map map) { + StringBuilder sb = new StringBuilder(); + for (Map.Entry entry : map.entrySet()) { + if (sb.length() > 0) { + sb.append("&"); + } + sb.append(String.format("%s=%s", + encodeURL(entry.getKey().toString()), + encodeURL(entry.getValue().toString()) + )); + } + return sb.toString(); + } + + +} diff --git a/src/main/java/me/ratsiel/auth/model/microsoft/MicrosoftToken.java b/src/main/java/me/ratsiel/auth/model/microsoft/MicrosoftToken.java new file mode 100644 index 0000000..04b3eaf --- /dev/null +++ b/src/main/java/me/ratsiel/auth/model/microsoft/MicrosoftToken.java @@ -0,0 +1,48 @@ +package me.ratsiel.auth.model.microsoft; + +import me.ratsiel.auth.abstracts.AuthenticationToken; + +/** + * The class Microsoft token stores token and refreshToken + */ +public class MicrosoftToken extends AuthenticationToken { + + protected String token; + protected String refreshToken; + + /** + * Instantiates a new Microsoft token. + */ + public MicrosoftToken() { + } + + /** + * Instantiates a new Microsoft token. + * + * @param token the token + * @param refreshToken the refresh token + */ + public MicrosoftToken(String token, String refreshToken) { + this.token = token; + this.refreshToken = refreshToken; + } + + /** + * Gets token. + * + * @return the token + */ + public String getToken() { + return token; + } + + /** + * Gets refresh token. + * + * @return the refresh token + */ + public String getRefreshToken() { + return refreshToken; + } + +} diff --git a/src/main/java/me/ratsiel/auth/model/microsoft/XboxLiveToken.java b/src/main/java/me/ratsiel/auth/model/microsoft/XboxLiveToken.java new file mode 100644 index 0000000..75ee6a7 --- /dev/null +++ b/src/main/java/me/ratsiel/auth/model/microsoft/XboxLiveToken.java @@ -0,0 +1,49 @@ +package me.ratsiel.auth.model.microsoft; + +import me.ratsiel.auth.abstracts.AuthenticationToken; + +/** + * The class Xbox live token stores token and uhs + */ +public class XboxLiveToken extends AuthenticationToken { + + protected String token; + protected String uhs; + + /** + * Instantiates a new Xbox live token. + */ + public XboxLiveToken() { + } + + /** + * Instantiates a new Xbox live token. + * + * @param token the token + * @param uhs the uhs + */ + public XboxLiveToken(String token, String uhs) { + this.token = token; + this.uhs = uhs; + } + + /** + * Gets token. + * + * @return the token + */ + public String getToken() { + return token; + } + + /** + * Gets uhs. + * + * @return the uhs + */ + public String getUhs() { + return uhs; + } + + +} diff --git a/src/main/java/me/ratsiel/auth/model/microsoft/XboxToken.java b/src/main/java/me/ratsiel/auth/model/microsoft/XboxToken.java new file mode 100644 index 0000000..57529d2 --- /dev/null +++ b/src/main/java/me/ratsiel/auth/model/microsoft/XboxToken.java @@ -0,0 +1,24 @@ +package me.ratsiel.auth.model.microsoft; + +/** + * The class xbox token has the same functions as {@link XboxLiveToken} + */ +public class XboxToken extends XboxLiveToken { + + /** + * Instantiates a new Xbox token. + */ + public XboxToken() { + } + + /** + * Instantiates a new Xbox token. + * + * @param token the token + * @param uhs the uhs + */ + public XboxToken(String token, String uhs) { + super(token, uhs); + } + +} diff --git a/src/main/java/me/ratsiel/auth/model/mojang/MinecraftAuthenticator.java b/src/main/java/me/ratsiel/auth/model/mojang/MinecraftAuthenticator.java new file mode 100644 index 0000000..ed7999a --- /dev/null +++ b/src/main/java/me/ratsiel/auth/model/mojang/MinecraftAuthenticator.java @@ -0,0 +1,188 @@ +package me.ratsiel.auth.model.mojang; + +import me.ratsiel.auth.abstracts.Authenticator; +import me.ratsiel.auth.abstracts.exception.AuthenticationException; +import me.ratsiel.auth.model.microsoft.MicrosoftAuthenticator; +import me.ratsiel.auth.model.microsoft.XboxToken; +import me.ratsiel.auth.model.mojang.profile.MinecraftCape; +import me.ratsiel.auth.model.mojang.profile.MinecraftProfile; +import me.ratsiel.auth.model.mojang.profile.MinecraftSkin; +import me.ratsiel.json.Json; +import me.ratsiel.json.model.*; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * The class Minecraft authenticator is used to log in with normal minecraft details or with microsoft + */ +public class MinecraftAuthenticator extends Authenticator { + + /** + * The Microsoft authenticator is used for {@link #loginWithXbox(String, String)} + */ + protected final MicrosoftAuthenticator microsoftAuthenticator = new MicrosoftAuthenticator(); + + @Override + public MinecraftToken login(String email, String password) { + try { + URL url = new URL("https://authserver.mojang.com/authenticate"); + URLConnection urlConnection = url.openConnection(); + HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection; + httpURLConnection.setRequestMethod("POST"); + httpURLConnection.setDoOutput(true); + + JsonObject request = new JsonObject(); + JsonObject agent = new JsonObject(); + agent.add(new JsonString("name", "Minecraft")); + agent.add(new JsonNumber("version", "1")); + request.add("agent", agent); + request.add(new JsonString("username", email)); + request.add(new JsonString("password", password)); + request.add(new JsonBoolean("requestUser", false)); + + + String requestBody = request.toString(); + + httpURLConnection.setFixedLengthStreamingMode(requestBody.length()); + httpURLConnection.setRequestProperty("Content-Type", "application/json"); + httpURLConnection.setRequestProperty("Host", "authserver.mojang.com"); + httpURLConnection.connect(); + + try (OutputStream outputStream = httpURLConnection.getOutputStream()) { + outputStream.write(requestBody.getBytes(StandardCharsets.US_ASCII)); + } + + JsonObject jsonObject = parseResponseData(httpURLConnection); + return new MinecraftToken(jsonObject.get("accessToken", JsonString.class).getValue(), ((JsonObject)jsonObject.get("selectedProfile")).get("name", JsonString.class).getValue()); + } catch (IOException exception) { + throw new AuthenticationException(String.format("Authentication error. Request could not be made! Cause: '%s'", exception.getMessage())); + } + + + } + + /** + * Login with microsoft email and microsoft password + * + * @param email the email + * @param password the password + * @return the minecraft token + */ + public MinecraftToken loginWithXbox(String email, String password) { + XboxToken xboxToken = microsoftAuthenticator.login(email, password); + + try { + URL url = new URL("https://api.minecraftservices.com/authentication/login_with_xbox"); + URLConnection urlConnection = url.openConnection(); + HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection; + httpURLConnection.setRequestMethod("POST"); + httpURLConnection.setDoOutput(true); + + JsonObject request = new JsonObject(); + request.add("identityToken", new JsonString("XBL3.0 x=" + xboxToken.getUhs() + ";" + xboxToken.getToken())); + + String requestBody = request.toString(); + + httpURLConnection.setFixedLengthStreamingMode(requestBody.length()); + httpURLConnection.setRequestProperty("Content-Type", "application/json"); + httpURLConnection.setRequestProperty("Host", "api.minecraftservices.com"); + httpURLConnection.connect(); + + try (OutputStream outputStream = httpURLConnection.getOutputStream()) { + outputStream.write(requestBody.getBytes(StandardCharsets.US_ASCII)); + } + + JsonObject jsonObject = microsoftAuthenticator.parseResponseData(httpURLConnection); + return new MinecraftToken(jsonObject.get("access_token", JsonString.class).getValue(), jsonObject.get("username", JsonString.class).getValue()); + } catch (IOException exception) { + throw new AuthenticationException(String.format("Authentication error. Request could not be made! Cause: '%s'", exception.getMessage())); + } + } + + /** + * Check ownership from {@link MinecraftToken} and generate {@link MinecraftProfile} + * + * @param minecraftToken the minecraft token + * @return the minecraft profile + */ + public MinecraftProfile checkOwnership(MinecraftToken minecraftToken) { + try { + URL url = new URL("https://api.minecraftservices.com/minecraft/profile"); + URLConnection urlConnection = url.openConnection(); + HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection; + httpURLConnection.setRequestMethod("GET"); + + httpURLConnection.setRequestProperty("Authorization", "Bearer " + minecraftToken.getAccessToken()); + httpURLConnection.setRequestProperty("Host", "api.minecraftservices.com"); + httpURLConnection.connect(); + + JsonObject jsonObject = parseResponseData(httpURLConnection); + + UUID uuid = generateUUID(jsonObject.get("id", JsonString.class).getValue()); + String name = jsonObject.get("name", JsonString.class).getValue(); + List minecraftSkins = json.fromJson(jsonObject.get("skins", JsonArray.class), List.class, MinecraftSkin.class); + List minecraftCapes = json.fromJson(jsonObject.get("capes", JsonArray.class), List.class, MinecraftCape.class); + + return new MinecraftProfile(uuid, name, minecraftSkins, minecraftCapes); + } catch (IOException exception) { + throw new AuthenticationException(String.format("Authentication error. Request could not be made! Cause: '%s'", exception.getMessage())); + } + } + + /** + * Parse response data to {@link JsonObject} + * + * @param httpURLConnection the http url connection + * @return the json object + * @throws IOException the io exception + */ + public JsonObject parseResponseData(HttpURLConnection httpURLConnection) throws IOException { + BufferedReader bufferedReader; + + if (httpURLConnection.getResponseCode() != 200) { + bufferedReader = new BufferedReader(new InputStreamReader(httpURLConnection.getErrorStream())); + } else { + bufferedReader = new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream())); + } + String lines = bufferedReader.lines().collect(Collectors.joining()); + + JsonObject jsonObject = json.fromJsonString(lines, JsonObject.class); + if (jsonObject.has("error")) { + throw new AuthenticationException(String.format("Could not find profile!. Error: '%s'", jsonObject.get("errorMessage", JsonString.class).getValue())); + } + return jsonObject; + } + + + /** + * Generate uuid from trimmedUUID + * + * @param trimmedUUID the trimmed uuid + * @return the uuid + * @throws IllegalArgumentException the illegal argument exception + */ + public UUID generateUUID(String trimmedUUID) throws IllegalArgumentException { + if (trimmedUUID == null) throw new IllegalArgumentException(); + StringBuilder builder = new StringBuilder(trimmedUUID.trim()); + try { + builder.insert(20, "-"); + builder.insert(16, "-"); + builder.insert(12, "-"); + builder.insert(8, "-"); + return UUID.fromString(builder.toString()); + } catch (StringIndexOutOfBoundsException e) { + return null; + } + } + +} diff --git a/src/main/java/me/ratsiel/auth/model/mojang/MinecraftToken.java b/src/main/java/me/ratsiel/auth/model/mojang/MinecraftToken.java new file mode 100644 index 0000000..88760d1 --- /dev/null +++ b/src/main/java/me/ratsiel/auth/model/mojang/MinecraftToken.java @@ -0,0 +1,53 @@ +package me.ratsiel.auth.model.mojang; + +/** + * The class Minecraft token stores minecraft accessToken and username + */ +public class MinecraftToken { + + private String accessToken; + private String username; + + /** + * Instantiates a new Minecraft token. + */ + public MinecraftToken() { + } + + /** + * Instantiates a new Minecraft token. + * + * @param accessToken the access token + * @param username the username + */ + public MinecraftToken(String accessToken, String username) { + this.accessToken = accessToken; + this.username = username; + } + + /** + * Gets access token. + * + * @return the access token + */ + public String getAccessToken() { + return accessToken; + } + + /** + * Gets username. + * + * @return the username + */ + public String getUsername() { + return username; + } + + @Override + public String toString() { + return "MinecraftToken{" + + "accessToken='" + accessToken + '\'' + + ", username='" + username + '\'' + + '}'; + } +} diff --git a/src/main/java/me/ratsiel/auth/model/mojang/profile/MinecraftCape.java b/src/main/java/me/ratsiel/auth/model/mojang/profile/MinecraftCape.java new file mode 100644 index 0000000..57dc9f9 --- /dev/null +++ b/src/main/java/me/ratsiel/auth/model/mojang/profile/MinecraftCape.java @@ -0,0 +1,31 @@ +package me.ratsiel.auth.model.mojang.profile; + +import me.ratsiel.auth.abstracts.TextureVariable; + +/** + * The class Minecraft cape stores cape data from {@link me.ratsiel.auth.model.mojang.profile.MinecraftProfile} + */ +public class MinecraftCape extends TextureVariable { + + /** + * Instantiates a new Minecraft cape. + */ + public MinecraftCape() { + } + + /** + * Instantiates a new Minecraft cape. + * + * @param id the id + * @param state the state + * @param url the url + * @param alias the alias + */ + public MinecraftCape(String id, String state, String url, String alias) { + super(id, state, url, alias); + } + + + + +} diff --git a/src/main/java/me/ratsiel/auth/model/mojang/profile/MinecraftProfile.java b/src/main/java/me/ratsiel/auth/model/mojang/profile/MinecraftProfile.java new file mode 100644 index 0000000..ac08c4e --- /dev/null +++ b/src/main/java/me/ratsiel/auth/model/mojang/profile/MinecraftProfile.java @@ -0,0 +1,82 @@ +package me.ratsiel.auth.model.mojang.profile; + +import java.util.List; +import java.util.UUID; + +/** + * The class Minecraft profile stores uuid, username, skins and capes + */ +public class MinecraftProfile { + + private UUID uuid; + private String username; + private List skins; + private List capes; + + /** + * Instantiates a new Minecraft profile. + */ + public MinecraftProfile() { + } + + /** + * Instantiates a new Minecraft profile. + * + * @param uuid the uuid + * @param username the username + * @param skins the skins + * @param capes the capes + */ + public MinecraftProfile(UUID uuid, String username, List skins, List capes) { + this.uuid = uuid; + this.username = username; + this.skins = skins; + this.capes = capes; + } + + /** + * Gets uuid. + * + * @return the uuid + */ + public UUID getUuid() { + return uuid; + } + + /** + * Gets username. + * + * @return the username + */ + public String getUsername() { + return username; + } + + /** + * Gets skins. + * + * @return the skins + */ + public List getSkins() { + return skins; + } + + /** + * Gets capes. + * + * @return the capes + */ + public List getCapes() { + return capes; + } + + @Override + public String toString() { + return "MinecraftProfile{" + + "uuid=" + uuid + + ", username='" + username + '\'' + + ", skins=" + skins + + ", capes=" + capes + + '}'; + } +} diff --git a/src/main/java/me/ratsiel/auth/model/mojang/profile/MinecraftSkin.java b/src/main/java/me/ratsiel/auth/model/mojang/profile/MinecraftSkin.java new file mode 100644 index 0000000..c8d9ec2 --- /dev/null +++ b/src/main/java/me/ratsiel/auth/model/mojang/profile/MinecraftSkin.java @@ -0,0 +1,61 @@ +package me.ratsiel.auth.model.mojang.profile; + +import me.ratsiel.auth.abstracts.TextureVariable; + +/** + * The class Minecraft skin stores cape data from {@link me.ratsiel.auth.model.mojang.profile.MinecraftProfile} + */ +public class MinecraftSkin extends TextureVariable { + + private String variant; + + /** + * Instantiates a new Minecraft skin. + */ + public MinecraftSkin() { + } + + /** + * Instantiates a new Minecraft skin. + * + * @param variant the variant + */ + public MinecraftSkin(String variant) { + this.variant = variant; + } + + /** + * Instantiates a new Minecraft skin. + * + * @param id the id + * @param state the state + * @param url the url + * @param alias the alias + * @param variant the variant + */ + public MinecraftSkin(String id, String state, String url, String alias, String variant) { + super(id, state, url, alias); + this.variant = variant; + } + + /** + * Gets variant. + * + * @return the variant + */ + public String getVariant() { + return variant; + } + + + @Override + public String toString() { + return "MinecraftSkin{" + + "id='" + getId() + '\'' + + ", state='" + getState() + '\'' + + ", url='" + getUrl() + '\'' + + ", alias='" + getAlias() + '\'' + + "variant='" + variant + '\'' + + '}'; + } +} diff --git a/src/main/java/me/ratsiel/json/Json.java b/src/main/java/me/ratsiel/json/Json.java new file mode 100644 index 0000000..46a9f08 --- /dev/null +++ b/src/main/java/me/ratsiel/json/Json.java @@ -0,0 +1,183 @@ +package me.ratsiel.json; + +import me.ratsiel.json.abstracts.JsonHandler; +import me.ratsiel.json.abstracts.JsonValue; +import me.ratsiel.json.model.JsonString; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.util.List; +import java.util.UUID; + +/** + * The type Json. + */ +public class Json { + + private final JsonGenerator jsonGenerator = new JsonGenerator(); + private final JsonParser jsonParser = new JsonParser(); + + public Json() { + registerHandler(UUID.class, new JsonHandler() { + @Override + public UUID serialize(JsonValue jsonValue) { + JsonString jsonString = (JsonString) jsonValue; + return UUID.fromString(jsonString.getValue()); + } + + @Override + public JsonValue deserialize(UUID value) { + return new JsonString(value.toString()); + } + }); + } + + /** + * Parse {@link String} to {@link JsonValue}. + * + * @param json the json used to parse + * @return the json value is the raw return type + */ + public JsonValue fromJsonString(String json) { + return jsonParser.parse(json); + } + + /** + * Parse {@link String} to {@link JsonValue} and cast it to {@link Class} + * + * @param the type parameter determines the type of {@link JsonValue} + * @param json the input stream is used to parse + * @param clazz the clazz is used to cast {@link T} + * @return the value {@link T} + */ + public T fromJsonString(String json, Class clazz) { + return jsonParser.parse(json, clazz); + } + + + /** + * Parse {@link File} to {@link JsonValue} and cast it to {@link Class} + * + * @param the type parameter determines the type of {@link JsonValue} + * @param file the file is used to parse + * @param clazz the clazz is used to cast {@link T} + * @return the value {@link T} + * @throws FileNotFoundException the file not found exception + */ + public T fromFile(File file, Class clazz) throws FileNotFoundException { + return clazz.cast(jsonParser.parse(file)); + } + + /** + * Parse {@link File} to {@link JsonValue}. + * + * @param file the file used to parse + * @return the json value raw return type + * @throws FileNotFoundException the file not found exception + */ + public JsonValue fromFile(File file) throws FileNotFoundException { + return jsonParser.parse(new FileInputStream(file)); + } + + + /** + * Parse {@link InputStream} to {@link JsonValue} and cast it to {@link Class} + * + * @param the type parameter determines the type of {@link JsonValue} + * @param inputStream the input stream is used to parse + * @param clazz the clazz is used to cast {@link T} + * @return the value {@link T} + */ + public T parse(InputStream inputStream, Class clazz) { + return jsonParser.parse(inputStream, clazz); + } + + /** + * Parse json value with {@link InputStream} + * + * @param inputStream the input stream is used to parse + * @return the json value is the raw return type + */ + public JsonValue parse(InputStream inputStream) { + return jsonParser.parse(inputStream); + } + + /** + * To json value. + * Parse object of type {@link T} to {@link JsonValue} + * + * @param the type parameter used to determine value + * @param value the value used to parse + * @return the json value returns raw type of {@link JsonValue} + */ + public JsonValue toJson(T value) { + return jsonGenerator.toJson(value); + } + + + /** + * From json to list. + * Convert {@link JsonValue} to {@link T} and cast with {@link Class} + * + * @param the type parameter determines object type + * @param the type parameter is the class that is used t ocast + * @param jsonValue the json value is used to convert + * @param listClazz the list clazz is used to cast {@link T} + * @param clazz the clazz is used to determine object in {@link List} + * @return T is a list with cast {@link Class} + */ + public , K> T fromJson(JsonValue jsonValue, Class listClazz, Class clazz) { + return jsonGenerator.fromJson(jsonValue, listClazz, clazz); + } + + /** + * From json with cast. + * Convert {@link JsonValue} to {@link T} + * + * @param the type parameter determines type of return object + * @param jsonValue the json value is used to convert to {@link T} + * @param clazz the clazz is used to cast {@link T} + * @return {@link T} cast with {@link Class} + */ + public T fromJsonCast(JsonValue jsonValue, Class clazz) { + return jsonGenerator.fromJsonCast(jsonValue, clazz); + } + + /** + * From json. + * Convert {@link JsonValue} to raw object + * + * @param the type parameter determines type of return object + * @param jsonValue the json value is used to convert to {@link Object} + * @param clazz the clazz is used create object + * @return the object + */ + public Object fromJson(JsonValue jsonValue, Class clazz) { + return jsonGenerator.fromJson(jsonValue, clazz); + } + + + /** + * Register handler. + * Register a {@link JsonHandler} by {@link Class} + * + * @param clazz the clazz used to put {@link Class} and {@link JsonHandler} in {@link #jsonGenerator} + * @param jsonHandler the json handler is used to insert + */ + public void registerHandler(Class clazz, JsonHandler jsonHandler) { + jsonGenerator.registerHandler(clazz, jsonHandler); + } + + /** + * Unregister handler. + * Unregister a {@link JsonHandler} by {@link Class} + * + * @param clazz the clazz used to remove {@link JsonHandler} from {@link #jsonGenerator} + */ + public void unregisterHandler(Class clazz) { + jsonGenerator.unregisterHandler(clazz); + } + +} diff --git a/src/main/java/me/ratsiel/json/JsonGenerator.java b/src/main/java/me/ratsiel/json/JsonGenerator.java new file mode 100644 index 0000000..1c85e2b --- /dev/null +++ b/src/main/java/me/ratsiel/json/JsonGenerator.java @@ -0,0 +1,312 @@ +package me.ratsiel.json; + +import me.ratsiel.json.abstracts.JsonHandler; +import me.ratsiel.json.abstracts.JsonValue; +import me.ratsiel.json.model.*; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * The class Json generator is used to parse json to a class or object to json. + */ +public class JsonGenerator { + + /** + * The Handlers is {@link Map} with key {@link Class} and {@link JsonHandler} + */ + protected final HashMap, JsonHandler> handlers = new HashMap<>(); + + /** + * To json value. + * Parse object of type {@link T} to {@link JsonValue} + * + * @param the type parameter used to determine value + * @param value the value used to parse + * @return the json value returns raw type of {@link JsonValue} + */ + public JsonValue toJson(T value) { + JsonValue jsonValue; + if (value == null) { + jsonValue = new JsonNull(); + return jsonValue; + } + + JsonHandler jsonHandler = getHandler(value.getClass()); + + if (jsonHandler != null) { + return jsonHandler.deserialize(value); + } + + Class clazz = value.getClass(); + + if (String.class.isAssignableFrom(clazz)) { + jsonValue = new JsonString(); + ((JsonString) jsonValue).setValue((String) value); + return jsonValue; + } else if (Enum.class.isAssignableFrom(clazz)) { + jsonValue = new JsonString(); + ((JsonString) jsonValue).setValue(((Enum) value).name()); + return jsonValue; + } else if (Number.class.isAssignableFrom(clazz)) { + jsonValue = new JsonNumber(); + ((JsonNumber) jsonValue).setValue(value.toString()); + return jsonValue; + } else if (Boolean.class.isAssignableFrom(clazz)) { + jsonValue = new JsonBoolean(); + ((JsonBoolean) jsonValue).setValue((Boolean) value); + return jsonValue; + } else if (List.class.isAssignableFrom(clazz)) { + JsonArray jsonArray = new JsonArray(); + List list = (List) value; + for (Object listValue : list) { + jsonArray.add(toJson(listValue)); + } + return jsonArray; + } else if (Map.class.isAssignableFrom(clazz)) { + JsonObject jsonObject = new JsonObject(); + Map map = (Map) value; + + for (Map.Entry entry : map.entrySet()) { + Object key = entry.getKey(); + Object mapValue = entry.getValue(); + if (key == null) { + jsonObject.add("null", toJson(mapValue)); + continue; + } + + Class keyClazz = key.getClass(); + if (String.class.isAssignableFrom(keyClazz) + || Number.class.isAssignableFrom(keyClazz) + || Boolean.class.isAssignableFrom(keyClazz)) { + jsonObject.add(String.valueOf(key), toJson(mapValue)); + } else if (Enum.class.isAssignableFrom(keyClazz)) { + jsonObject.add(((Enum) key).name(), toJson(mapValue)); + } + + } + return jsonObject; + } else { + JsonObject jsonObject = new JsonObject(); + + if (hasSuperclass(clazz)) { + for (Field declaredField : clazz.getSuperclass().getDeclaredFields()) { + populateFields(value, jsonObject, declaredField); + } + } + + for (Field declaredField : clazz.getDeclaredFields()) { + populateFields(value, jsonObject, declaredField); + } + + jsonValue = jsonObject; + } + + + return jsonValue; + } + + private void populateFields(T value, JsonObject jsonObject, Field declaredField) { + if(declaredField.getModifiers() == Modifier.STATIC) return; + + declaredField.setAccessible(true); + try { + JsonValue transformedValue = toJson(declaredField.get(value)); + transformedValue.setKey(declaredField.getName()); + + jsonObject.add(transformedValue); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + /** + * From json to list. + * Convert {@link JsonValue} to {@link T} and cast with {@link Class} + * + * @param the type parameter determines object type + * @param the type parameter is the class that is used t ocast + * @param jsonValue the json value is used to convert + * @param listClazz the list clazz is used to cast {@link T} + * @param clazz the clazz is used to determine object in {@link List} + * @return T is a list with cast {@link Class} + */ + public , K> T fromJson(JsonValue jsonValue, Class listClazz, Class clazz) { + return listClazz.cast(fromJson(null, jsonValue, clazz)); + } + + /** + * From json with cast. + * Convert {@link JsonValue} to {@link T} + * + * @param the type parameter determines type of return object + * @param jsonValue the json value is used to convert to {@link T} + * @param clazz the clazz is used to cast {@link T} + * @return {@link T} cast with {@link Class} + */ + public T fromJsonCast(JsonValue jsonValue, Class clazz) { + return clazz.cast(fromJson(null, jsonValue, clazz)); + } + + /** + * From json. + * Convert {@link JsonValue} to raw object + * + * @param the type parameter determines type of return object + * @param jsonValue the json value is used to convert to {@link Object} + * @param clazz the clazz is used create object + * @return the object + */ + public Object fromJson(JsonValue jsonValue, Class clazz) { + return fromJson(null, jsonValue, clazz); + } + + /** + * From json. + * Convert {@link JsonValue} to raw object + * + * @param the type parameter determines type of return object + * @param key the key is used to get a value out of {@link JsonObject} + * @param jsonValue the json value is used to convert to {@link Object} + * @param clazz the clazz is used create object + * @return the object + */ + public Object fromJson(String key, JsonValue jsonValue, Class clazz) { + JsonHandler jsonHandler = getHandler(clazz); + + if (jsonValue instanceof JsonString) { + if (jsonHandler != null) { + return jsonHandler.serialize(jsonValue); + } + + return clazz.cast(((JsonString) jsonValue).getValue()); + } else if (jsonValue instanceof JsonNumber) { + + if (jsonHandler != null) { + return jsonHandler.serialize(jsonValue); + } + + return ((JsonNumber) jsonValue).getNumber(clazz); + } else if (jsonValue instanceof JsonBoolean) { + + if (jsonHandler != null) { + return jsonHandler.serialize(jsonValue); + } + + return clazz.cast(((JsonBoolean) jsonValue).isValue()); + } else if (jsonValue instanceof JsonNull) { + + if (jsonHandler != null) { + return jsonHandler.serialize(jsonValue); + } + + return null; + } else if (jsonValue instanceof JsonObject) { + if (key != null) { + return fromJson(((JsonObject) jsonValue).get(key), clazz); + } else { + try { + T object = clazz.newInstance(); + + if (jsonHandler != null) { + return jsonHandler.serialize(jsonValue); + } + + Class objectClazz = object.getClass(); + + if (hasSuperclass(objectClazz)) { + for (Field declaredField : objectClazz.getSuperclass().getDeclaredFields()) { + declaredField.setAccessible(true); + declaredField.set(object, fromJson(declaredField.getName(), jsonValue, declaredField.getType())); + } + } + + for (Field declaredField : objectClazz.getDeclaredFields()) { + declaredField.setAccessible(true); + declaredField.set(object, fromJson(declaredField.getName(), jsonValue, declaredField.getType())); + } + + return object; + } catch (InstantiationException | IllegalAccessException e) { + e.printStackTrace(); + } + + } + + } else if (jsonValue instanceof JsonArray) { + List list = new ArrayList(); + ((JsonArray) jsonValue).loop((integer, listValue) -> { + list.add(fromJson(listValue, clazz)); + }); + return list; + } + + + return null; + } + + /** + * Has superclass boolean. + * Checks if given class has a superclass + * + * @param the type parameter determines type of class used in class + * @param clazz the clazz is used to check + * @return the boolean true or false + */ + public boolean hasSuperclass(Class clazz) { + return clazz.getSuperclass() != null; + } + + /** + * Register handler. + * Register a {@link JsonHandler} by {@link Class} + * + * @param clazz the clazz used to put {@link Class} and {@link JsonHandler} in {@link #handlers} + * @param jsonHandler the json handler is used to insert + */ + public void registerHandler(Class clazz, JsonHandler jsonHandler) { + if (!isRegistered(clazz)) { + handlers.put(clazz, jsonHandler); + } + } + + /** + * Unregister handler. + * Unregister a {@link JsonHandler} by {@link Class} + * + * @param clazz the clazz used to remove {@link JsonHandler} from {@link #handlers} + */ + public void unregisterHandler(Class clazz) { + if (isRegistered(clazz)) { + handlers.remove(clazz); + } + } + + /** + * Gets handler from {@link #handlers}. + * + * @param clazz the clazz used to get {@link JsonHandler} from {@link #handlers} + * @return the handler null or {@link JsonHandler} + */ + public JsonHandler getHandler(Class clazz) { + if (!isRegistered(clazz)) return null; + + return handlers.get(clazz); + } + + /** + * Is registered boolean. + * Check if {@link JsonHandler} is registered in {@link #handlers} + * + * @param clazz the clazz used to get {@link JsonHandler} + * @return the boolean true or false + */ + public boolean isRegistered(Class clazz) { + return handlers.containsKey(clazz); + } + +} diff --git a/src/main/java/me/ratsiel/json/JsonParser.java b/src/main/java/me/ratsiel/json/JsonParser.java new file mode 100644 index 0000000..8381069 --- /dev/null +++ b/src/main/java/me/ratsiel/json/JsonParser.java @@ -0,0 +1,531 @@ +package me.ratsiel.json; + +import me.ratsiel.json.abstracts.JsonValue; +import me.ratsiel.json.model.*; + +import java.io.*; +import java.nio.charset.StandardCharsets; + +/** + * The class Json parser is used to parse a {@link String} or a {@link File} to {@link JsonValue} + */ +public class JsonParser { + + private char[] jsonBuffer; + private int jsonBufferPosition = 0; + + /** + * Parse {@link InputStream} to {@link JsonValue} and cast it to {@link Class} + * + * @param the type parameter determines the type of {@link JsonValue} + * @param inputStream the input stream is used to parse + * @param clazz the clazz is used to cast {@link T} + * @return the value {@link T} + */ + public T parse(InputStream inputStream, Class clazz) { + return clazz.cast(parse(inputStream)); + } + + /** + * Parse json value with {@link InputStream} + * + * @param inputStream the input stream is used to parse + * @return the json value is the raw return type + */ + public JsonValue parse(InputStream inputStream) { + try { + prepareParser(inputStream); + } catch (IOException exception) { + exception.printStackTrace(); + } + + skipWhiteSpace(); + switch (peekChar()) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '.': + case 'E': + case 'e': + case '+': + case '-': { + return parseJsonNumber(); + } + case 'f': + case 't': { + return parseJsonBoolean(); + } + case 'n': { + return parseJsonNull(); + } + case '"': { + return parseJsonString(); + } + case '{': { + JsonObject jsonObject = parseJsonObject(); + jsonBuffer = null; + jsonBufferPosition = 0; + return jsonObject; + } + case '[': { + JsonArray jsonArray = parseJsonArray(); + jsonBuffer = null; + jsonBufferPosition = 0; + return jsonArray; + } + default: { + throw new RuntimeException("Could not parse file to Json!"); + } + } + } + + + /** + * Parse {@link String} to {@link JsonValue} and cast it to {@link Class} + * + * @param the type parameter determines the type of {@link JsonValue} + * @param json the input stream is used to parse + * @param clazz the clazz is used to cast {@link T} + * @return the value {@link T} + */ + public T parse(String json, Class clazz) { + return clazz.cast(parse(json)); + } + + /** + * Parse {@link String} to {@link JsonValue}. + * + * @param json the json used to parse + * @return the json value is the raw return type + */ + public JsonValue parse(String json) { + return parse(new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8))); + } + + + /** + * Parse {@link File} to {@link JsonValue} and cast it to {@link Class} + * + * @param the type parameter determines the type of {@link JsonValue} + * @param file the file is used to parse + * @param clazz the clazz is used to cast {@link T} + * @return the value {@link T} + * @throws FileNotFoundException the file not found exception + */ + public T parse(File file, Class clazz) throws FileNotFoundException { + return clazz.cast(parse(file)); + } + + /** + * Parse {@link File} to {@link JsonValue}. + * + * @param file the file used to parse + * @return the json value raw return type + * @throws FileNotFoundException the file not found exception + */ + public JsonValue parse(File file) throws FileNotFoundException { + return parse(new FileInputStream(file)); + } + + + /** + * Prepare parser to parse given input to {@link JsonValue}. + * + * @param inputStream the input stream is used to parse + * @throws IOException the io exception + */ + public void prepareParser(InputStream inputStream) throws IOException { + StringBuilder stringBuilder = new StringBuilder(); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); + + String line; + while ((line = bufferedReader.readLine()) != null) { + stringBuilder.append(line).append("\n"); + } + + jsonBuffer = stringBuilder.toString().toCharArray(); + bufferedReader.close(); + } + + /** + * Parse char from {@link #jsonBuffer} and increase {@link #jsonBufferPosition} + * + * @return the char + */ + public char parseChar() { + try { + return jsonBuffer[jsonBufferPosition++]; + } catch (IndexOutOfBoundsException exception) { + return (char) -1; + } + } + + /** + * Peek char with {@link #parseChar()} and revoke {@link #jsonBufferPosition} + * + * @return the char + */ + public char peekChar() { + char currentChar = parseChar(); + jsonBufferPosition--; + return currentChar; + } + + /** + * Skip white space. + */ + public void skipWhiteSpace() { + while (isWhiteSpace()) { + parseChar(); + } + } + + + /** + * Parse json object json object. + * Iterates through chars and construct json object. + * + * @return the json object + */ + public JsonObject parseJsonObject() { + JsonObject jsonObject = new JsonObject(); + + skipWhiteSpace(); + // Skip '{' + parseChar(); + + char currentChar = peekChar(); + while (currentChar != '}') { + skipWhiteSpace(); + + // Check the next char and handle it! + switch (peekChar()) { + case '"': { + + String key = parseJsonString().getValue(); + + skipWhiteSpace(); + // Skip ':' + currentChar = parseChar(); + skipWhiteSpace(); + + switch (peekChar()) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '.': + case 'E': + case 'e': + case '+': + case '-': { + JsonNumber jsonNumber = parseJsonNumber(); + jsonNumber.setKey(key); + jsonObject.add(jsonNumber); + break; + } + case 'f': + case 't': { + JsonBoolean jsonBoolean = parseJsonBoolean(); + jsonBoolean.setKey(key); + jsonObject.add(jsonBoolean); + break; + } + case 'n': { + JsonNull jsonNull = parseJsonNull(); + jsonNull.setKey(key); + jsonObject.add(jsonNull); + break; + } + case '"': { + JsonString jsonString = parseJsonString(); + jsonString.setKey(key); + jsonObject.add(jsonString); + break; + } + case '{': { + JsonObject object = parseJsonObject(); + object.setKey(key); + jsonObject.add(object); + break; + } + case '[': { + JsonArray jsonArray = parseJsonArray(); + jsonArray.setKey(key); + jsonObject.add(jsonArray); + break; + } + } + + break; + } + case '}': + case ',': { + currentChar = parseChar(); + break; + } + default: { + throw new RuntimeException(String.format("Could not parse JsonObject stuck at Index: %s as Char: %s", jsonBufferPosition, jsonBuffer[jsonBufferPosition])); + } + + } + } + return jsonObject; + } + + /** + * Parse json array json array. + * Iterates through chars and construct json array. + * + * @return the json array + */ + public JsonArray parseJsonArray() { + JsonArray jsonArray = new JsonArray(); + + skipWhiteSpace(); + // Skip '[' + parseChar(); + + char currentChar = peekChar(); + + + while (currentChar != ']') { + skipWhiteSpace(); + + // Check the next char and handle it! + switch (peekChar()) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '.': + case 'E': + case 'e': + case '+': + case '-': { + jsonArray.add(parseJsonNumber()); + break; + } + case '"': { + jsonArray.add(parseJsonString()); + break; + } + case 'f': + case 't': { + jsonArray.add(parseJsonBoolean()); + break; + } + case 'n': { + jsonArray.add(parseJsonNull()); + break; + } + case '{': { + jsonArray.add(parseJsonObject()); + break; + } + case '[': { + jsonArray.add(parseJsonArray()); + break; + } + case ']': + case ',': { + currentChar = parseChar(); + break; + } + default: { + throw new RuntimeException(String.format("Could not parse JsonArray stuck at Index: %s as Char: %s", jsonBufferPosition, jsonBuffer[jsonBufferPosition])); + } + } + } + + return jsonArray; + } + + /** + * Parse json string. + * Iterates through chars and construct json string. + * + * @return the json string + */ + public JsonString parseJsonString() { + JsonString jsonString = new JsonString(); + + StringBuilder stringBuilder = new StringBuilder(); + + skipWhiteSpace(); + + + // Take the first '"' + parseChar(); + + char currentChar = parseChar(); + + while (currentChar != '"') { + // Check if in a string is an escape char + if (currentChar == '\\') { + stringBuilder.append(currentChar); + currentChar = parseChar(); + } + + stringBuilder.append(currentChar); + currentChar = parseChar(); + } + + skipWhiteSpace(); + + jsonString.setValue(stringBuilder.toString()); + + return jsonString; + } + + /** + * Parse json null. + * Iterates through chars and check if json null is given. + * + * @return the json null + */ + public JsonNull parseJsonNull() { + char currentChar = parseChar(); + if (currentChar == 'n' && isNextChar('u', 0) && isNextChar('l', 1) && isNextChar('l', 2)) { + for (int i = 0; i < 3; i++) { + jsonBufferPosition++; + } + return new JsonNull(); + } + return null; + } + + /** + * Parse json boolean. + * Iterates through chars and check if json boolean is given. + * + * @return the json boolean + */ + public JsonBoolean parseJsonBoolean() { + char currentChar = parseChar(); + JsonBoolean jsonBoolean = null; + if (currentChar == 't' && isNextChar('r', 0) && isNextChar('u', 1) && isNextChar('e', 2)) { + for (int i = 0; i < 3; i++) { + jsonBufferPosition++; + } + jsonBoolean = new JsonBoolean(); + jsonBoolean.setValue(true); + } else if (currentChar == 'f' && isNextChar('a', 0) && isNextChar('l', 1) && isNextChar('s', 2) && isNextChar('e', 3)) { + for (int i = 0; i < 4; i++) { + jsonBufferPosition++; + } + jsonBoolean = new JsonBoolean(); + jsonBoolean.setValue(false); + } + return jsonBoolean; + } + + /** + * Parse json number. + * Iterates through chars and check if json number is given. + * + * @return the json number + */ + public JsonNumber parseJsonNumber() { + StringBuilder stringBuilder = new StringBuilder(); + + char currentChar = parseChar(); + + while (isNumber(currentChar)) { + stringBuilder.append(currentChar); + + currentChar = parseChar(); + } + + jsonBufferPosition--; + + JsonNumber jsonNumber = new JsonNumber(); + jsonNumber.setValue(stringBuilder.toString()); + + return jsonNumber; + } + + /** + * Is white space boolean. + * Check if current char in {@link #jsonBuffer} is a whitespace + * + * @return the boolean true or false + */ + public boolean isWhiteSpace() { + char currentChar = jsonBuffer[jsonBufferPosition]; + return currentChar == ' ' || currentChar == '\t' || currentChar == '\r' || currentChar == '\n'; + } + + /** + * Is next char boolean. + * Check if next char is given char by position + * + * @param currentChar the current char is the char that is going to be checked + * @param position the position used to get char at position from {@link #jsonBuffer} + * @return the boolean true or false + */ + public boolean isNextChar(char currentChar, int position) { + for (int i = 0; i < position; i++) { + parseChar(); + } + char nextChar = peekChar(); + for (int i = 0; i < position; i++) { + jsonBufferPosition--; + } + return nextChar == currentChar; + } + + /** + * Is number boolean. + * Check if current char is a number + * @param currentChar the current char is used to check + * @return the boolean true or false + */ + public boolean isNumber(char currentChar) { + char[] numberElements = new char[]{ + '0', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + '.', + 'E', + 'e', + '+', + '-' + }; + + for (char listChar : numberElements) { + if (listChar == currentChar) { + return true; + } + } + return false; + } + + +} diff --git a/src/main/java/me/ratsiel/json/abstracts/JsonHandler.java b/src/main/java/me/ratsiel/json/abstracts/JsonHandler.java new file mode 100644 index 0000000..9b5ede0 --- /dev/null +++ b/src/main/java/me/ratsiel/json/abstracts/JsonHandler.java @@ -0,0 +1,26 @@ +package me.ratsiel.json.abstracts; + +/** + * The abstract class {@link JsonHandler} with generic type {@link T} is used to serialize and deserialize an object + * + * @param the type parameter type of object which is going to be serialized or deserialized. + */ +public abstract class JsonHandler { + + /** + * Serialize {@link JsonValue} to object of type {@link T} + * + * @param jsonValue the json value can be a value like something {@link me.ratsiel.json.model.JsonObject} or {@link me.ratsiel.json.model.JsonArray} + * @return the t + */ + public abstract T serialize(JsonValue jsonValue); + + /** + * Deserialize {@link T} to {@link JsonValue} + * + * @param value the value is an object of {@link T} + * @return the json value is the deserialized value of {@link T} + */ + public abstract JsonValue deserialize(T value); + +} diff --git a/src/main/java/me/ratsiel/json/abstracts/JsonValue.java b/src/main/java/me/ratsiel/json/abstracts/JsonValue.java new file mode 100644 index 0000000..e0e2ea5 --- /dev/null +++ b/src/main/java/me/ratsiel/json/abstracts/JsonValue.java @@ -0,0 +1,80 @@ +package me.ratsiel.json.abstracts; + +/** + * The class {@link JsonValue} is an upper class for Json values like {@link me.ratsiel.json.model.JsonObject} + */ +public abstract class JsonValue { + + private int intend = 0; + private String key; + + /** + * Instantiates a new Json value. + */ + public JsonValue() { + } + + /** + * Instantiates a new Json value. + * + * @param key the key is used to find a document in {@link me.ratsiel.json.model.JsonObject} + */ + public JsonValue(String key) { + this.key = key; + } + + /** + * Create space string. + * Used to generate the spaces when a object of type {@link JsonValue} is used with {@link #toString()} + * + * @return the string + */ + protected String createSpace() { + StringBuilder spaceBuilder = new StringBuilder(); + for (int i = 0; i < intend; i++) { + spaceBuilder.append(" "); + } + return spaceBuilder.toString(); + } + + @Override + public abstract String toString(); + + /** + * Gets key. + * + * @return key the key is used to find a document in {@link me.ratsiel.json.model.JsonObject} + */ + public String getKey() { + return key; + } + + /** + * Sets key. + * + * @param key the key is used to find a document in {@link me.ratsiel.json.model.JsonObject} + */ + public void setKey(String key) { + this.key = key; + } + + + /** + * Gets intend. + * + * @return the intend used to calculate free space in method {@link #createSpace()} + */ + public int getIntend() { + return intend; + } + + /** + * Sets intend. + * + * @param intend the intend used to calculate ree space in method {@link #createSpace()} + */ + public void setIntend(int intend) { + this.intend = intend; + } + +} diff --git a/src/main/java/me/ratsiel/json/interfaces/IListable.java b/src/main/java/me/ratsiel/json/interfaces/IListable.java new file mode 100644 index 0000000..15143a6 --- /dev/null +++ b/src/main/java/me/ratsiel/json/interfaces/IListable.java @@ -0,0 +1,70 @@ +package me.ratsiel.json.interfaces; + +import me.ratsiel.json.abstracts.JsonValue; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +/** + * The interface Listable. + * Used to implement functions like {@link #loop(Consumer)} or {@link #get(int)} + * + * @param the type parameter determines generic type of value that can be get or added from a list + */ +public interface IListable { + + /** + * Size int. + * + * @return the int determines the size of given list + */ + int size(); + + /** + * Add value to something. + * + * @param value the value is going to be add to a list or something else + */ + void add(T value); + + /** + * Add value to something where a index is needed. + * + * @param index the index is the position identifier of the object + * @param value the value is going to be add to a list or something else + */ + void add(int index, T value); + + /** + * Get a value of type {@link T} by its index and cast it with {@link Class}. + * + * @param index the index is the position of the object + * @param clazz the clazz is the type of object + * @return object of type {@link T} + */ + T get(int index, Class clazz); + + /** + * Get a value of type {@link T} by its index. + * + * @param index the index is the position of the object + * @return object of type {@link T} + */ + T get(int index); + + /** + * Loop is used to iterate through a {@link java.util.List} or an {@link java.util.Iterator} + * + * @param consumer the consumer accepts values from {@link java.util.List} + */ + void loop(Consumer consumer); + + /** + * Loop is used to iterate through a {@link java.util.List} or an {@link java.util.Iterator} + * + * @param consumer the consumer the consumer accepts values and indexes from {@link java.util.List} + */ + void loop(BiConsumer consumer); + + +} diff --git a/src/main/java/me/ratsiel/json/model/JsonArray.java b/src/main/java/me/ratsiel/json/model/JsonArray.java new file mode 100644 index 0000000..1919480 --- /dev/null +++ b/src/main/java/me/ratsiel/json/model/JsonArray.java @@ -0,0 +1,147 @@ +package me.ratsiel.json.model; + +import me.ratsiel.json.abstracts.JsonValue; +import me.ratsiel.json.interfaces.IListable; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +/** + * The class Json array is used to store objects of type {@link JsonValue} in {@link #values} + */ +public class JsonArray extends JsonValue implements IListable { + + + /** + * The Values is a list which is used by the {@link IListable} + */ + protected final List values = new ArrayList<>(); + + /** + * Instantiates a new Json array. + */ + public JsonArray() { + } + + /** + * Instantiates a new Json array. + * + * @param key the key is the identifier of {@link JsonValue} + */ + public JsonArray(String key) { + super(key); + } + + + + + @Override + public String toString() { + StringBuilder stringBuilder = new StringBuilder(); + + String space = createSpace(); + + stringBuilder.append(space); + if (getKey() != null && !getKey().isEmpty()) { + stringBuilder.append("\"").append(getKey()).append("\"").append(" : "); + } + + stringBuilder.append("["); + loop((integer, jsonValue) -> { + jsonValue.setIntend(getIntend() + 2); + stringBuilder.append("\n"); + if(!(integer == size() - 1)) { + stringBuilder.append(jsonValue).append(","); + } else { + stringBuilder.append(jsonValue); + } + }); + stringBuilder.append("\n").append(space).append("]"); + + return stringBuilder.toString(); + } + + + @Override + public int size() { + return values.size(); + } + + @Override + public void add(JsonValue value) { + values.add(value); + } + + @Override + public void add(int index, JsonValue value) { + values.set(index, value); + } + + public void addString(String value) { + JsonString jsonString = new JsonString(value); + this.add(jsonString); + } + + public void addBoolean(boolean value) { + JsonBoolean jsonBoolean = new JsonBoolean(value); + this.add(jsonBoolean); + } + + public void addByte(byte value) { + JsonNumber jsonNumber = new JsonNumber(String.valueOf(value)); + this.add(jsonNumber); + } + + public void addInteger(int value) { + JsonNumber jsonNumber = new JsonNumber(String.valueOf(value)); + this.add(jsonNumber); + } + + public void addShort(short value) { + JsonNumber jsonNumber = new JsonNumber(String.valueOf(value)); + this.add(jsonNumber); + } + + public void addDouble(double value) { + JsonNumber jsonNumber = new JsonNumber(String.valueOf(value)); + this.add(jsonNumber); + } + + public void addFloat(float value) { + JsonNumber jsonNumber = new JsonNumber(String.valueOf(value)); + this.add(jsonNumber); + } + + public void addLong(long value) { + JsonNumber jsonNumber = new JsonNumber(String.valueOf(value)); + this.add(jsonNumber); + } + + @Override + public JsonValue get(int index, Class clazz) { + return clazz.cast(get(index)); + } + + @Override + public JsonValue get(int index) { + return values.get(index); + } + + @Override + public void loop(Consumer consumer) { + for (JsonValue value : values) { + consumer.accept(value); + } + } + + @Override + public void loop(BiConsumer consumer) { + for (int index = 0; index < values.size(); index++) { + JsonValue jsonValue = values.get(index); + consumer.accept(index, jsonValue); + } + } + +} diff --git a/src/main/java/me/ratsiel/json/model/JsonBoolean.java b/src/main/java/me/ratsiel/json/model/JsonBoolean.java new file mode 100644 index 0000000..08c604d --- /dev/null +++ b/src/main/java/me/ratsiel/json/model/JsonBoolean.java @@ -0,0 +1,77 @@ +package me.ratsiel.json.model; + +import me.ratsiel.json.abstracts.JsonValue; + +/** + * The class Json boolean stores a boolean. + */ +public class JsonBoolean extends JsonValue { + + /** + * The Value is the given boolean + */ + public boolean value; + + /** + * Instantiates a new Json boolean. + */ + public JsonBoolean() { + } + + /** + * Instantiates a new Json boolean. + * + * @param value the value is the given boolean that is set to {@link #value} + */ + public JsonBoolean(boolean value) { + this.value = value; + } + + /** + * Instantiates a new Json boolean. + * + * @param key the key is the identifier of {@link JsonValue} + * @param value the value is the given boolean that is set to {@link #value} + */ + public JsonBoolean(String key, boolean value) { + super(key); + this.value = value; + } + + + @Override + public String toString() { + StringBuilder stringBuilder = new StringBuilder(); + String space = createSpace(); + + stringBuilder.append(space); + if (getKey() != null && !getKey().isEmpty()) { + stringBuilder.append("\"").append(getKey()).append("\"").append(" : "); + } + stringBuilder.append(isValue()); + + return stringBuilder.toString(); + } + + + /** + * Is value boolean. + * + * @return the boolean value is the stored at {@link #value} + */ + public boolean isValue() { + return value; + } + + /** + * Sets value. + * + * @param value the value is the value {@link #value} + */ + public void setValue(boolean value) { + this.value = value; + } + + + +} diff --git a/src/main/java/me/ratsiel/json/model/JsonNull.java b/src/main/java/me/ratsiel/json/model/JsonNull.java new file mode 100644 index 0000000..74e605d --- /dev/null +++ b/src/main/java/me/ratsiel/json/model/JsonNull.java @@ -0,0 +1,53 @@ +package me.ratsiel.json.model; + +import me.ratsiel.json.abstracts.JsonValue; + +/** + * The class Json null store a null value. + */ +public class JsonNull extends JsonValue { + + /** + * The Value is a final value because null is null! + */ + protected final String value = "null"; + + /** + * Instantiates a new Json null. + */ + public JsonNull() { + } + + /** + * Instantiates a new Json null. + * + * @param key the key is the identifier of {@link JsonValue} + */ + public JsonNull(String key) { + super(key); + } + + @Override + public String toString() { + StringBuilder stringBuilder = new StringBuilder(); + String space = createSpace(); + + + stringBuilder.append(space); + if (getKey() != null && !getKey().isEmpty()) { + stringBuilder.append("\"").append(getKey()).append("\"").append(" : "); + } + stringBuilder.append("null"); + + return stringBuilder.toString(); + } + + /** + * Gets value. + * + * @return the value is the stored at {@link #value} + */ + public String getValue() { + return value; + } +} diff --git a/src/main/java/me/ratsiel/json/model/JsonNumber.java b/src/main/java/me/ratsiel/json/model/JsonNumber.java new file mode 100644 index 0000000..5d73649 --- /dev/null +++ b/src/main/java/me/ratsiel/json/model/JsonNumber.java @@ -0,0 +1,275 @@ +package me.ratsiel.json.model; + +import me.ratsiel.json.abstracts.JsonValue; + +import java.math.BigDecimal; + +/** + * The class Json number stores a string that can be cast to a number. + */ +public class JsonNumber extends JsonValue { + + /** + * The Value is a string that can be cast to a number. + */ + protected String value; + + /** + * Instantiates a new Json number. + */ + public JsonNumber() { + } + + + /** + * Instantiates a new Json number. + * + * @param key the key is the identifier of {@link JsonValue} + * @param value the value is the given string that is set to {@link #value} + */ + public JsonNumber(String key, String value) { + super(key); + this.value = value; + } + + /** + * Instantiates a new Json number. + * + * @param value the value + */ + public JsonNumber(String value) { + this.value = value; + } + + @Override + public String toString() { + StringBuilder stringBuilder = new StringBuilder(); + + String space = createSpace(); + + stringBuilder.append(space); + if (getKey() != null && !getKey().isEmpty()) { + stringBuilder.append("\"").append(getKey()).append("\"").append(" : "); + } + stringBuilder.append(getValue()); + + return stringBuilder.toString(); + } + + + /** + * Gets value. + * + * @return the value the value is the given string that is set to {@link #value} + */ + public String getValue() { + return value; + } + + /** + * Sets value. + * + * @param value the value is set to {@link #value} + */ + public void setValue(String value) { + this.value = value; + } + + /** + * Byte value byte. + * Convert {@link #value} to {@link Byte} + * + * @return the byte converted value + */ + public byte byteValue() { + return getBigDecimal().byteValue(); + } + + /** + * Int value int. + * Convert {@link #value} to {@link Integer} + * + * @return the int converted value + */ + public int intValue() { + return getBigDecimal().intValue(); + } + + /** + * Short value short. + * Convert {@link #value} to {@link Short} + * + * @return the short converted value + */ + public short shortValue() { + return getBigDecimal().shortValue(); + } + + /** + * Double value double. + * Convert {@link #value} to {@link Double} + * + * @return the double converted value + */ + public double doubleValue() { + return getBigDecimal().doubleValue(); + } + + /** + * Float value float. + * Convert {@link #value} to {@link Float} + * + * @return the float converted value + */ + public float floatValue() { + return getBigDecimal().floatValue(); + } + + /** + * Long value long. + * Convert {@link #value} to {@link Long} + * + * @return the long converted value + */ + public long longValue() { + return getBigDecimal().longValue(); + } + + /** + * Gets number determined by given class {@link Class} + * + * @param the type parameter determines the given number + * @param clazz the clazz used to use correct number. + * @return the number object of type {@link Number} + */ + public Object getNumber(Class clazz) { + + if (clazz.equals(Byte.class) || clazz.equals(byte.class)) { + return byteValue(); + } else if (clazz.equals(Integer.class) || clazz.equals(int.class)) { + return intValue(); + } else if (clazz.equals(Short.class) || clazz.equals(short.class)) { + return shortValue(); + } else if (clazz.equals(Double.class) || clazz.equals(double.class)) { + return doubleValue(); + } else if (clazz.equals(Float.class) || clazz.equals(float.class)) { + return floatValue(); + } else if (clazz.equals(Long.class) || clazz.equals(long.class)) { + return longValue(); + } + + return null; + } + + /** + * Gets big decimal. + * + * @return the big decimal created by {@link #value} + */ + public BigDecimal getBigDecimal() { + return new BigDecimal(getValue()); + } + + + /** + * Is numeric boolean. + * Check if given string is a number + * + * @param value the value is used to check + * @return the boolean true or false + */ + public boolean isNumeric(String value) { + + if (value == null) { + return false; + } + boolean isNumber = value.matches("[+-]?(\\d+([.]\\d*)?([eE][+-]?\\d+)?|[.]\\d+([eE][+-]?\\d+)?)"); + + if (isNumber) { + String testValue; + + if (isInteger(value)) { + int integerValue = intValue(); + + testValue = String.valueOf(integerValue); + } else if (isDouble(value)) { + double doubleValue = doubleValue(); + + + if (doubleValue > Double.MAX_VALUE || doubleValue < Double.MIN_EXPONENT) { + return false; + } + + testValue = String.valueOf(Math.round(doubleValue)); + } else if (isFloat(value)) { + float floatValue = floatValue(); + + if (floatValue > Float.MAX_VALUE || floatValue < Float.MIN_EXPONENT) { + return false; + } + + testValue = String.valueOf(Math.round(floatValue)); + } else { + testValue = value; + } + + try { + Long.parseLong(testValue); + } catch (Exception exception) { + return false; + } + + } + + return isNumber; + } + + /** + * Is float boolean. + * Check if given string is a float + * + * @param value the value is used to check + * @return the boolean true or false + */ + public boolean isFloat(String value) { + try { + Float.parseFloat(value); + return true; + } catch (Exception exception) { + return false; + } + } + + /** + * Is integer boolean. + * Check if given string is a integer + * + * @param value the value is used to check + * @return the boolean true or false + */ + public boolean isInteger(String value) { + try { + Integer.parseInt(value); + return true; + } catch (Exception exception) { + return false; + } + } + + /** + * Is double boolean. + * Check if given string is a double + * + * @param value the value is used to check + * @return the boolean true or false + */ + public boolean isDouble(String value) { + try { + Double.parseDouble(value); + return true; + } catch (Exception exception) { + return false; + } + } + +} diff --git a/src/main/java/me/ratsiel/json/model/JsonObject.java b/src/main/java/me/ratsiel/json/model/JsonObject.java new file mode 100644 index 0000000..6f9e41a --- /dev/null +++ b/src/main/java/me/ratsiel/json/model/JsonObject.java @@ -0,0 +1,215 @@ +package me.ratsiel.json.model; + +import me.ratsiel.json.abstracts.JsonValue; +import me.ratsiel.json.interfaces.IListable; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +/** + * The class Json object is used to store objects of type {@link JsonValue} in {@link #values} + */ +public class JsonObject extends JsonValue implements IListable { + + /** + * The Values is a list which is used by the {@link IListable} + */ + protected final List values = new ArrayList<>(); + + /** + * Instantiates a new Json object. + */ + public JsonObject() { + } + + /** + * Instantiates a new Json object. + * + * @param key the key is the identifier of {@link JsonValue} + */ + public JsonObject(String key) { + super(key); + } + + @Override + public String toString() { + StringBuilder stringBuilder = new StringBuilder(); + String space = createSpace(); + + stringBuilder.append(space); + + if (getKey() != null && !getKey().isEmpty()) { + stringBuilder.append("\"").append(getKey()).append("\"").append(" : "); + } + + stringBuilder.append("{"); + loop((integer, jsonValue) -> { + jsonValue.setIntend(getIntend() + 2); + stringBuilder.append("\n"); + if(!(integer == size() - 1)) { + stringBuilder.append(jsonValue).append(","); + } else { + stringBuilder.append(jsonValue); + } + }); + stringBuilder.append("\n").append(space).append("}"); + + return stringBuilder.toString(); + } + + + /** + * Get json value by key. + * + * @param key the key is the identifier of {@link JsonValue} + * @return the json value is null or the Json value that is found in {@link #values} + */ + public JsonValue get(String key) { + if(!has(key)) return null; + + return values.stream().filter(jsonValue -> jsonValue.getKey().equalsIgnoreCase(key)).findFirst().get(); + } + + /** + * Get {@link T} by key and cast it to {@link Class} + * + * @param the type parameter is used to determine what type of object is needed + * @param key the key is the identifier of {@link JsonValue} + * @param clazz the clazz is used to cast the {@link T} + * @return the {@link T} object + */ + public T get(String key, Class clazz) { + return clazz.cast(get(key)); + } + + /** + * Has is used to find a {@link JsonValue} by its key in {@link #values} + * + * @param key the key is the identifier of {@link JsonValue} + * @return the boolean true or false + */ + public boolean has(String key) { + return values.stream().anyMatch(jsonValue -> jsonValue.getKey() != null && jsonValue.getKey().equalsIgnoreCase(key)); + } + + /** + * Gets index. + * Used to get the index of an key if it is found in {@link #values} + * @param key the key is the identifier of {@link JsonValue} + * @return the index by key + */ + public int getIndex(String key) { + if(!has(key)) return -1; + + for (int index = 0; index < values.size(); index++) { + JsonValue jsonValue = values.get(index); + if(jsonValue.getKey().equalsIgnoreCase(key)) { + return index; + } + } + return -1; + } + + + @Override + public int size() { + return values.size(); + } + + /** + * Add an object to {@link #values} by its key. + * + * @param key the key is the identifier of {@link JsonValue} + * @param jsonValue the json value is the {@link JsonValue} that is going to be added to {@link #values} + */ + public void add(String key, JsonValue jsonValue) { + jsonValue.setKey(key); + add(jsonValue); + } + + @Override + public void add(JsonValue value) { + if(has(value.getKey())) { + int index = getIndex(value.getKey()); + add(index, value); + } else { + values.add(value); + } + } + + @Override + public void add(int index, JsonValue value) { + values.set(index, value); + } + + + public void addString(String key, String value) { + JsonString jsonString = new JsonString(key, value); + this.add(jsonString); + } + + public void addBoolean(String key, boolean value) { + JsonBoolean jsonBoolean = new JsonBoolean(key, value); + this.add(jsonBoolean); + } + + public void addByte(String key, byte value) { + JsonNumber jsonNumber = new JsonNumber(key, String.valueOf(value)); + this.add(jsonNumber); + } + + public void addInteger(String key, int value) { + JsonNumber jsonNumber = new JsonNumber(key, String.valueOf(value)); + this.add(jsonNumber); + } + + public void addShort(String key, short value) { + JsonNumber jsonNumber = new JsonNumber(key, String.valueOf(value)); + this.add(jsonNumber); + } + + public void addDouble(String key, double value) { + JsonNumber jsonNumber = new JsonNumber(key, String.valueOf(value)); + this.add(jsonNumber); + } + + public void addFloat(String key, float value) { + JsonNumber jsonNumber = new JsonNumber(key, String.valueOf(value)); + this.add(jsonNumber); + } + + public void addLong(String key, long value) { + JsonNumber jsonNumber = new JsonNumber(key, String.valueOf(value)); + this.add(jsonNumber); + } + + + @Override + public JsonValue get(int index, Class clazz) { + return clazz.cast(get(index)); + } + + @Override + public JsonValue get(int index) { + return values.get(index); + } + + @Override + public void loop(Consumer consumer) { + for (JsonValue value : values) { + consumer.accept(value); + } + } + + @Override + public void loop(BiConsumer consumer) { + for (int index = 0; index < values.size(); index++) { + JsonValue jsonValue = values.get(index); + consumer.accept(index, jsonValue); + } + } + + +} diff --git a/src/main/java/me/ratsiel/json/model/JsonString.java b/src/main/java/me/ratsiel/json/model/JsonString.java new file mode 100644 index 0000000..74733ec --- /dev/null +++ b/src/main/java/me/ratsiel/json/model/JsonString.java @@ -0,0 +1,73 @@ +package me.ratsiel.json.model; + +import me.ratsiel.json.abstracts.JsonValue; + +/** + * The class Json string is used to store a string. + */ +public class JsonString extends JsonValue { + + private String value; + + /** + * Instantiates a new Json string. + */ + public JsonString() { + } + + /** + * Instantiates a new Json string. + * + * @param key the key is the identifier of {@link JsonValue} + * @param value the value is the given string that is set to {@link #value} + */ + public JsonString(String key, String value) { + super(key); + this.value = value; + } + + /** + * Instantiates a new Json string. + * + * @param value the value is the given string that is set to {@link #value} + */ + public JsonString(String value) { + this.value = value; + } + + @Override + public String toString() { + StringBuilder stringBuilder = new StringBuilder(); + String space = createSpace(); + + stringBuilder.append(space); + if (getKey() != null && !getKey().isEmpty()) { + stringBuilder.append("\"").append(getKey()).append("\"") + .append(" : ").append("\"").append(getValue()).append("\""); + } else { + stringBuilder.append("\"").append(value).append("\""); + } + + return stringBuilder.toString(); + } + + + /** + * Gets value. + * + * @return the value is the stored at {@link #value} + */ + public String getValue() { + return value; + } + + /** + * Sets value. + * + * @param value the value is set to {@link #value} + */ + public void setValue(String value) { + this.value = value; + } + +} diff --git a/src/main/java/net/lax1dude/eaglercraft/GuiScreenEditProfile.java b/src/main/java/net/lax1dude/eaglercraft/GuiScreenEditProfile.java index f2f0270..7b0d888 100644 --- a/src/main/java/net/lax1dude/eaglercraft/GuiScreenEditProfile.java +++ b/src/main/java/net/lax1dude/eaglercraft/GuiScreenEditProfile.java @@ -1,5 +1,9 @@ package net.lax1dude.eaglercraft; +import me.ratsiel.auth.abstracts.exception.AuthenticationException; +import me.ratsiel.auth.model.mojang.MinecraftAuthenticator; +import me.ratsiel.auth.model.mojang.MinecraftToken; +import me.ratsiel.auth.model.mojang.profile.MinecraftProfile; import net.minecraft.src.GuiButton; import net.minecraft.src.GuiScreen; import net.minecraft.src.GuiTextField; @@ -10,6 +14,7 @@ public class GuiScreenEditProfile extends GuiScreen { private GuiScreen parent; private GuiTextField username; + private GuiTextField password; private boolean dropDownOpen = false; private String[] dropDownOptions; @@ -66,15 +71,16 @@ public class GuiScreenEditProfile extends GuiScreen { this.dropDownOptions = EaglerProfile.concatArrays(EaglerProfile.skinNames.toArray(new String[0]), defaultOptions); } - private GuiButton button0, button1, button2, button3; + private GuiButton button0, button1, button2, button3, button4; public void initGui() { super.initGui(); EaglerAdapter.enableRepeatEvents(true); StringTranslate var1 = StringTranslate.getInstance(); this.screenTitle = var1.translateKey("profile.title"); - this.username = new GuiTextField(this.fontRenderer, this.width / 2 - 20 + 1, this.height / 6 + 24 + 1, 138, 20); + this.username = new GuiTextField(this.fontRenderer, this.width / 2 - 20 + 1, this.height / 6 + 1, 138, 20); this.username.setFocused(true); + this.password = new GuiTextField(this.fontRenderer, this.width / 2 - 20 + 1, this.height / 6 + 24 + 1, 138, 20); this.username.setText(EaglerProfile.username); selectedSlot = EaglerProfile.presetSkinId == -1 ? EaglerProfile.customSkinId : (EaglerProfile.presetSkinId + EaglerProfile.skinNames.size()); //this.buttonList.add(new GuiButton(0, this.width / 2 - 100, 140, "eeeee")); @@ -83,6 +89,8 @@ public class GuiScreenEditProfile extends GuiScreen { this.buttonList.add(button2 = new GuiButton(3, this.width / 2 - 21 + 71, this.height / 6 + 110, 72, 20, var1.translateKey("profile.clearSkin"))); this.buttonList.add(button3 = new GuiButton(4, this.width / 2 - 21 + 71, this.height / 6 + 134, 72, 20, this.mc.gameSettings.useDefaultProtocol?"Switch to Eaglercraft":"Switch to Vanilla")); + + this.buttonList.add(button4 = new GuiButton(5, this.width / 2 - 21, this.height / 6 + 134, 72, 20, this.mc.gameSettings.sessionId==null?"Microsoft Login":"Logout")); //this.buttonList.add(new GuiButton(200, this.width / 2, this.height / 6 + 72, 150, 20, var1.translateKey("gui.done"))); } @@ -183,7 +191,7 @@ public class GuiScreenEditProfile extends GuiScreen { protected void actionPerformed(GuiButton par1GuiButton) { if(!dropDownOpen) { if(par1GuiButton.id == 200) { - EaglerProfile.username = this.username.getText().length() == 0 ? "null" : this.username.getText(); + if(mc.gameSettings.sessionId==null)EaglerProfile.username = this.username.getText().length() == 0 ? "null" : this.username.getText(); EaglerProfile.presetSkinId = selectedSlot - EaglerProfile.skinNames.size(); if(EaglerProfile.presetSkinId < 0) { EaglerProfile.presetSkinId = -1; @@ -219,7 +227,23 @@ public class GuiScreenEditProfile extends GuiScreen { }else if (par1GuiButton.id == 4) { //toggle version mode this.mc.gameSettings.useDefaultProtocol=!this.mc.gameSettings.useDefaultProtocol; - button3.displayString=this.mc.gameSettings.useDefaultProtocol?"Switch to Eaglercraft":"Switch to Vanilla"; + this.button3.displayString=this.mc.gameSettings.useDefaultProtocol?"Switch to Eaglercraft":"Switch to Vanilla"; + }else if (par1GuiButton.id == 5) { + //log in/out + if(this.mc.gameSettings.sessionId==null){ + try { + MinecraftAuthenticator minecraftAuthenticator = new MinecraftAuthenticator(); + MinecraftToken minecraftToken = minecraftAuthenticator.loginWithXbox(this.username.getText(), ""); + MinecraftProfile minecraftProfile = minecraftAuthenticator.checkOwnership(minecraftToken); + this.button4.displayString="Logout"; + EaglerProfile.username=minecraftToken.getUsername(); + this.username.setText(EaglerProfile.username); + this.password.setText(""); + mc.gameSettings.sessionId=minecraftToken.getAccessToken(); + }catch(AuthenticationException e){} + }else{ + this.mc.gameSettings.sessionId=null; + } } } } @@ -270,14 +294,18 @@ public class GuiScreenEditProfile extends GuiScreen { public void onGuiClosed() { EaglerAdapter.enableRepeatEvents(false); + if(mc.gameSettings.sessionId==null){ + EaglerProfile.username=EaglerProfile.username.replaceAll("@",""); + } } - + + //will be called for password typing as well lol, meh protected void keyTyped(char par1, int par2) { this.username.textboxKeyTyped(par1, par2); - + String text = username.getText(); if(text.length() > 16) text = text.substring(0, 16); - text = text.replaceAll("[^A-Za-z0-9\\-_]", "_"); + text = text.replaceAll("[^A-Za-z0-9\\-_@]", "_"); this.username.setText(text); if(par2 == 200 && selectedSlot > 0) { diff --git a/src/main/java/net/minecraft/src/GameSettings.java b/src/main/java/net/minecraft/src/GameSettings.java index 47865a8..a4e208a 100644 --- a/src/main/java/net/minecraft/src/GameSettings.java +++ b/src/main/java/net/minecraft/src/GameSettings.java @@ -6,6 +6,7 @@ import net.minecraft.client.Minecraft; public class GameSettings { public static boolean useDefaultProtocol = false; + public static String sessionId = null; private static final String[] RENDER_DISTANCES = new String[] { "options.renderDistance.far", "options.renderDistance.normal", "options.renderDistance.short", "options.renderDistance.tiny" };