From a7548cad84c249b91abf56177df22d1ae7f2c3c1 Mon Sep 17 00:00:00 2001 From: nathan Date: Fri, 10 Feb 2017 13:59:05 +0100 Subject: [PATCH] Merged utilities from common + bootstrap into single project. --- Utilities.iml | 1 + src/dorkbox/exit/Exit.java | 364 ++++++++++ src/dorkbox/exit/ExitBase.java | 69 ++ src/dorkbox/exit/ExitError.java | 34 + src/dorkbox/exit/ExitRestart.java | 25 + src/dorkbox/urlHandler/BoxHandler.java | 98 +++ src/dorkbox/urlHandler/BoxHandlerFactory.java | 60 ++ src/dorkbox/urlHandler/BoxURLConnection.java | 153 ++++ src/dorkbox/util/Base64Fast.java | 660 ++++++++++++++++++ src/dorkbox/util/ClassResolver.java | 49 ++ src/dorkbox/util/LZMA.java | 111 +++ src/dorkbox/util/LocationResolver.java | 584 ++++++++++++++++ src/dorkbox/util/OS.java | 316 +++++++++ src/dorkbox/util/OsType.java | 136 ++++ src/dorkbox/util/Property.java | 40 ++ 15 files changed, 2700 insertions(+) create mode 100644 src/dorkbox/exit/Exit.java create mode 100644 src/dorkbox/exit/ExitBase.java create mode 100644 src/dorkbox/exit/ExitError.java create mode 100644 src/dorkbox/exit/ExitRestart.java create mode 100644 src/dorkbox/urlHandler/BoxHandler.java create mode 100644 src/dorkbox/urlHandler/BoxHandlerFactory.java create mode 100644 src/dorkbox/urlHandler/BoxURLConnection.java create mode 100644 src/dorkbox/util/Base64Fast.java create mode 100644 src/dorkbox/util/ClassResolver.java create mode 100644 src/dorkbox/util/LZMA.java create mode 100644 src/dorkbox/util/LocationResolver.java create mode 100644 src/dorkbox/util/OS.java create mode 100644 src/dorkbox/util/OsType.java create mode 100644 src/dorkbox/util/Property.java diff --git a/Utilities.iml b/Utilities.iml index 5e56cf8..8a95a99 100644 --- a/Utilities.iml +++ b/Utilities.iml @@ -21,5 +21,6 @@ + \ No newline at end of file diff --git a/src/dorkbox/exit/Exit.java b/src/dorkbox/exit/Exit.java new file mode 100644 index 0000000..d13c944 --- /dev/null +++ b/src/dorkbox/exit/Exit.java @@ -0,0 +1,364 @@ +/* + * Copyright 2010 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.exit; + + +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.PrintStream; +import java.io.Writer; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import dorkbox.util.OS; + +/** + * The EXIT system uses ERRORS to exit the application. We must make sure NOT to swallow them higher up! -- so don't catch "throwable"! + */ +public final class Exit { + /** + * Used to set the data that will be + * 1) used for relaunch, + * 2) used to display an error message on exit + */ + private static native void setExitError(String data); + + private static native boolean isNative0(); + + /** + * Check to see if we are native. + * Used for determining how exit's are handled. + */ + public static final boolean isNative() { + try { + return isNative0(); + } catch (Throwable t) { + return false; + } + } + + // MIN/MAX values are -128 - 127, because of parent process limitations (anything over 127 will be converted to negative) + private static int UNDEFINED = -1; // must match C source (also anything < 0) + private static int NORMAL = 0; // must match C source + private static int FAILED_CONFIG = 1; // when we are trying to configure something, and it fails. Generally this is for required configurations + private static int FAILED_INIT = 2; + private static int FAILED_SECURITY = 3; + + // must match C source!! + private static int RESERVED = 100; // must match C source + private static List RESERVED_LIST = new ArrayList(2); + + private static int SAVED_IN_LOG_FILE = 101; // must match C source + private static int RESTART = 102; // must match C source + private static int UPGRADE_EXECUTABLE = 103; // must match C source + + static { + RESERVED_LIST.add(new Integer(SAVED_IN_LOG_FILE)); + RESERVED_LIST.add(new Integer(RESTART)); + RESERVED_LIST.add(new Integer(UPGRADE_EXECUTABLE)); + } + + + // so, it is important to observe, that WHILE the application is starting (Launcher.java), + // throwing exceptions is ACCEPTABLE. Afterwards, we MUST rely on the blocking structure to notify + // the main thread, so it can return normally. + + + private static AtomicInteger exitValue = new AtomicInteger(NORMAL); + private static ExitBase exitError = null; + private Exit() {} + + + + // This is what tells us that we are DONE launching, and have moved onto the bootstrap. + public static int getExitValue() { + _setExitData(exitError); + + int exit = exitValue.get(); + if (exit > RESERVED) { + if (RESERVED_LIST.contains(exitError)) { + return exit; + } else { + setExitError("You cannot use any number greater than 99"); + return RESERVED; + } + } else { + return exit; + } + } + + /** + * Only used on the inside of a thrown exception by the main thread + * + *

+ * sets the exit data (ONLY IF there isn't already an error set in the system) then gets the return code + */ + public static int getExitDuringException(ExitBase exitError) { + if (Exit.exitError != null) { + _setExitData(Exit.exitError); + } else { + _setExitData(exitError); + } + + // just in case. + int exit = exitValue.get(); + if (exit > RESERVED) { + if (RESERVED_LIST.contains(exitError)) { + return exit; + } else { + setExitError("You cannot use any number greater than 99"); + return RESERVED; + } + } else { + return exit; + } + } + /** + * Writes a message to the log file + */ + public static void writeToLogFile(String title, String message) { + try { + Writer output = new BufferedWriter(new FileWriter("error.log", true)); + + try { + if (message != null) { + // FileWriter always assumes default encoding is OK + if (title != null) { + output.write(title); + output.write(OS.LINE_SEPARATOR + " "); + } + + output.write(new java.util.Date() + OS.LINE_SEPARATOR + " "); + output.write(message); + output.write(OS.LINE_SEPARATOR); + } else { + output.write("Execption thrown! Unknown error."); + } + output.write(OS.LINE_SEPARATOR); + } finally { + output.close(); + } + } catch (IOException e) { + } + } + + + + /** + * called by the uncaught exception hander. + */ + public static void handleUncaughtException(Throwable error) { + // only manage this if it's not on purpose! + if (!(error instanceof ExitBase)) { + // Not always undefined, although sometimes it is. + String message = error.getMessage(); + if (message == null) { + message = Exit.getStackStrace(error); + } + + Exit.writeToLogFile("Uncaught Exception: " + error.getClass(), message); + + // if we are launching, then throw the error. If we FINISHED launching, trip the block + // if we are closing (happens with a LEGIT error), then do nothing, since we are already + // handling it. + _throwIfLaunching(new ExitError(Exit.UNDEFINED, + "Abnormal termination from an uncaught exception! See log file.)")); + } + } + + + /** + * Undefined/unknown exit, and the info has been written to the log file. + * @param string + * @return + */ + public static int Undefined(Throwable e) { + // Not always undefined, although sometimes it is. + String message = e.getMessage(); + if (message == null) { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(8196); + PrintStream printStream = new PrintStream(byteArrayOutputStream); + e.printStackTrace(printStream); + message = byteArrayOutputStream.toString(); + printStream.close(); + } + + message = e.getClass().toString() + " : " + message; + + // undefined exit! The launcher will restart the JVM in this case + Exit.writeToLogFile("Error", message); + + // will actually return the ACTUAL exit error, if there is one. + return Generic(Exit.UNDEFINED, "Undefined exception! Saved in log file.", message); + } + + /** + * Exit normally (from the launcher). + */ + public static int Normal() { + return Generic(Exit.NORMAL, "Normal exit called."); + } + + /** + * when we are trying to configure something, and it fails. Generally this is for required configurations + */ + public static int FailedConfiguration(String errorMessage, Throwable throwable) { + return Generic(Exit.FAILED_CONFIG, "FailedConfiguration called: " + errorMessage, throwable); + } + + /** + * when we are trying to configure something, and it fails. Generally this is for required configurations + */ + public static int FailedInitialization(Throwable throwable) { + return Generic(Exit.FAILED_INIT, "FailedInitialization called: " + throwable.getMessage(), throwable); + } + + public static int FailedConfiguration(String errorMessage) { + return Generic(Exit.FAILED_CONFIG, "FailedConfiguration called: " + errorMessage); + } + + public static int FailedInitialization(String errorMessage, Throwable throwable) { + return Generic(Exit.FAILED_INIT, "FailedInitialization called: " + errorMessage, throwable); + } + + public static int FailedInitialization(String errorMessage) { + return Generic(Exit.FAILED_INIT, "FailedInitialization called: " + errorMessage); + } + + public static int FailedSecurity(Throwable throwable) { + return Generic(Exit.FAILED_SECURITY, "FailedSecurity called: " + throwable.getMessage(), throwable); + } + + public static int FailedSecurity(String errorMessage, Throwable throwable) { + return Generic(Exit.FAILED_SECURITY, "FailedSecurity called: " + errorMessage, throwable); + } + + public static int FailedSecurity(String errorMessage) { + return Generic(Exit.FAILED_SECURITY, "FailedSecurity called: " + errorMessage); + } + + + + + + public static int Generic(int exitCode, String errorMessage, Throwable throwable) { + return Generic(exitCode, errorMessage + OS.LINE_SEPARATOR + throwable.getClass() + OS.LINE_SEPARATOR + throwable.getMessage()); + } + + public static int Generic(int exitCode, String errorMessage) { + // if we are launching, then throw the error. If we FINISHED launching, trip the block + _throwIfLaunching(new ExitError(exitCode, errorMessage)); + return exitCode; + } + + public static int Generic(int exitCode, String errorTitle, String errorMessage) { + // if we are launching, then throw the error. If we FINISHED launching, trip the block + _throwIfLaunching(new ExitError(exitCode, errorTitle, errorMessage)); + return exitCode; + } + + /** + * restart the application with the current arguments. + * If we need to modify launch args, use the ini file. (or create a new one) + */ + public static void Restart() { + _throwIfLaunching(new ExitRestart(Exit.RESTART)); + } + + /** + * Specify that we want to upgrade the launcher executable. Other types of upgrade should use + * restart, where the launcher will automatically detect and upgrade the components in place. + */ + public static void UpgradeExectuable() { + _throwIfLaunching(new ExitRestart(Exit.UPGRADE_EXECUTABLE)); + } + + + static void _setExitData(ExitBase e) { + if (e == null) { + return; + } + + exitValue.set(e.getExitCode()); + + // exitData is passed to the launcher. + if (e.getMessage() != null) { + String message = e.getMessage(); + if (isNative()) { + if (e.getTitle() != null) { + // can set the title if we want to. Normally it's just the program name. + setExitError("" + e.getTitle() + "" + OS.LINE_SEPARATOR + message); + } else { + setExitError(message); + } + } + } + } + + static void _setExitCode(int exitCode) { + exitValue.set(exitCode); + } + + // throw error if we are still launcher, otherwise set and notify the block + static void _throwIfLaunching(ExitBase exitError) { + // save the error. It's not always passed up the chain. + if (exitError != null && Exit.exitError == null) { + Exit.exitError = exitError; + } + + // throw the error. This makes sure we exit/quit the thread we are currently in. + // we will end up in our "uncaught exception handler"!! + if (exitError != null) { + throw exitError; + } else { + throw new ExitError(Exit.FAILED_INIT, "Unable to have a null errorMessage!"); + } + } + + /** + * Utility method to get the stack trace from an exception, and convert it to a string. + */ + public static String getStackStrace(Throwable throwable) { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(8196); + PrintStream printStream = new PrintStream(byteArrayOutputStream); + throwable.printStackTrace(printStream); + + String message = byteArrayOutputStream.toString(); + + printStream.flush(); + printStream.close(); + + return message; + } + + @Override + public final Object clone() throws java.lang.CloneNotSupportedException { + throw new java.lang.CloneNotSupportedException(); + } + + public final void writeObject(ObjectOutputStream out) throws java.io.IOException { + throw new java.io.NotSerializableException(); + } + + public final void readObject(ObjectInputStream in) throws java.io.IOException { + throw new java.io.NotSerializableException(); + } +} diff --git a/src/dorkbox/exit/ExitBase.java b/src/dorkbox/exit/ExitBase.java new file mode 100644 index 0000000..e90731d --- /dev/null +++ b/src/dorkbox/exit/ExitBase.java @@ -0,0 +1,69 @@ +/* + * Copyright 2010 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.exit; + +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +//package scope, as we don't want to accidentally let someone "catch" this error. +class ExitBase extends Error { + + private static final long serialVersionUID = 546657685093303326L; + + private final String message; + private final int exitCode; + private final String title; + + ExitBase(int exitCode) { + this(exitCode, null, null); + } + + ExitBase(int exitCode, String message) { + this(exitCode, null, message); + } + + ExitBase(int exitCode, String title, String message) { + this.exitCode = exitCode; + this.title = title; + this.message = message; + } + + public final String getTitle() { + return this.title; + } + + @Override + public final String getMessage() { + return this.message; + } + + public final int getExitCode() { + return this.exitCode; + } + + @Override + public final Object clone() throws java.lang.CloneNotSupportedException { + throw new java.lang.CloneNotSupportedException(); + } + + public final void writeObject(ObjectOutputStream out) throws java.io.IOException { + throw new java.io.NotSerializableException(); + } + + public final void readObject(ObjectInputStream in) throws java.io.IOException { + throw new java.io.NotSerializableException(); + } +} diff --git a/src/dorkbox/exit/ExitError.java b/src/dorkbox/exit/ExitError.java new file mode 100644 index 0000000..ac37e65 --- /dev/null +++ b/src/dorkbox/exit/ExitError.java @@ -0,0 +1,34 @@ +/* + * Copyright 2010 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.exit; + +public final class ExitError extends ExitBase { + + private static final long serialVersionUID = 4291020084877555138L; + + + public ExitError(int exitCode) { + super(exitCode); + } + + public ExitError(int exitCode, String message) { + super(exitCode, message); + } + + public ExitError(int exitCode, String title, String message) { + super(exitCode, title, message); + } +} diff --git a/src/dorkbox/exit/ExitRestart.java b/src/dorkbox/exit/ExitRestart.java new file mode 100644 index 0000000..3cede24 --- /dev/null +++ b/src/dorkbox/exit/ExitRestart.java @@ -0,0 +1,25 @@ +/* + * Copyright 2010 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.exit; + +public final class ExitRestart extends ExitBase { + + private static final long serialVersionUID = -3569173102400803538L; + + public ExitRestart(int exitCode) { + super(exitCode); + } +} diff --git a/src/dorkbox/urlHandler/BoxHandler.java b/src/dorkbox/urlHandler/BoxHandler.java new file mode 100644 index 0000000..0cdd72d --- /dev/null +++ b/src/dorkbox/urlHandler/BoxHandler.java @@ -0,0 +1,98 @@ +/* + * Copyright 2010 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.urlHandler; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; + +public +class BoxHandler extends URLStreamHandler { + // + // This is also in the (ClassLoader project) Node!!!, but I didn't want to force a dependency just because of this. + // + // + // The following must ALL be valid URI symbols, defined by RFC 3986: http://tools.ietf.org/html/rfc3986#section-2 + // + // ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~:/?#[]@!$&'()*+,;=. + // + // Any other character needs to be encoded with the percent-encoding (%hh). Each part of the URI has further restrictions about + // what characters need to be represented by an percent-encoded word. + + /** This is exclusively used to identify if a resource we are requesting is inside of a jar that was already parsed */ + static final char jarUrlSeperator = '*'; + static final char jarPathToken = '/'; + static final char packageToken = '.'; + + static final String protocol = "box"; + + static final String protocolFull = protocol + ":/"; + static final int protocolLength = protocolFull.length(); + + + public BoxHandler() { + } + + @Override + protected URLConnection openConnection(URL url) throws IOException { + return new BoxURLConnection(url); + } + + /** + * Makes sure that when creating paths, etc, from this URL, that we also make sure to add a token, so + * our classloader knows where to find the resource. + * + * This absolutely MUST not end in special characters. it must be the letters/numbers or a "/". NOTHING ELSE. + */ + @Override + protected String toExternalForm(URL url) { + // ONLY append jarUrlSeperator if we haven't already done so! + String externalForm = super.toExternalForm(url); + + char jarurlseperator = jarUrlSeperator; + + if (externalForm.indexOf(jarurlseperator) == -1) { + int length = externalForm.length(); + StringBuilder stringBuilder = new StringBuilder(length + 1); + stringBuilder.append(externalForm); + if (length > 1 && externalForm.charAt(length-1) == jarPathToken) { + stringBuilder.insert(length, jarurlseperator); + } else { + stringBuilder.append(jarurlseperator); + } + return stringBuilder.toString(); + } else { + // we've already modified it, don't do it again. + return externalForm; + } + } + + @Override + public final Object clone() throws java.lang.CloneNotSupportedException { + throw new java.lang.CloneNotSupportedException(); + } + + public final void writeObject(ObjectOutputStream out) throws java.io.IOException { + throw new java.io.NotSerializableException(); + } + + public final void readObject(ObjectInputStream in) throws java.io.IOException { + throw new java.io.NotSerializableException(); + } +} diff --git a/src/dorkbox/urlHandler/BoxHandlerFactory.java b/src/dorkbox/urlHandler/BoxHandlerFactory.java new file mode 100644 index 0000000..21fb1c5 --- /dev/null +++ b/src/dorkbox/urlHandler/BoxHandlerFactory.java @@ -0,0 +1,60 @@ +/* + * Copyright 2010 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.urlHandler; + +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.net.URLStreamHandler; +import java.net.URLStreamHandlerFactory; + +public +class BoxHandlerFactory implements URLStreamHandlerFactory { + private final BoxHandler transparentJar; + + public + BoxHandlerFactory(BoxHandler transparentJar) { + this.transparentJar = transparentJar; + } + + @Override + public + URLStreamHandler createURLStreamHandler(String protocol) { + // transparent jar handler. + if (BoxHandler.protocol.equals(protocol)) { + return this.transparentJar; + } + else { + // use the default URLStreamHandlers + return null; + } + } + + @Override + public final + Object clone() throws java.lang.CloneNotSupportedException { + throw new java.lang.CloneNotSupportedException(); + } + + public final + void writeObject(ObjectOutputStream out) throws java.io.IOException { + throw new java.io.NotSerializableException(); + } + + public final + void readObject(ObjectInputStream in) throws java.io.IOException { + throw new java.io.NotSerializableException(); + } +} diff --git a/src/dorkbox/urlHandler/BoxURLConnection.java b/src/dorkbox/urlHandler/BoxURLConnection.java new file mode 100644 index 0000000..7349f77 --- /dev/null +++ b/src/dorkbox/urlHandler/BoxURLConnection.java @@ -0,0 +1,153 @@ +/* + * Copyright 2010 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.urlHandler; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.net.URL; +import java.net.URLConnection; + +/** + * A 'Box' URL is nothing like a JAR/ZIP, HOWEVER, it appears as though it is a jar/zip file. + */ + +public +class BoxURLConnection extends URLConnection { + + public + BoxURLConnection(URL url) { + super(url); + } + + /** + * @return the base name of the url. This will be the internal container (inside the main jar file) that actually contains our resource. + * This will be empty for class files. + */ + public + String getContainerName() { + String spec = this.url.getPath(); + int separator = spec.indexOf(BoxHandler.jarUrlSeperator); + int length = spec.length(); + + if (separator > 0 && separator != length) { + if (spec.charAt(0) == '/') { + if (spec.charAt(separator - 1) == '/') { + String substring = spec.substring(1, separator - 1); + return substring; + } + else { + String substring = spec.substring(1, separator); + return substring; + } + } + else { + if (spec.charAt(separator - 1) == '/') { + String substring = spec.substring(0, separator - 1); + return substring; + } + else { + String substring = spec.substring(0, separator); + return substring; + } + } + } + else { + return ""; + } + } + + /** + * @return the name of the entry that is nested inside an internal resource. This would be the name of a file, where the base URL would + * be the internal resource container. + */ + public + String getResourceName() { + String spec = this.url.getPath(); + int separator = spec.indexOf(BoxHandler.jarUrlSeperator); + + if (separator > -1 && separator != spec.length()) { + if (spec.charAt(separator + 1) == '/') { + return spec.substring(separator + 2); + } + else { + return spec.substring(separator + 1); + } + + } + else { + return ""; + } + } + + @Override + public + void connect() throws IOException { + this.connected = true; + } + + @Override + public + int getContentLength() { + // if we are inside our box file, this will return -1, so inputstreams will be used (which they have to be...) + // if we return anything other than -1, then our box resource will try to be opened like a file (which we don't want) + return -1; + } + + @Override + public + long getLastModified() { + return 0; + } + + /** + * Loads the resources stream, if applicable. You cannot load classes using this method + */ + @Override + public + InputStream getInputStream() throws IOException { + String path = this.url.getPath(); + + int length = BoxHandler.protocolLength; + StringBuilder stringBuilder = new StringBuilder(path.length() + length); + stringBuilder.append(BoxHandler.protocolFull); + if (path.charAt(0) == '/') { + stringBuilder.deleteCharAt(length - 1); + } + stringBuilder.append(path); + + InputStream is = getClass().getClassLoader() + .getResourceAsStream(stringBuilder.toString()); + return is; + } + + @Override + public final + Object clone() throws java.lang.CloneNotSupportedException { + throw new java.lang.CloneNotSupportedException(); + } + + public final + void writeObject(ObjectOutputStream out) throws java.io.IOException { + throw new java.io.NotSerializableException(); + } + + public final + void readObject(ObjectInputStream in) throws java.io.IOException { + throw new java.io.NotSerializableException(); + } +} diff --git a/src/dorkbox/util/Base64Fast.java b/src/dorkbox/util/Base64Fast.java new file mode 100644 index 0000000..faba23e --- /dev/null +++ b/src/dorkbox/util/Base64Fast.java @@ -0,0 +1,660 @@ +/* + * Copyright 2010 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.util; + +import java.util.Arrays; + +/** A very fast and memory efficient class to encode and decode to and from BASE64 in full accordance + * with RFC 2045.

+ * On Windows XP sp1 with 1.4.2_04 and later ;), this encoder and decoder is about 10 times faster + * on small arrays (10 - 1000 bytes) and 2-3 times as fast on larger arrays (10000 - 1000000 bytes) + * compared to sun.misc.Encoder()/Decoder().

+ * + * On byte arrays the encoder is about 20% faster than Jakarta Commons Base64 Codec for encode and + * about 50% faster for decoding large arrays. This implementation is about twice as fast on very small + * arrays (< 30 bytes). If source/destination is a String this + * version is about three times as fast due to the fact that the Commons Codec result has to be recoded + * to a String from byte[], which is very expensive.

+ * + * This encode/decode algorithm doesn't create any temporary arrays as many other codecs do, it only + * allocates the resulting array. This produces less garbage and it is possible to handle arrays twice + * as large as algorithms that create a temporary array. (E.g. Jakarta Commons Codec). It is unknown + * whether Sun's sun.misc.Encoder()/Decoder() produce temporary arrays but since performance + * is quite low it probably does.

+ * + * The encoder produces the same output as the Sun one except that the Sun's encoder appends + * a trailing line separator if the last character isn't a pad. Unclear why but it only adds to the + * length and is probably a side effect. Both are in conformance with RFC 2045 though.
+ * Commons codec seem to always add a trailing line separator.

+ * + * Note! + * The encode/decode method pairs (types) come in three versions with the exact same algorithm and + * thus a lot of code redundancy. This is to not create any temporary arrays for transcoding to/from different + * format types. The methods not used can simply be commented out.

+ * + * There is also a "fast" version of all decode methods that works the same way as the normal ones, but + * has a few demands on the decoded input. Normally though, these fast versions should be used if the source if + * the input is known and it hasn't bee tampered with.

+ * + * If you find the code useful or you find a bug, please send me a note at base64 @ miginfocom . com. + * + * License (BSD): + * ============== + * + * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (base64 @ miginfocom . com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * Neither the name of the MiG InfoCom AB nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * @version 2.2 + * @author Mikael Grev + * Date: 2004-aug-02 + * Time: 11:31:11 + */ + +public class Base64Fast +{ + private static final char[] CA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray(); + private static final int[] IA = new int[256]; + static { + Arrays.fill(IA, -1); + for (int i = 0, iS = CA.length; i < iS; i++) { + IA[CA[i]] = i; + } + IA['='] = 0; + } + + // **************************************************************************************** + // * char[] version + // **************************************************************************************** + + /** Encodes a raw byte array into a BASE64 char[] representation in accordance with RFC 2045. + * @param sArr The bytes to convert. If null or length 0 an empty array will be returned. + * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
+ * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a + * little faster. + * @return A BASE64 encoded array. Never null. + */ + public final static char[] encodeToChar(byte[] sArr, boolean lineSep) + { + sArr = sArr != null ? sArr : new byte[0]; + + // Check special case + int sLen = sArr.length; + if (sLen == 0) { + return new char[0]; + } + + int eLen = sLen / 3 * 3; // Length of even 24-bits. + int cCnt = (sLen - 1) / 3 + 1 << 2; // Returned character count + int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0); // Length of returned array + char[] dArr = new char[dLen]; + + // Encode even 24-bits + for (int s = 0, d = 0, cc = 0; s < eLen;) { + // Copy next three bytes into lower 24 bits of int, paying attension to sign. + int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | sArr[s++] & 0xff; + + // Encode the int into four chars + dArr[d++] = CA[i >>> 18 & 0x3f]; + dArr[d++] = CA[i >>> 12 & 0x3f]; + dArr[d++] = CA[i >>> 6 & 0x3f]; + dArr[d++] = CA[i & 0x3f]; + + // Add optional line separator + if (lineSep && ++cc == 19 && d < dLen - 2) { + dArr[d++] = '\r'; + dArr[d++] = '\n'; + cc = 0; + } + } + + // Pad and encode last bits if source isn't even 24 bits. + int left = sLen - eLen; // 0 - 2. + if (left > 0) { + // Prepare the int + int i = (sArr[eLen] & 0xff) << 10 | (left == 2 ? (sArr[sLen - 1] & 0xff) << 2 : 0); + + // Set last four chars + dArr[dLen - 4] = CA[i >> 12]; + dArr[dLen - 3] = CA[i >>> 6 & 0x3f]; + dArr[dLen - 2] = left == 2 ? CA[i & 0x3f] : '='; + dArr[dLen - 1] = '='; + } + return dArr; + } + + /** Decodes a BASE64 encoded char array. All illegal characters will be ignored and can handle both arrays with + * and without line separators. + * @param sArr The source array. null or length 0 will return an empty array. + * @return The decoded array of bytes. May be of length 0. Will be null if the legal characters + * (including '=') isn't divideable by 4. (I.e. definitely corrupted). + */ + public final static byte[] decode(char[] sArr) + { + sArr = sArr != null ? sArr : new char[0]; + + // Check special case + int sLen = sArr.length; + if (sLen == 0) { + return new byte[0]; + } + + // Count illegal characters (including '\r', '\n') to know what size the returned array will be, + // so we don't have to reallocate & copy it later. + int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...) + for (int i = 0; i < sLen; i++) { + if (IA[sArr[i]] < 0) { + sepCnt++; + } + } + + // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045. + if ((sLen - sepCnt) % 4 != 0) { + return null; + } + + int pad = 0; + for (int i = sLen; i > 1 && IA[sArr[--i]] <= 0;) { + if (sArr[i] == '=') { + pad++; + } + } + + int len = ((sLen - sepCnt) * 6 >> 3) - pad; + + byte[] dArr = new byte[len]; // Preallocate byte[] of exact length + + for (int s = 0, d = 0; d < len;) { + // Assemble three bytes into an int from four "valid" characters. + int i = 0; + for (int j = 0; j < 4; j++) { // j only increased if a valid char was found. + int c = IA[sArr[s++]]; + if (c >= 0) { + i |= c << 18 - j * 6; + } else { + j--; + } + } + // Add the bytes + dArr[d++] = (byte) (i >> 16); + if (d < len) { + dArr[d++]= (byte) (i >> 8); + if (d < len) { + dArr[d++] = (byte) i; + } + } + } + return dArr; + } + + /** Decodes a BASE64 encoded char array that is known to be resonably well formatted. The method is about twice as + * fast as {@link #decode(char[])}. The preconditions are:
+ * + The array must have a line length of 76 chars OR no line separators at all (one line).
+ * + Line separator must be "\r\n", as specified in RFC 2045 + * + The array must not contain illegal characters within the encoded string
+ * + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
+ * @param sArr The source array. Length 0 will return an empty array. null will throw an exception. + * @return The decoded array of bytes. May be of length 0. + */ + public final static byte[] decodeFast(char[] sArr) + { + // Check special case + int sLen = sArr.length; + if (sLen == 0) { + return new byte[0]; + } + + int sIx = 0, eIx = sLen - 1; // Start and end index after trimming. + + // Trim illegal chars from start + while (sIx < eIx && IA[sArr[sIx]] < 0) { + sIx++; + } + + // Trim illegal chars from end + while (eIx > 0 && IA[sArr[eIx]] < 0) { + eIx--; + } + + // get the padding count (=) (0, 1 or 2) + int pad = sArr[eIx] == '=' ? sArr[eIx - 1] == '=' ? 2 : 1 : 0; // Count '=' at end. + int cCnt = eIx - sIx + 1; // Content count including possible separators + int sepCnt = sLen > 76 ? (sArr[76] == '\r' ? cCnt / 78 : 0) << 1 : 0; + + int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes + byte[] dArr = new byte[len]; // Preallocate byte[] of exact length + + // Decode all but the last 0 - 2 bytes. + int d = 0; + for (int cc = 0, eLen = len / 3 * 3; d < eLen;) { + // Assemble three bytes into an int from four "valid" characters. + int i = IA[sArr[sIx++]] << 18 | IA[sArr[sIx++]] << 12 | IA[sArr[sIx++]] << 6 | IA[sArr[sIx++]]; + + // Add the bytes + dArr[d++] = (byte) (i >> 16); + dArr[d++] = (byte) (i >> 8); + dArr[d++] = (byte) i; + + // If line separator, jump over it. + if (sepCnt > 0 && ++cc == 19) { + sIx += 2; + cc = 0; + } + } + + if (d < len) { + // Decode last 1-3 bytes (incl '=') into 1-3 bytes + int i = 0; + for (int j = 0; sIx <= eIx - pad; j++) { + i |= IA[sArr[sIx++]] << 18 - j * 6; + } + + for (int r = 16; d < len; r -= 8) { + dArr[d++] = (byte) (i >> r); + } + } + + return dArr; + } + + // **************************************************************************************** + // * byte[] version + // **************************************************************************************** + + /** Encodes a raw byte array into a BASE64 byte[] representation i accordance with RFC 2045. + * @param sArr The bytes to convert. If null or length 0 an empty array will be returned. + * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
+ * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a + * little faster. + * @return A BASE64 encoded array. Never null. + */ + public final static byte[] encodeToByte(byte[] sArr, boolean lineSep) + { + return encodeToByte(sArr, 0, sArr != null ? sArr.length : 0, lineSep); + } + + /** Encodes a raw byte array into a BASE64 byte[] representation i accordance with RFC 2045. + * @param sArr The bytes to convert. If null an empty array will be returned. + * @param sOff The starting position in the bytes to convert. + * @param sLen The number of bytes to convert. If 0 an empty array will be returned. + * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
+ * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a + * little faster. + * @return A BASE64 encoded array. Never null. + */ + public final static byte[] encodeToByte(byte[] sArr, int sOff, int sLen, boolean lineSep) + { + // Check special case + if (sArr == null || sLen == 0) { + return new byte[0]; + } + + int eLen = sLen / 3 * 3; // Length of even 24-bits. + int cCnt = (sLen - 1) / 3 + 1 << 2; // Returned character count + int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0); // Length of returned array + byte[] dArr = new byte[dLen]; + + // Encode even 24-bits + for (int s = sOff, d = 0, cc = 0; s < sOff + eLen;) { + // Copy next three bytes into lower 24 bits of int, paying attension to sign. + int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | sArr[s++] & 0xff; + + // Encode the int into four chars + dArr[d++] = (byte) CA[i >>> 18 & 0x3f]; + dArr[d++] = (byte) CA[i >>> 12 & 0x3f]; + dArr[d++] = (byte) CA[i >>> 6 & 0x3f]; + dArr[d++] = (byte) CA[i & 0x3f]; + + // Add optional line separator + if (lineSep && ++cc == 19 && d < dLen - 2) { + dArr[d++] = '\r'; + dArr[d++] = '\n'; + cc = 0; + } + } + + // Pad and encode last bits if source isn't an even 24 bits. + int left = sLen - eLen; // 0 - 2. + if (left > 0) { + // Prepare the int + int i = (sArr[sOff + eLen] & 0xff) << 10 | (left == 2 ? (sArr[sOff + sLen - 1] & 0xff) << 2 : 0); + + // Set last four chars + dArr[dLen - 4] = (byte) CA[i >> 12]; + dArr[dLen - 3] = (byte) CA[i >>> 6 & 0x3f]; + dArr[dLen - 2] = left == 2 ? (byte) CA[i & 0x3f] : (byte) '='; + dArr[dLen - 1] = '='; + } + return dArr; + } + + /** Decodes a BASE64 encoded byte array. All illegal characters will be ignored and can handle both arrays with + * and without line separators. + * @param sArr The source array. Length 0 will return an empty array. null will throw an exception. + * @return The decoded array of bytes. May be of length 0. Will be null if the legal characters + * (including '=') isn't divideable by 4. (I.e. definitely corrupted). + */ + public final static byte[] decode(byte[] sArr) + { + return decode(sArr, 0, sArr.length); + } + + /** Decodes a BASE64 encoded byte array. All illegal characters will be ignored and can handle both arrays with + * and without line separators. + * @param sArr The source array. null will throw an exception. + * @param sOff The starting position in the source array. + * @param sLen The number of bytes to decode from the source array. Length 0 will return an empty array. + * @return The decoded array of bytes. May be of length 0. Will be null if the legal characters + * (including '=') isn't divideable by 4. (I.e. definitely corrupted). + */ + public final static byte[] decode(byte[] sArr, int sOff, int sLen) + { + // Count illegal characters (including '\r', '\n') to know what size the returned array will be, + // so we don't have to reallocate & copy it later. + int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...) + for (int i = 0; i < sLen; i++) { + if (IA[sArr[sOff + i] & 0xff] < 0) { + sepCnt++; + } + } + + // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045. + if ((sLen - sepCnt) % 4 != 0) { + return null; + } + + int pad = 0; + for (int i = sLen; i > 1 && IA[sArr[sOff + --i] & 0xff] <= 0;) { + if (sArr[sOff + i] == '=') { + pad++; + } + } + + int len = ((sLen - sepCnt) * 6 >> 3) - pad; + + byte[] dArr = new byte[len]; // Preallocate byte[] of exact length + + for (int s = 0, d = 0; d < len;) { + // Assemble three bytes into an int from four "valid" characters. + int i = 0; + for (int j = 0; j < 4; j++) { // j only increased if a valid char was found. + int c = IA[sArr[sOff + s++] & 0xff]; + if (c >= 0) { + i |= c << 18 - j * 6; + } else { + j--; + } + } + + // Add the bytes + dArr[d++] = (byte) (i >> 16); + if (d < len) { + dArr[d++]= (byte) (i >> 8); + if (d < len) { + dArr[d++] = (byte) i; + } + } + } + + return dArr; + } + + + /** Decodes a BASE64 encoded byte array that is known to be resonably well formatted. The method is about twice as + * fast as {@link #decode(byte[])}. The preconditions are:
+ * + The array must have a line length of 76 chars OR no line separators at all (one line).
+ * + Line separator must be "\r\n", as specified in RFC 2045 + * + The array must not contain illegal characters within the encoded string
+ * + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
+ * @param sArr The source array. Length 0 will return an empty array. null will throw an exception. + * @return The decoded array of bytes. May be of length 0. + */ + public final static byte[] decodeFast(byte[] sArr) + { + // Check special case + int sLen = sArr.length; + if (sLen == 0) { + return new byte[0]; + } + + int sIx = 0, eIx = sLen - 1; // Start and end index after trimming. + + // Trim illegal chars from start + while (sIx < eIx && IA[sArr[sIx] & 0xff] < 0) { + sIx++; + } + + // Trim illegal chars from end + while (eIx > 0 && IA[sArr[eIx] & 0xff] < 0) { + eIx--; + } + + // get the padding count (=) (0, 1 or 2) + int pad = sArr[eIx] == '=' ? sArr[eIx - 1] == '=' ? 2 : 1 : 0; // Count '=' at end. + int cCnt = eIx - sIx + 1; // Content count including possible separators + int sepCnt = sLen > 76 ? (sArr[76] == '\r' ? cCnt / 78 : 0) << 1 : 0; + + int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes + byte[] dArr = new byte[len]; // Preallocate byte[] of exact length + + // Decode all but the last 0 - 2 bytes. + int d = 0; + for (int cc = 0, eLen = len / 3 * 3; d < eLen;) { + // Assemble three bytes into an int from four "valid" characters. + int i = IA[sArr[sIx++]] << 18 | IA[sArr[sIx++]] << 12 | IA[sArr[sIx++]] << 6 | IA[sArr[sIx++]]; + + // Add the bytes + dArr[d++] = (byte) (i >> 16); + dArr[d++] = (byte) (i >> 8); + dArr[d++] = (byte) i; + + // If line separator, jump over it. + if (sepCnt > 0 && ++cc == 19) { + sIx += 2; + cc = 0; + } + } + + if (d < len) { + // Decode last 1-3 bytes (incl '=') into 1-3 bytes + int i = 0; + for (int j = 0; sIx <= eIx - pad; j++) { + i |= IA[sArr[sIx++]] << 18 - j * 6; + } + + for (int r = 16; d < len; r -= 8) { + dArr[d++] = (byte) (i >> r); + } + } + + return dArr; + } + + // **************************************************************************************** + // * String version + // **************************************************************************************** + + /** Encodes a raw byte array into a BASE64 String representation i accordance with RFC 2045. + * @param sArr The bytes to convert. If null or length 0 an empty array will be returned. + * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
+ * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a + * little faster. + * @return A BASE64 encoded array. Never null. + */ + public final static String encodeToString(byte[] sArr, boolean lineSep) + { + // Reuse char[] since we can't create a String incrementally anyway and StringBuffer/Builder would be slower. + return new String(encodeToChar(sArr, lineSep)); + } + + /** Decodes a BASE64 encoded String. All illegal characters will be ignored and can handle both strings with + * and without line separators.
+ * Note! It can be up to about 2x the speed to call decode(str.toCharArray()) instead. That + * will create a temporary array though. This version will use str.charAt(i) to iterate the string. + * @param str The source string. null or length 0 will return an empty array. + * @return The decoded array of bytes. May be of length 0. Will be null if the legal characters + * (including '=') isn't divideable by 4. (I.e. definitely corrupted). + */ + public final static byte[] decode(String str) + { + str = str != null ? str : ""; + + // Check special case + int sLen = str.length(); + if (sLen == 0) { + return new byte[0]; + } + + // Count illegal characters (including '\r', '\n') to know what size the returned array will be, + // so we don't have to reallocate & copy it later. + int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...) + for (int i = 0; i < sLen; i++) { + if (IA[str.charAt(i)] < 0) { + sepCnt++; + } + } + + // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045. + if ((sLen - sepCnt) % 4 != 0) { + return null; + } + + // Count '=' at end + int pad = 0; + for (int i = sLen; i > 1 && IA[str.charAt(--i)] <= 0;) { + if (str.charAt(i) == '=') { + pad++; + } + } + + int len = ((sLen - sepCnt) * 6 >> 3) - pad; + + byte[] dArr = new byte[len]; // Preallocate byte[] of exact length + + for (int s = 0, d = 0; d < len;) { + // Assemble three bytes into an int from four "valid" characters. + int i = 0; + for (int j = 0; j < 4; j++) { // j only increased if a valid char was found. + int c = IA[str.charAt(s++)]; + if (c >= 0) { + i |= c << 18 - j * 6; + } else { + j--; + } + } + // Add the bytes + dArr[d++] = (byte) (i >> 16); + if (d < len) { + dArr[d++]= (byte) (i >> 8); + if (d < len) { + dArr[d++] = (byte) i; + } + } + } + return dArr; + } + + /** Decodes a BASE64 encoded string that is known to be reasonably well formatted. The method is about twice as + * fast as {@link #decode(String)}. The preconditions are:
+ * + The array must have a line length of 76 chars OR no line separators at all (one line).
+ * + Line separator must be "\r\n", as specified in RFC 2045 + * + The array must not contain illegal characters within the encoded string
+ * + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
+ * @param s The source string. Length 0 will return an empty array. null will throw an exception. + * @return The decoded array of bytes. May be of length 0. + */ + public final static byte[] decodeFast(String s) + { + // Check special case + int sLen = s.length(); + if (sLen == 0) { + return new byte[0]; + } + + int sIx = 0, eIx = sLen - 1; // Start and end index after trimming. + + // Trim illegal chars from start + while (sIx < eIx && IA[s.charAt(sIx) & 0xff] < 0) { + sIx++; + } + + // Trim illegal chars from end + while (eIx > 0 && IA[s.charAt(eIx) & 0xff] < 0) { + eIx--; + } + + // get the padding count (=) (0, 1 or 2) + int pad = s.charAt(eIx) == '=' ? s.charAt(eIx - 1) == '=' ? 2 : 1 : 0; // Count '=' at end. + int cCnt = eIx - sIx + 1; // Content count including possible separators + int sepCnt = sLen > 76 ? (s.charAt(76) == '\r' ? cCnt / 78 : 0) << 1 : 0; + + int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes + byte[] dArr = new byte[len]; // Preallocate byte[] of exact length + + // Decode all but the last 0 - 2 bytes. + int d = 0; + for (int cc = 0, eLen = len / 3 * 3; d < eLen;) { + // Assemble three bytes into an int from four "valid" characters. + int i = IA[s.charAt(sIx++)] << 18 | IA[s.charAt(sIx++)] << 12 | IA[s.charAt(sIx++)] << 6 | IA[s.charAt(sIx++)]; + + // Add the bytes + dArr[d++] = (byte) (i >> 16); + dArr[d++] = (byte) (i >> 8); + dArr[d++] = (byte) i; + + // If line separator, jump over it. + if (sepCnt > 0 && ++cc == 19) { + sIx += 2; + cc = 0; + } + } + + if (d < len) { + // Decode last 1-3 bytes (incl '=') into 1-3 bytes + int i = 0; + for (int j = 0; sIx <= eIx - pad; j++) { + i |= IA[s.charAt(sIx++)] << 18 - j * 6; + } + + for (int r = 16; d < len; r -= 8) { + dArr[d++] = (byte) (i >> r); + } + } + + return dArr; + } +} diff --git a/src/dorkbox/util/ClassResolver.java b/src/dorkbox/util/ClassResolver.java new file mode 100644 index 0000000..9765d94 --- /dev/null +++ b/src/dorkbox/util/ClassResolver.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.util; + +public abstract class ClassResolver { + /** + * A helper class to get the call context. It subclasses SecurityManager to make getClassContext() accessible. An instance of + * CallerResolver only needs to be created, not installed as an actual security manager. + */ + private static final class CallerResolver extends SecurityManager { + @Override + protected Class[] getClassContext() { + return super.getClassContext(); + } + } + + private static final int CALL_CONTEXT_OFFSET = 3; // may need to change if this class is redesigned + private static final CallerResolver CALLER_RESOLVER; + + static { + try { + // This can fail if the current SecurityManager does not allow + // RuntimePermission ("createSecurityManager"): + CALLER_RESOLVER = new CallerResolver(); + } catch (SecurityException se) { + throw new RuntimeException("ClassLoaderResolver: could not create CallerResolver: " + se); + } + } + + /** + * Indexes into the current method call context with a given offset. + */ + public static Class getCallerClass(final int callerOffset) { + return CALLER_RESOLVER.getClassContext()[CALL_CONTEXT_OFFSET + callerOffset]; + } +} diff --git a/src/dorkbox/util/LZMA.java b/src/dorkbox/util/LZMA.java new file mode 100644 index 0000000..4682a5e --- /dev/null +++ b/src/dorkbox/util/LZMA.java @@ -0,0 +1,111 @@ +/* + * Copyright 2010 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.util; + +import lzma.sdk.lzma.Decoder; +import lzma.sdk.lzma.Encoder; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; + +public class LZMA { + // LZMA (Java) 4.61 2008-11-23 + // http://jponge.github.com/lzma-java/ + + public static final void encode(long fileSize, InputStream input, OutputStream output) throws IOException { + try { + final Encoder encoder = new Encoder(); + + if (!encoder.setDictionarySize(1 << 23)) { + throw new RuntimeException("Incorrect dictionary size"); + } + if (!encoder.setNumFastBytes(273)) { + throw new RuntimeException("Incorrect -fb value"); + } + if (!encoder.setMatchFinder(1)) { + throw new RuntimeException("Incorrect -mf value"); + } + if (!encoder.setLcLpPb(3, 0, 2)) { + throw new RuntimeException("Incorrect -lc or -lp or -pb value"); + } + encoder.setEndMarkerMode(false); + encoder.writeCoderProperties(output); + + for (int ii = 0; ii < 8; ii++) { + output.write((int)(fileSize >>> 8 * ii) & 0xFF); + } + + encoder.code(input, output, -1, -1, null); + } finally { + input.close(); + output.flush(); + } + } + + public static final ByteArrayOutputStream decode(InputStream input) throws IOException { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(8192); + decode(input, byteArrayOutputStream); + return byteArrayOutputStream; + } + + public static final void decode(InputStream input, OutputStream output) throws IOException { + try { + int propertiesSize = 5; + byte[] properties = new byte[propertiesSize]; + if (input.read(properties, 0, propertiesSize) != propertiesSize) { + throw new IOException("input .lzma file is too short"); + } + + Decoder decoder = new Decoder(); + if (!decoder.setDecoderProperties(properties)) { + throw new IOException("Incorrect stream properties"); + } + + long outSize = 0; + for (int i = 0; i < 8; i++) + { + int v = input.read(); + if (v < 0) { + throw new IOException("Can't read stream size"); + } + outSize |= (long)v << 8 * i; + } + if (!decoder.code(input, output, outSize)) { + throw new IOException("Error in data stream"); + } + } finally { + output.flush(); + input.close(); + } + } + + @Override + public final Object clone() throws java.lang.CloneNotSupportedException { + throw new java.lang.CloneNotSupportedException(); + } + + private final void writeObject(ObjectOutputStream out) throws java.io.IOException { + throw new java.io.NotSerializableException(); + } + + private final void readObject(ObjectInputStream in) throws java.io.IOException { + throw new java.io.NotSerializableException(); + } +} diff --git a/src/dorkbox/util/LocationResolver.java b/src/dorkbox/util/LocationResolver.java new file mode 100644 index 0000000..62cb969 --- /dev/null +++ b/src/dorkbox/util/LocationResolver.java @@ -0,0 +1,584 @@ +/* + * Copyright 2010 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.net.URLDecoder; +import java.security.CodeSource; +import java.security.ProtectionDomain; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.List; +import java.util.Vector; +import java.util.regex.Pattern; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +/** + * Convenience methods for working with resource/file/class locations + */ +public +class LocationResolver { + private static final Pattern SLASH_PATTERN = Pattern.compile("\\\\"); + + private static + void log(String message) { + System.err.println(prefix() + message); + } + + /** + * Normalizes the path. fixes %20 as spaces (in winxp at least). Converts \ -> / (windows slash -> unix slash) + * + * @return a string pointing to the cleaned path + * @throws IOException + */ + private static + String normalizePath(String path) throws IOException { + // make sure the slashes are in unix format. + path = SLASH_PATTERN.matcher(path) + .replaceAll("/"); + + // Can have %20 as spaces (in winxp at least). need to convert to proper path from URL + return URLDecoder.decode(path, "UTF-8"); + } + + /** + * Retrieve the location of the currently loaded jar, or possibly null if it was compiled on the fly + */ + public static + File get() { + return get(LocationResolver.class); + } + + /** + * Retrieve the location that this classfile was loaded from, or possibly null if the class was compiled on the fly + */ + public static + File get(Class clazz) { + // Get the location of this class + ProtectionDomain pDomain = clazz.getProtectionDomain(); + CodeSource cSource = pDomain.getCodeSource(); + + // file:/X:/workspace/XYZ/classes/ when it's in ide/flat + // jar:/X:/workspace/XYZ/jarname.jar when it's jar + URL loc = cSource.getLocation(); + + // we don't always have a protection domain (for example, when we compile classes on the fly, from memory) + if (loc == null) { + return null; + } + + // Can have %20 as spaces (in winxp at least). need to convert to proper path from URL + try { + File file = new File(normalizePath(loc.getFile())).getAbsoluteFile() + .getCanonicalFile(); + return file; + + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("Unable to decode file path!", e); + } catch (IOException e) { + throw new RuntimeException("Unable to get canonical file path!", e); + } + } + + /** + * Retrieves a URL of a given resourceName. If the resourceName is a directory, the returned URL will be the URL for the directory. + *

+ * This method searches the disk first (via new {@link File#File(String)}, then by {@link ClassLoader#getResource(String)}, then by + * {@link ClassLoader#getSystemResource(String)}. + * + * @param resourceName the resource name to search for + * + * @return the URL for that given resource name + */ + public static + URL getResource(String resourceName) { + try { + resourceName = normalizePath(resourceName); + } catch (IOException e) { + e.printStackTrace(); + } + + URL resource = null; + + // 1) maybe it's on disk? priority is disk + File file = new File(resourceName); + if (file.canRead()) { + try { + resource = file.toURI() + .toURL(); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + } + + // 2) is it in the context classloader + if (resource == null) { + resource = Thread.currentThread() + .getContextClassLoader() + .getResource(resourceName); + } + + // 3) is it in the system classloader + if (resource == null) { + // maybe it's in the system classloader? + resource = ClassLoader.getSystemResource(resourceName); + } + + // 4) look for it, and log the output (so we can find or debug it) + if (resource == null) { + try { + searchResource(resourceName); + } catch (IOException e) { + e.printStackTrace(); + } + } + + return resource; + } + + /** + * Retrieves an enumeration of URLs of a given resourceName. If the resourceName is a directory, the returned list will be the URLs + * of the contents of that directory. The first URL will always be the directory URL, as returned by {@link #getResource(String)}. + *

+ * This method searches the disk first (via new {@link File#File(String)}, then by {@link ClassLoader#getResources(String)}, then by + * {@link ClassLoader#getSystemResources(String)}. + * + * @param resourceName the resource name to search for + * + * @return the enumeration of URLs for that given resource name + */ + public static + Enumeration getResources(String resourceName) { + try { + resourceName = normalizePath(resourceName); + } catch (IOException e) { + e.printStackTrace(); + } + + Enumeration resources = null; + try { + // 1) maybe it's on disk? priority is disk + File file = new File(resourceName); + if (file.canRead()) { + ArrayDeque urlList = new ArrayDeque(4); + // add self always + urlList.add(file.toURI() + .toURL()); + + if (file.isDirectory()) { + // add urls of all children + File[] files = file.listFiles(); + if (files != null) { + for (int i = 0, n = files.length; i < n; i++) { + urlList.add(files[i].toURI() + .toURL()); + } + } + } + resources = new Vector(urlList).elements(); + } + + // 2) is it in the context classloader + if (resources == null) { + resources = Thread.currentThread() + .getContextClassLoader() + .getResources(resourceName); + } + + // 3) is it in the system classloader + if (resources == null) { + // maybe it's in the system classloader? + resources = ClassLoader.getSystemResources(resourceName); + + } + + // 4) look for it, and log the output (so we can find or debug it) + if (resources == null) { + searchResource(resourceName); + } + } catch (IOException e) { + e.printStackTrace(); + } + + return resources; + } + + /** + * Retrieves the resource as a stream. + *

+ * 1) checks the disk in the relative location to the executing app
+ * 2) Checks the current thread context classloader
+ * 3) Checks the Classloader system resource + * + * @param resourceName the name, including path information (Only valid '\' as the path separator) + * + * @return the resource stream, if it could be found, otherwise null. + */ + public static + InputStream getResourceAsStream(String resourceName) { + try { + resourceName = normalizePath(resourceName); + } catch (IOException e) { + e.printStackTrace(); + } + + InputStream resourceAsStream = null; + + // 1) maybe it's on disk? priority is disk + if (new File(resourceName).canRead()) { + try { + resourceAsStream = new FileInputStream(resourceName); + } catch (FileNotFoundException e) { + // shouldn't happen, but if there is something wonky... + e.printStackTrace(); + } + } + + // 2) is it in the context classloader + if (resourceAsStream == null) { + resourceAsStream = Thread.currentThread() + .getContextClassLoader() + .getResourceAsStream(resourceName); + } + + // 3) is it in the system classloader + if (resourceAsStream == null) { + // maybe it's in the system classloader? + resourceAsStream = ClassLoader.getSystemResourceAsStream(resourceName); + } + + + // 4) look for it, and log the output (so we can find or debug it) + if (resourceAsStream == null) { + try { + searchResource(resourceName); + } catch (IOException e) { + e.printStackTrace(); + } + } + return resourceAsStream; + } + + + // via RIVEN at JGO. CC0 as far as I can tell. + public static + void searchResource(String path) throws IOException { + try { + path = normalizePath(path); + } catch (IOException e) { + e.printStackTrace(); + } + + List roots = new ArrayList(); + + ClassLoader contextClassLoader = Thread.currentThread() + .getContextClassLoader(); + + if (contextClassLoader instanceof URLClassLoader) { + URL[] urLs = ((URLClassLoader) contextClassLoader).getURLs(); + for (URL url : urLs) { + roots.add(new Root(url)); + } + + System.err.println(); + log("SEARCHING: \"" + path + "\""); + + for (int attempt = 1; attempt <= 6; attempt++) { + for (Root root : roots) { + if (root.search(path, attempt)) { + return; + } + } + } + + log("FAILED: failed to find anything like"); + log(" \"" + path + "\""); + log(" in all classpath entries:"); + + for (Root root : roots) { + final File entry = root.entry; + if (entry != null) { + log(" \"" + entry.getAbsolutePath() + "\""); + } + } + } + else { + throw new IOException("Unable to search for '" + path + "' in the context classloader of type '" + contextClassLoader.getClass() + + "'. Please report this issue with as many specific details as possible (OS, Java version, application version"); + } + } + + @SuppressWarnings("Duplicates") + private static + class Root { + final File entry; + final List resources = new ArrayList(); + + public + Root(URL entry) throws IOException { + this.entry = visitRoot(entry, resources); + } + + public + boolean search(String path, int attempt) { + try { + path = normalizePath(path); + } catch (IOException e) { + e.printStackTrace(); + } + + switch (attempt) { + case 1: { + for (String resource : resources) { + if (path.equals(resource)) { + log("SUCCESS: found resource \"" + path + "\" in root: " + entry); + return true; + } + } + break; + } + + + case 2: { + for (String resource : resources) { + if (path.toLowerCase() + .equals(resource.toLowerCase())) { + log("FOUND: similarly named resource:"); + log(" \"" + resource + "\""); + log(" in classpath entry:"); + log(" \"" + entry + "\""); + log(" for access use:"); + log(" getResourceAsStream(\"/" + resource + "\");"); + return true; + } + } + break; + } + + + case 3: { + for (String resource : resources) { + String r1 = path; + String r2 = resource; + + if (r1.contains("/")) { + r1 = r1.substring(r1.lastIndexOf('/') + 1); + } + if (r2.contains("/")) { + r2 = r2.substring(r2.lastIndexOf('/') + 1); + } + + if (r1.equals(r2)) { + log("FOUND: mislocated resource:"); + log(" \"" + resource + "\""); + log(" in classpath entry:"); + log(" \"" + entry + "\""); + log(" for access use:"); + log(" getResourceAsStream(\"/" + resource + "\");"); + return true; + } + } + break; + } + + + case 4: { + for (String resource : resources) { + String r1 = path.toLowerCase(); + String r2 = resource.toLowerCase(); + + if (r1.contains("/")) { + r1 = r1.substring(r1.lastIndexOf('/') + 1); + } + if (r2.contains("/")) { + r2 = r2.substring(r2.lastIndexOf('/') + 1); + } + + if (r1.equals(r2)) { + log("FOUND: mislocated, similarly named resource:"); + log(" \"" + resource + "\""); + log(" in classpath entry:"); + log(" \"" + entry + "\""); + log(" for access use:"); + log(" getResourceAsStream(\"/" + resource + "\");"); + return true; + } + } + + break; + } + + + case 5: { + for (String resource : resources) { + String r1 = path; + String r2 = resource; + + if (r1.contains("/")) { + r1 = r1.substring(r1.lastIndexOf('/') + 1); + } + if (r2.contains("/")) { + r2 = r2.substring(r2.lastIndexOf('/') + 1); + } + if (r1.contains(".")) { + r1 = r1.substring(0, r1.lastIndexOf('.')); + } + if (r2.contains(".")) { + r2 = r2.substring(0, r2.lastIndexOf('.')); + } + + if (r1.equals(r2)) { + log("FOUND: resource with different extension:"); + log(" \"" + resource + "\""); + log(" in classpath entry:"); + log(" \"" + entry + "\""); + log(" for access use:"); + log(" getResourceAsStream(\"/" + resource + "\");"); + return true; + } + } + break; + } + + + case 6: { + for (String resource : resources) { + String r1 = path.toLowerCase(); + String r2 = resource.toLowerCase(); + + if (r1.contains("/")) { + r1 = r1.substring(r1.lastIndexOf('/') + 1); + } + if (r2.contains("/")) { + r2 = r2.substring(r2.lastIndexOf('/') + 1); + } + if (r1.contains(".")) { + r1 = r1.substring(0, r1.lastIndexOf('.')); + } + if (r2.contains(".")) { + r2 = r2.substring(0, r2.lastIndexOf('.')); + } + + if (r1.equals(r2)) { + log("FOUND: similarly named resource with different extension:"); + log(" \"" + resource + "\""); + log(" in classpath entry:"); + log(" \"" + entry + "\""); + log(" for access use:"); + log(" getResourceAsStream(\"/" + resource + "\");"); + return true; + } + } + + break; + } + + default: + return false; + } + + return false; + } + } + + + private static + File visitRoot(URL url, List resources) throws IOException { + if (!url.getProtocol() + .equals("file")) { + throw new IllegalStateException(); + } + String path = url.getPath(); + + if (OS.isWindows()) { + if (path.startsWith("/")) { + path = path.substring(1); + } + } + + File root = new File(path); + if (!root.exists()) { + log("failed to find classpath entry in filesystem: " + path); + return null; + } + + if (root.isDirectory()) { + visitDir(normalizePath(root.getAbsolutePath()), root, resources); + } + else { + final String s = root.getName() + .toLowerCase(); + + if (s.endsWith(".zip")) { + visitZip(root, resources); + } + else if (s.endsWith(".jar")) { + visitZip(root, resources); + } + else { + log("unknown classpath entry type: " + path); + return null; + } + } + return root; + } + + private static + void visitDir(String root, File dir, Collection out) { + final File[] files = dir.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + visitDir(root, file, out); + } + + out.add(file.getAbsolutePath() + .replace('\\', '/') + .substring(root.length() + 1)); + } + } + } + + private static + void visitZip(File jar, Collection out) throws IOException { + ZipInputStream zis = new ZipInputStream(new FileInputStream(jar)); + while (true) { + ZipEntry entry = zis.getNextEntry(); + if (entry == null) { + break; + } + out.add(entry.getName() + .replace('\\', '/')); + } + zis.close(); + } + + private static + String prefix() { + return "[" + LocationResolver.class.getSimpleName() + "] "; + } +} diff --git a/src/dorkbox/util/OS.java b/src/dorkbox/util/OS.java new file mode 100644 index 0000000..b542d8d --- /dev/null +++ b/src/dorkbox/util/OS.java @@ -0,0 +1,316 @@ +/* + * Copyright 2010 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.util; + +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.nio.charset.Charset; +import java.util.Locale; +import java.util.TimeZone; + + +@SuppressWarnings({"unused", "WeakerAccess"}) +public +class OS { + public static final String LINE_SEPARATOR = System.getProperty("line.separator"); + public static final String LINE_SEPARATOR_UNIX = "\n"; + public static final String LINE_SEPARATOR_WINDOWS = "\r\n"; + public static final Charset US_ASCII = Charset.forName("US-ASCII"); + public static final Charset UTF_8 = Charset.forName("UTF-8"); + public static final Charset UTF_16LE = Charset.forName("UTF-16LE"); + + /** + * The currently running java version as a NUMBER. For example, "Java version 1.7u45", and converts it into 7 + */ + public static final int javaVersion = _getJavaVersion(); + + + private static final OsType osType; + private static final String originalTimeZone = TimeZone.getDefault() + .getID(); + + static { + /** + * By default, the timer resolution in some operating systems are not particularly high-resolution (ie: 'Thread.sleep(1)' will not + * really sleep for 1ms, but will really sleep for 16ms). This forces the JVM to use high resolution timers. This is USUALLY + * necessary on Windows. + */ + Thread timerAccuracyThread = new Thread(new Runnable() { + public + void run() { + //noinspection InfiniteLoopStatement + while (true) { + try { + Thread.sleep(Long.MAX_VALUE); + } catch (Exception ignored) { + } + } + } + }, "ForceHighResTimer"); + timerAccuracyThread.setDaemon(true); + timerAccuracyThread.start(); + + + String osName = System.getProperty("os.name"); + String osArch = System.getProperty("os.arch"); + + if (osName != null && osArch != null) { + osName = osName.toLowerCase(Locale.US); + osArch = osArch.toLowerCase(Locale.US); + + if (osName.startsWith("linux")) { + // best way to determine if it's android or not + boolean isAndroid; + try { + Class.forName("android.app.Activity"); + isAndroid = true; + } catch (ClassNotFoundException e) { isAndroid = false; } + + + if (isAndroid) { + // android check from https://stackoverflow.com/questions/14859954/android-os-arch-output-for-arm-mips-x86 + if (osArch.equals("armeabi")) { + // really old/low-end non-hf 32bit cpu + osType = OsType.AndroidArm56; + } + else if (osArch.equals("armeabi-v7a")) { + // 32bit hf cpu + osType = OsType.AndroidArm7; + } + else if (osArch.equals("arm64-v8a")) { + // 64bit hf cpu + osType = OsType.AndroidArm8; + } + else if (osArch.equals("x86")) { + // 32bit x86 (usually emulator) + osType = OsType.AndroidX86; + } + else if (osArch.equals("x86_64")) { + // 64bit x86 (usually emulator) + osType = OsType.AndroidX86_64; + } + else if (osArch.equals("mips")) { + // 32bit mips + osType = OsType.AndroidMips; + } + else if (osArch.equals("mips64")) { + // 64bit mips + osType = OsType.AndroidMips64; + } else { + // who knows? + osType = null; + } + } + else { + // normal linux 32/64/arm32/arm64 + if ("amd64".equals(osArch)) { + osType = OsType.Linux64; + } + else { + if (osArch.startsWith("arm")) { + if (osArch.contains("v8")) { + osType = OsType.LinuxArm64; + } + else { + osType = OsType.LinuxArm32; + } + } + else { + osType = OsType.Linux32; + } + } + } + } + else if (osName.startsWith("windows")) { + if ("amd64".equals(osArch)) { + osType = OsType.Windows64; + } + else { + osType = OsType.Windows32; + } + } + else if (osName.startsWith("mac") || osName.startsWith("darwin")) { + if ("x86_64".equals(osArch)) { + osType = OsType.MacOsX64; + } + else { + osType = OsType.MacOsX32; + } + } + else if (osName.startsWith("freebsd") || osName.contains("nix") || osName.contains("nux") || osName.startsWith("aix")) { + if ("x86".equals(osArch) || "i386".equals(osArch)) { + osType = OsType.Unix32; + } + else if ("arm".equals(osArch)) { + osType = OsType.UnixArm; + } + else { + osType = OsType.Unix64; + } + } + else if (osName.startsWith("solaris") || osName.startsWith("sunos")) { + osType = OsType.Solaris; + } + else { + osType = null; + } + } + else { + osType = null; + } + } + + public static + OsType get() { + return osType; + } + + public static + boolean is64bit() { + return osType.is64bit(); + } + + public static + boolean is32bit() { + return osType.is32bit(); + } + + /** + * @return true if this is a "standard" x86/x64 architecture (intel/amd/etc) processor. + */ + public static + boolean isX86() { + return osType.isX86(); + } + + public static + boolean isMips() { + return osType.isMips(); + } + + public static + boolean isArm() { + return osType.isArm(); + } + + public static + boolean isLinux() { + return osType.isLinux(); + } + + public static + boolean isUnix() { + return osType.isUnix(); + } + + public static + boolean isSolaris() { + return osType.isSolaris(); + } + + + public static + boolean isWindows() { + return osType.isWindows(); + } + + public static + boolean isMacOsX() { + return osType.isMacOsX(); + } + + public static + boolean isAndroid() { + return osType.isAndroid(); + } + + + /** + * Gets the currently running java version as a NUMBER. For example, "Java version 1.7u45", and converts it into 7 + */ + private static + int _getJavaVersion() { + String fullJavaVersion = System.getProperty("java.version"); + + char versionChar; + if (fullJavaVersion.startsWith("1.")) { + versionChar = fullJavaVersion.charAt(2); + } + else { + versionChar = fullJavaVersion.charAt(0); + } + + switch (versionChar) { + case '4': + return 4; + case '5': + return 5; + case '6': + return 6; + case '7': + return 7; + case '8': + return 8; + case '9': + return 9; + default: + return -1; + } + } + + /** + * Set our system to UTC time zone. Retrieve the original time zone via {@link #getOriginalTimeZone()} + */ + public static + void setUTC() { + // have to set our default timezone to UTC. EVERYTHING will be UTC, and if we want local, we must explicitly ask for it. + TimeZone.setDefault(TimeZone.getTimeZone("UTC")); + } + + /** + * Returns the *ORIGINAL* system time zone, before (*IF*) it was changed to UTC + */ + public static + String getOriginalTimeZone() { + return originalTimeZone; + } + + /** + * @return the optimum number of threads for a given task. Makes certain not to take ALL the threads, always returns at least one + * thread. + */ + public static + int getOptimumNumberOfThreads() { + return Math.max(Runtime.getRuntime() + .availableProcessors() - 2, 1); + } + + @Override + public final + Object clone() throws java.lang.CloneNotSupportedException { + throw new java.lang.CloneNotSupportedException(); + } + + public final + void writeObject(ObjectOutputStream out) throws java.io.IOException { + throw new java.io.NotSerializableException(); + } + + public final + void readObject(ObjectInputStream in) throws java.io.IOException { + throw new java.io.NotSerializableException(); + } +} diff --git a/src/dorkbox/util/OsType.java b/src/dorkbox/util/OsType.java new file mode 100644 index 0000000..29ac6af --- /dev/null +++ b/src/dorkbox/util/OsType.java @@ -0,0 +1,136 @@ +/* + * Copyright 2010 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.util; + +public enum OsType { + Windows32("windows_32", ".dll"), + Windows64("windows_64", ".dll"), + Linux32("linux_32", ".so"), + Linux64("linux_64", ".so"), + MacOsX32("macosx_32", ".jnilib", ".dylib"), + MacOsX64("macosx_64", ".jnilib", ".dylib"), + + UnixArm("unix_arm", ".so"), + Unix32("unix_32", ".so"), + Unix64("unix_64", ".so"), + + Solaris("solaris", ".so"), + + AndroidArm56("android_arm56", ".so"), // 32bit no hardware float support + AndroidArm7("android_arm7", ".so"), // 32bit hardware float support + AndroidArm8("android_arm8", ".so"), // 64bit (w/ hardware float. everything now has hard float) + + AndroidMips("android_mips", ".so"), // 32bit mips + AndroidX86("android_x86", ".so"), // 32bit x86 (usually emulator) + + AndroidMips64("android_mips64", ".so"), // 64bit mips + AndroidX86_64("android_x86_64", ".so"), // 64bit x86 (usually emulator) + + /* + * Linux OS, Hard float, meaning floats are handled in hardware. WE ONLY SUPPORT HARD FLOATS for linux ARM!. + * For Raspberry-PI, Beaglebone, Odroid, etc PCs + */ + LinuxArm32("linux_arm7_hf", ".so"), + LinuxArm64("linux_arm8_hf", ".so"), + ; + + private final String name; + private final String[] libraryNames; + + OsType(String name, String... libraryNames) { + this.name = name; + this.libraryNames = libraryNames; + } + + public String getName() { + return this.name; + } + public String[] getLibraryNames() { + return this.libraryNames; + } + + + public + boolean is64bit() { + return this == OsType.Linux64 || this == OsType.LinuxArm64 || + this == OsType.Windows64 || this == OsType.MacOsX64 || + this == OsType.AndroidArm8 || this == OsType.AndroidX86_64 || this == OsType.AndroidMips64 || + this == OsType.Unix64; + } + + public + boolean is32bit() { + return this == OsType.Linux32 || this == OsType.LinuxArm32 || + this == OsType.Windows32 || this == OsType.MacOsX32 || + this == OsType.AndroidArm56 || this == OsType.AndroidArm7 || this == OsType.AndroidX86 || this == OsType.AndroidMips || + this == OsType.UnixArm || this == OsType.Unix32; + } + + public + boolean isMips() { + return this == OsType.AndroidMips || this == OsType.AndroidMips64; + } + + /** + * @return true if this is a "standard" x86/x64 architecture (intel/amd/etc) processor. + */ + public + boolean isX86() { + return this == OsType.Linux64 || this == OsType.LinuxArm64 || + this == OsType.Windows64 || this == OsType.MacOsX64 || + this == OsType.Linux32 || this == OsType.LinuxArm32 || + this == OsType.Windows32 || this == OsType.MacOsX32 || + this == OsType.Unix32 || this == OsType.Unix64 || + this == OsType.AndroidX86 || this == OsType.AndroidX86_64; + } + + public + boolean isArm() { + return this == OsType.LinuxArm32 || this == OsType.LinuxArm64 || + this == OsType.AndroidArm56 || this == OsType.AndroidArm7 || this == OsType.AndroidArm8; + } + + public + boolean isLinux() { + return this == OsType.Linux32 || this == OsType.Linux64 || this == OsType.LinuxArm64 || this == OsType.LinuxArm32; + } + + public + boolean isUnix() { + return this == OsType.Unix32 || this == OsType.Unix64 || this == OsType.UnixArm; + } + + public + boolean isSolaris() { + return this == OsType.Solaris; + } + + public + boolean isWindows() { + return this == OsType.Windows64 || this == OsType.Windows32; + } + + public + boolean isMacOsX() { + return this == OsType.MacOsX64 || this == OsType.MacOsX32; + } + + public + boolean isAndroid() { + return this == OsType.AndroidArm56 || this == OsType.AndroidArm7 || this == OsType.AndroidX86 || this == OsType.AndroidMips || + this == OsType.AndroidArm8 || this == OsType.AndroidX86_64 || this == OsType.AndroidMips64; + } +} diff --git a/src/dorkbox/util/Property.java b/src/dorkbox/util/Property.java new file mode 100644 index 0000000..68916e2 --- /dev/null +++ b/src/dorkbox/util/Property.java @@ -0,0 +1,40 @@ +/* + * Copyright 2010 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to track system properties across the entire code-base. System properties are values that can be changed programmatically, + * via a "System.getProperty()", or via arguments. + *
+ * Loading arguments is left for the end-user application. Using an annotation detector to load/save properties is recommended. + *
+ * For example (if implemented): -Ddorkbox.Args.Debug=true + */ +@Retention(value = RetentionPolicy.RUNTIME) +@Target(value = {ElementType.FIELD}) +public +@interface Property { + + /** + * This is used to mark a unique value for the property, in case the object name is used elsewhere, is generic, or is repeated. + */ + String alias() default ""; +}