--- /dev/null
+Macaco
+Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+
+Released under MIT license:
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+
+Small portions inspired by other projects or web pages.
+See source code for additional information.
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!--
+ - Macaco
+ - Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ - Released under MIT license (see LICENSE for details).
+ -->
+
+<project name="macaco" default="clean">
+
+ <property file="build.properties" />
+
+ <target name="clean" description="Cleans up everything.">
+ <delete dir="target/" />
+ </target>
+
+ <target name="createArchive" depends="clean" description="Packs source files into a .tar.gz archive.">
+ <tar compression="gzip" destfile="target/${ant.project.name}.tar.gz" basedir="." excludes="target/" />
+ </target>
+
+ <target name="compile" depends="clean" description="Compiles .java source code to .class bytecode.">
+ <mkdir dir="target/classes/" />
+ <javac target="1.8" source="1.8" srcdir="src/java/" destdir="target/classes/">
+ <compilerarg value="-Xlint" />
+ </javac>
+ </target>
+
+ <target name="createJarFile" depends="compile" description="Creates a .jar file.">
+ <jar destfile="target/${ant.project.name}.jar">
+ <fileset dir="target/classes/" includes="**/*.class" />
+ </jar>
+ </target>
+
+</project>
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco;
+
+public abstract class MException extends Exception {
+
+ /* */
+
+ public MException() {
+ super();
+ }
+
+ public MException(String message) {
+ super(message);
+ }
+
+ public MException(Throwable error) {
+ super(error);
+ }
+
+ public MException(String message, Throwable error) {
+ super(message, error);
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco;
+
+import com.marcozanon.macaco.text.MText;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+public class MInformation extends MObject {
+
+ public static final String MACACO_VERSION = "5.x";
+
+ public static final String TEXT_ENCODING = "UTF-8";
+
+ /* Generic information. */
+
+ public static String getMacacoVersion() {
+ return MInformation.MACACO_VERSION;
+ }
+
+ public static String getMacacoFullName() {
+ return "Macaco " + MInformation.getMacacoVersion();
+ }
+
+ public static String getMacacoCopyrightInformation() {
+ StringBuilder s = new StringBuilder("");
+ s.append(MInformation.getMacacoFullName() + System.getProperty("line.separator"));
+ s.append("Copyright (c) 2009-2017 by Marco Zanon." + System.getProperty("line.separator"));
+ s.append("Released under the MIT license. See LICENSE for additional information." + System.getProperty("line.separator"));
+ s.append("Small portions inspired by other projects or web pages. See source code for additional information.");
+ return s.toString();
+ }
+
+ /* Exceptions. */
+
+ public static String getExceptionAsString(Exception exception) {
+ if (null == exception) {
+ throw new IllegalArgumentException("Invalid 'exception': null.");
+ }
+ //
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ exception.printStackTrace(pw);
+ pw.flush();
+ sw.flush();
+ //
+ return sw.toString();
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco;
+
+public abstract class MObject {
+
+ /* */
+
+ protected MObject clone() {
+ throw new UnsupportedOperationException("Please provide manually by yourself.");
+ }
+
+ public String toString() {
+ throw new UnsupportedOperationException("Please use appropriate methods (if any).");
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.conversion;
+
+import com.marcozanon.macaco.MException;
+
+public abstract class MConversionException extends MException {
+
+ /* */
+
+ public MConversionException() {
+ super();
+ }
+
+ public MConversionException(String message) {
+ super(message);
+ }
+
+ public MConversionException(Throwable error) {
+ super(error);
+ }
+
+ public MConversionException(String message, Throwable error) {
+ super(message, error);
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.conversion;
+
+import com.marcozanon.macaco.MObject;
+import com.marcozanon.macaco.text.MText;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Locale;
+import java.util.TimeZone;
+
+public class MDateConverter extends MObject {
+
+ protected LinkedHashSet<String> dateFormats = new LinkedHashSet<String>();
+ protected Locale locale = null;
+ protected TimeZone timeZone = null;
+
+ /* */
+
+ public MDateConverter(String defaultDateFormat, Locale locale) {
+ this(defaultDateFormat, locale, TimeZone.getDefault());
+ }
+
+ public MDateConverter(String defaultDateFormat, Locale locale, TimeZone timeZone) {
+ super();
+ //
+ this.addDateFormat(defaultDateFormat);
+ this.setLocale(locale);
+ this.setTimeZone(timeZone);
+ }
+
+ public MDateConverter(LinkedHashSet<String> dateFormats, Locale locale, TimeZone timeZone) {
+ super();
+ //
+ this.setDateFormats(dateFormats);
+ this.setLocale(locale);
+ this.setTimeZone(timeZone);
+ }
+
+ public MDateConverter clone() {
+ return new MDateConverter(this.getDateFormats(), this.getLocale(), this.getTimeZone());
+ }
+
+ /* Date formats. */
+
+ public void setDateFormats(LinkedHashSet<String> dateFormats) {
+ if (null == dateFormats) {
+ throw new IllegalArgumentException("Invalid 'dateFormats': null.");
+ }
+ else {
+ Iterator<String> i = dateFormats.iterator();
+ while (i.hasNext()) {
+ MDateConverter.checkDateFormat(i.next());
+ }
+ }
+ //
+ synchronized (this.dateFormats) {
+ this.dateFormats = dateFormats;
+ }
+ }
+
+ public void addDateFormat(String dateFormat) {
+ MDateConverter.checkDateFormat(dateFormat);
+ //
+ synchronized (this.dateFormats) {
+ this.dateFormats.add(dateFormat);
+ }
+ }
+
+ public LinkedHashSet<String> getDateFormats() {
+ return this.dateFormats;
+ }
+
+ public String getDefaultDateFormat() {
+ return this.getDateFormats().iterator().next();
+ }
+
+ /* Locale. */
+
+ protected void setLocale(Locale locale) {
+ if (null == locale) {
+ throw new IllegalArgumentException("Invalid 'locale': null.");
+ }
+ //
+ this.locale = locale;
+ }
+
+ public Locale getLocale() {
+ return this.locale;
+ }
+
+ /* Time zone. */
+
+ protected void setTimeZone(TimeZone timeZone) {
+ if (null == timeZone) {
+ throw new IllegalArgumentException("Invalid 'timeZone': null.");
+ }
+ //
+ this.timeZone = timeZone;
+ }
+
+ public TimeZone getTimeZone() {
+ return this.timeZone;
+ }
+
+ /* Conversions. */
+
+ protected static void checkDateFormat(String dateFormat) {
+ if (MText.isBlank(dateFormat)) {
+ throw new IllegalArgumentException("Invalid 'dateFormat': null or empty.");
+ }
+ //
+ try {
+ SimpleDateFormat testDateFormat = new SimpleDateFormat(dateFormat);
+ }
+ catch (IllegalArgumentException exception) {
+ throw new IllegalArgumentException(String.format("Invalid 'dateFormat': %s.", dateFormat)); // no need to propagate exception
+ }
+ }
+
+ protected static Date getDateFromStringByParameters(String x, String inputDateFormat, Locale inputLocale, TimeZone inputTimeZone) throws MInvalidConversionFormatException {
+ if (MText.isBlank(x)) {
+ throw new IllegalArgumentException("Invalid 'x': null or empty.");
+ }
+ MDateConverter.checkDateFormat(inputDateFormat);
+ if (null == inputLocale) {
+ throw new IllegalArgumentException("Invalid 'inputLocale': null.");
+ }
+ if (null == inputTimeZone) {
+ throw new IllegalArgumentException("Invalid 'inputTimeZone': null.");
+ }
+ //
+ Calendar c1 = Calendar.getInstance(inputTimeZone, inputLocale);
+ c1.clear();
+ c1.setLenient(false);
+ SimpleDateFormat sdf = new SimpleDateFormat(inputDateFormat, inputLocale);
+ sdf.setCalendar(c1);
+ Date d1 = null;
+ try {
+ d1 = sdf.parse(x);
+ }
+ catch (ParseException exception) {
+ throw new MInvalidConversionFormatException(String.format("Invalid 'x' or parsing: %s (input format: %s).", x, inputDateFormat)); // no need to propagate exception
+ }
+ Calendar c2 = Calendar.getInstance(inputTimeZone, inputLocale);
+ c2.clear();
+ c2.set(Calendar.YEAR, c1.get(Calendar.YEAR));
+ c2.set(Calendar.MONTH, c1.get(Calendar.MONTH));
+ c2.set(Calendar.DAY_OF_MONTH, c1.get(Calendar.DAY_OF_MONTH));
+ c2.set(Calendar.HOUR_OF_DAY, c1.get(Calendar.HOUR_OF_DAY));
+ c2.set(Calendar.MINUTE, c1.get(Calendar.MINUTE));
+ c2.set(Calendar.SECOND, c1.get(Calendar.SECOND));
+ Date d2 = c2.getTime();
+ if (!x.equals(sdf.format(d2))) {
+ throw new MInvalidConversionFormatException(String.format("Invalid 'x' or parsing: %s (input format: %s).", x, inputDateFormat));
+ }
+ return d2;
+ }
+
+ protected static String getStringFromDateByParameters(Date date, String outputDateFormat, Locale outputLocale, TimeZone outputTimeZone) {
+ if (null == date) {
+ throw new IllegalArgumentException("Invalid 'date': null.");
+ }
+ MDateConverter.checkDateFormat(outputDateFormat);
+ if (null == outputLocale) {
+ throw new IllegalArgumentException("Invalid 'outputLocale': null.");
+ }
+ if (null == outputTimeZone) {
+ throw new IllegalArgumentException("Invalid 'outputTimeZone': null.");
+ }
+ //
+ SimpleDateFormat sdf = new SimpleDateFormat(outputDateFormat, outputLocale);
+ sdf.setCalendar(Calendar.getInstance(outputTimeZone, outputLocale));
+ return sdf.format(date);
+ }
+
+ public Date getDateFromString(String x) throws MInvalidConversionFormatException {
+ Date y = null;
+ for (String dateFormat: this.getDateFormats()) {
+ try {
+ y = MDateConverter.getDateFromStringByParameters(x, dateFormat, this.getLocale(), this.getTimeZone());
+ return y;
+ }
+ catch (MInvalidConversionFormatException exception) {
+ }
+ }
+ if (null == y) {
+ throw new MInvalidConversionFormatException(String.format("Invalid 'x': %s.", x));
+ }
+ return y; // necessary to avoid Java compilation errors
+ }
+
+ public String getStringFromDate(Date x) {
+ return MDateConverter.getStringFromDateByParameters(x, this.getDefaultDateFormat(), this.getLocale(), this.getTimeZone());
+ }
+
+ /* Helpers. */
+
+ public static Date getFlatDate(Date x) {
+ if (null == x) {
+ return x;
+ }
+ //
+ Calendar calendar = new GregorianCalendar();
+ calendar.setTime(x);
+ calendar.set(Calendar.HOUR_OF_DAY, 0);
+ calendar.set(Calendar.MINUTE, 0);
+ calendar.set(Calendar.SECOND, 0);
+ calendar.set(Calendar.MILLISECOND, 0);
+ return calendar.getTime();
+ }
+
+ public static Date getCeilDate(Date x) {
+ if (null == x) {
+ return x;
+ }
+ //
+ Calendar calendar = new GregorianCalendar();
+ calendar.setTime(x);
+ calendar.set(Calendar.HOUR_OF_DAY, 23);
+ calendar.set(Calendar.MINUTE, 59);
+ calendar.set(Calendar.SECOND, 59);
+ calendar.set(Calendar.MILLISECOND, 999);
+ return calendar.getTime();
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.conversion;
+
+@SuppressWarnings("serial")
+public class MInvalidConversionFormatException extends MConversionException {
+
+ /* */
+
+ public MInvalidConversionFormatException() {
+ super();
+ }
+
+ public MInvalidConversionFormatException(String message) {
+ super(message);
+ }
+
+ public MInvalidConversionFormatException(Throwable error) {
+ super(error);
+ }
+
+ public MInvalidConversionFormatException(String message, Throwable error) {
+ super(message, error);
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.conversion;
+
+import com.marcozanon.macaco.MObject;
+import com.marcozanon.macaco.text.MText;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.ParsePosition;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Locale;
+
+public class MNumberConverter extends MObject {
+
+ protected LinkedHashSet<String> numberFormats = new LinkedHashSet<String>();
+ protected Locale locale = null;
+
+ /* */
+
+ public MNumberConverter(String defaultNumberFormat, Locale locale) {
+ super();
+ //
+ this.addNumberFormat(defaultNumberFormat);
+ this.setLocale(locale);
+ }
+
+ public MNumberConverter(LinkedHashSet<String> numberFormats, Locale locale) {
+ super();
+ //
+ this.setNumberFormats(numberFormats);
+ this.setLocale(locale);
+ }
+
+ public MNumberConverter clone() {
+ return new MNumberConverter(this.getNumberFormats(), this.getLocale());
+ }
+
+ /* Number formats. */
+
+ public void setNumberFormats(LinkedHashSet<String> numberFormats) {
+ if (null == numberFormats) {
+ throw new IllegalArgumentException("Invalid 'numberFormats': null.");
+ }
+ else {
+ Iterator<String> i = numberFormats.iterator();
+ while (i.hasNext()) {
+ MNumberConverter.checkNumberFormat(i.next());
+ }
+ }
+ //
+ synchronized (this.numberFormats) {
+ this.numberFormats = numberFormats;
+ }
+ }
+
+ public void addNumberFormat(String numberFormat) {
+ MNumberConverter.checkNumberFormat(numberFormat);
+ //
+ synchronized (this.numberFormats) {
+ this.numberFormats.add(numberFormat);
+ }
+ }
+
+ public LinkedHashSet<String> getNumberFormats() {
+ return this.numberFormats;
+ }
+
+ public String getDefaultNumberFormat() {
+ return this.getNumberFormats().iterator().next();
+ }
+
+ /* Locale. */
+
+ protected void setLocale(Locale locale) {
+ if (null == locale) {
+ throw new IllegalArgumentException("Invalid 'locale': null.");
+ }
+ //
+ this.locale = locale;
+ }
+
+ public Locale getLocale() {
+ return this.locale;
+ }
+
+ /* Conversions. */
+
+ protected static void checkNumberFormat(String numberFormat) {
+ if (MText.isBlank(numberFormat)) {
+ throw new IllegalArgumentException("Invalid 'numberFormat': null or empty.");
+ }
+ //
+ try {
+ DecimalFormat testNumberFormat = new DecimalFormat(numberFormat);
+ }
+ catch (IllegalArgumentException exception) {
+ throw new IllegalArgumentException(String.format("Invalid 'numberFormat': %s.", numberFormat)); // no need to propagate exception
+ }
+ }
+
+ protected static BigDecimal getNumberFromStringByParameters(String x, String inputNumberFormat, Locale inputLocale) throws MInvalidConversionFormatException {
+ if (MText.isBlank(x)) {
+ throw new IllegalArgumentException("Invalid 'x': null or empty.");
+ }
+ MNumberConverter.checkNumberFormat(inputNumberFormat);
+ if (null == inputLocale) {
+ throw new IllegalArgumentException("Invalid 'inputLocale': null.");
+ }
+ //
+ DecimalFormatSymbols dfs = new DecimalFormatSymbols(inputLocale);
+ DecimalFormat df = new DecimalFormat(inputNumberFormat, dfs);
+ df.setParseBigDecimal(true);
+ ParsePosition validPosition = new ParsePosition(0);
+ BigDecimal bd = (BigDecimal)df.parse(x, validPosition);
+ if (validPosition.getIndex() < x.length()) {
+ throw new MInvalidConversionFormatException(String.format("Invalid 'x' or parsing: %s (input format: %s).", x, inputNumberFormat));
+ }
+ return bd;
+ }
+
+ protected static String getStringFromNumberByParameters(BigDecimal number, String outputNumberFormat, Locale outputLocale) {
+ if (null == number) {
+ throw new IllegalArgumentException("Invalid 'number': null.");
+ }
+ MNumberConverter.checkNumberFormat(outputNumberFormat);
+ if (null == outputLocale) {
+ throw new IllegalArgumentException("Invalid 'outputLocale': null.");
+ }
+ //
+ DecimalFormatSymbols dfs = new DecimalFormatSymbols(outputLocale);
+ DecimalFormat df = new DecimalFormat(outputNumberFormat, dfs);
+ df.setRoundingMode(RoundingMode.HALF_UP);
+ return df.format(number);
+ }
+
+ public BigDecimal getNumberFromString(String x) throws MInvalidConversionFormatException {
+ BigDecimal y = null;
+ for (String numberFormat: this.getNumberFormats()) {
+ try {
+ y = MNumberConverter.getNumberFromStringByParameters(x, numberFormat, this.getLocale());
+ return y;
+ }
+ catch (MInvalidConversionFormatException exception) {
+ }
+ }
+ if (null == y) {
+ throw new MInvalidConversionFormatException(String.format("Invalid 'x': %s.", x));
+ }
+ return y; // necessary to avoid Java compilation errors
+ }
+
+ public String getStringFromNumber(BigDecimal x) {
+ return MNumberConverter.getStringFromNumberByParameters(x, this.getDefaultNumberFormat(), this.getLocale());
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.database;
+
+import com.marcozanon.macaco.MObject;
+import com.marcozanon.macaco.logging.MLogListener;
+import com.marcozanon.macaco.text.MText;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+
+public class MDatabaseConnection extends MObject {
+
+ public static enum TransactionStatus {
+ CLOSED,
+ SUCCESSFUL,
+ FAILED
+ };
+
+ protected String driver = null;
+ protected String url = null;
+ protected String username = null;
+ protected String password = null;
+ protected MLogListener logListener = null;
+
+ protected Connection connection = null;
+
+ protected MDatabaseConnection.TransactionStatus transactionStatus = MDatabaseConnection.TransactionStatus.CLOSED;
+
+ /* */
+
+ public MDatabaseConnection(String driver, String url, String username, String password, MLogListener logListener) throws MDatabaseConnectionFailureException {
+ super();
+ //
+ if (MText.isBlank(driver)) {
+ throw new IllegalArgumentException("Invalid 'driver': null or empty.");
+ }
+ if (MText.isBlank(url)) {
+ throw new IllegalArgumentException("Invalid 'url': null or empty.");
+ }
+ if (null == username) {
+ throw new IllegalArgumentException("Invalid 'username': null.");
+ }
+ if (null == password) {
+ throw new IllegalArgumentException("Invalid 'password': null.");
+ }
+ //
+ this.driver = driver;
+ this.url = url;
+ this.username = username;
+ this.password = password;
+ this.setLogListener(logListener);
+ // Load driver.
+ try {
+ Class.forName(this.getDriver()).newInstance();
+ }
+ catch (ClassNotFoundException exception) {
+ throw new MDatabaseConnectionFailureException("Could not load driver.", exception);
+ }
+ catch (IllegalAccessException exception) {
+ throw new MDatabaseConnectionFailureException("Could not load driver.", exception);
+ }
+ catch (InstantiationException exception) {
+ throw new MDatabaseConnectionFailureException("Could not load driver.", exception);
+ }
+ // Establish a connection.
+ try {
+ this.connection = DriverManager.getConnection(this.getUrl(), this.getUsername(), this.getPassword());
+ }
+ catch (SQLException exception) {
+ throw new MDatabaseConnectionFailureException("Could not get database connection.", exception);
+ }
+ // Set SQL mode to standard ANSI.
+ try {
+ this.getConnection().createStatement().executeUpdate("SET SQL_MODE='ANSI'");
+ this.logStatement("### SET SQL_MODE='ANSI' ###");
+ }
+ catch (SQLException exception) {
+ throw new MDatabaseConnectionFailureException("Could not set SQL mode to ANSI.", exception);
+ }
+ }
+
+ public void close() throws MDatabaseConnectionFailureException {
+ try {
+ this.getConnection().close();
+ }
+ catch (SQLException exception) {
+ throw new MDatabaseConnectionFailureException("Could not close database connection.", exception);
+ }
+ }
+
+ public boolean isClosed() throws MDatabaseConnectionFailureException {
+ try {
+ return this.getConnection().isClosed();
+ }
+ catch (SQLException exception) {
+ throw new MDatabaseConnectionFailureException("Could not determine the state.", exception);
+ }
+ }
+
+ protected void finalize() {
+ try {
+ this.close();
+ }
+ catch (Exception exception) {
+ }
+ }
+
+ /* Driver. */
+
+ public String getDriver() {
+ return this.driver;
+ }
+
+ /* Url. */
+
+ public String getUrl() {
+ return this.url;
+ }
+
+ /* Username. */
+
+ public String getUsername() {
+ return this.username;
+ }
+
+ /* Password. */
+
+ public String getPassword() {
+ return this.password;
+ }
+
+ /* Connection. */
+
+ protected Connection getConnection() {
+ return this.connection;
+ }
+
+ /* Transactions. */
+
+ protected void setTransactionStatus(MDatabaseConnection.TransactionStatus transactionStatus) {
+ if (null == transactionStatus) {
+ throw new IllegalArgumentException("Invalid 'transactionStatus': null.");
+ }
+ //
+ this.transactionStatus = transactionStatus;
+ }
+
+ public MDatabaseConnection.TransactionStatus getTransactionStatus() {
+ return this.transactionStatus;
+ }
+
+ public void startTransaction() throws MSqlTransactionException {
+ if (MDatabaseConnection.TransactionStatus.CLOSED != this.getTransactionStatus()) {
+ throw new MSqlTransactionException("Nested transactions not allowed.");
+ }
+ try {
+ this.getConnection().setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
+ this.getConnection().setAutoCommit(false);
+ this.setTransactionStatus(MDatabaseConnection.TransactionStatus.SUCCESSFUL);
+ this.logStatement("### BEGIN TRANSACTION ###");
+ }
+ catch (SQLException exception) {
+ throw new MSqlTransactionException("Could not start transaction.", exception);
+ }
+ }
+
+ public void rollBackTransaction() throws MSqlTransactionException {
+ if (MDatabaseConnection.TransactionStatus.CLOSED == this.getTransactionStatus()) {
+ throw new MSqlTransactionException("Transaction not started.");
+ }
+ try {
+ this.getConnection().rollback();
+ this.getConnection().setAutoCommit(true);
+ this.setTransactionStatus(MDatabaseConnection.TransactionStatus.CLOSED);
+ this.logStatement("### ROLLBACK ###");
+ }
+ catch (SQLException exception) {
+ this.setTransactionStatus(MDatabaseConnection.TransactionStatus.FAILED);
+ throw new MSqlTransactionException("Could not roll back transaction.", exception);
+ }
+ }
+
+ public MDatabaseConnection.TransactionStatus commitTransaction() throws MSqlTransactionException {
+ switch (this.getTransactionStatus()) {
+ case SUCCESSFUL:
+ try {
+ this.getConnection().commit();
+ this.getConnection().setAutoCommit(true);
+ this.setTransactionStatus(MDatabaseConnection.TransactionStatus.CLOSED);
+ this.logStatement("### COMMIT ###");
+ return MDatabaseConnection.TransactionStatus.SUCCESSFUL;
+ }
+ catch (SQLException exception) {
+ this.rollBackTransaction();
+ return MDatabaseConnection.TransactionStatus.FAILED;
+ }
+ case FAILED:
+ this.rollBackTransaction();
+ return MDatabaseConnection.TransactionStatus.FAILED;
+ default: // instead of "case CLOSED:" (necessary to avoid Java compilation errors)
+ throw new MSqlTransactionException("Transaction not started.");
+ }
+ }
+
+ /* Statements. */
+
+ public MSqlStatementResults executePreparedStatement(String statement) throws MSqlStatementException {
+ return this.executePreparedStatement(statement, new LinkedList<Object>());
+ }
+
+ public MSqlStatementResults executePreparedStatement(String statement, LinkedList<Object> parameters) throws MSqlStatementException {
+ if (MText.isBlank(statement)) {
+ throw new IllegalArgumentException("Invalid 'statement': null or empty.");
+ }
+ if (null == parameters) {
+ throw new IllegalArgumentException("Invalid 'parameters': null.");
+ }
+ //
+ MSqlStatementResults results = null;
+ PreparedStatement preparedStatement = null;
+ try {
+ // Prepare the statement.
+ preparedStatement = this.getConnection().prepareStatement(statement, PreparedStatement.RETURN_GENERATED_KEYS);
+ for (int p = 0; parameters.size() > p; p++) {
+ preparedStatement.setObject(p + 1, parameters.get(p));
+ }
+ // Execute the statement and parse the results.
+ preparedStatement.execute();
+ results = new MSqlStatementResults(preparedStatement);
+ this.logStatement(preparedStatement.toString());
+ }
+ catch (SQLException exception) {
+ if (MDatabaseConnection.TransactionStatus.SUCCESSFUL == this.getTransactionStatus()) {
+ this.setTransactionStatus(MDatabaseConnection.TransactionStatus.FAILED);
+ }
+ throw new MSqlStatementException(String.format("Could not execute prepared statement: %s.", preparedStatement), exception);
+ }
+ return results;
+ }
+
+ /* Tables. */
+
+ public MSqlTable getTable(String table, String primaryKey) {
+ return new MSqlTable(this, table, primaryKey);
+ }
+
+ /* Logging. */
+
+ public void setLogListener(MLogListener logListener) {
+ this.logListener = logListener;
+ }
+
+ protected MLogListener getLogListener() {
+ return this.logListener;
+ }
+
+ protected void logStatement(String statement) {
+ MLogListener logListener = this.getLogListener();
+ if (null != logListener) {
+ logListener.onMessageLogging(statement);
+ }
+ }
+
+ /* Engine version. */
+
+ public String getEngineVersion() throws MSqlStatementException {
+ MSqlStatementResults results = this.executePreparedStatement("SELECT VERSION()");
+ LinkedList<LinkedHashMap<String, Object>> resultList = results.getRecords();
+ //
+ return (String)resultList.get(0).get("VERSION()");
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.database;
+
+@SuppressWarnings("serial")
+public class MDatabaseConnectionFailureException extends MDatabaseException {
+
+ /* */
+
+ public MDatabaseConnectionFailureException() {
+ super();
+ }
+
+ public MDatabaseConnectionFailureException(String message) {
+ super(message);
+ }
+
+ public MDatabaseConnectionFailureException(Throwable error) {
+ super(error);
+ }
+
+ public MDatabaseConnectionFailureException(String message, Throwable error) {
+ super(message, error);
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.database;
+
+import com.marcozanon.macaco.MObject;
+import com.marcozanon.macaco.logging.MLogListener;
+import com.marcozanon.macaco.text.MText;
+
+public class MDatabaseConnectionGenerator extends MObject {
+
+ protected String driver = null;
+ protected String url = null;
+ protected String username = null;
+ protected String password = null;
+ protected MLogListener logListener = null;
+
+ /* */
+
+ public MDatabaseConnectionGenerator(String driver, String url, String username, String password, MLogListener logListener) {
+ super();
+ //
+ if (MText.isBlank(driver)) {
+ throw new IllegalArgumentException("Invalid 'driver': null or empty.");
+ }
+ if (MText.isBlank(url)) {
+ throw new IllegalArgumentException("Invalid 'url': null or empty.");
+ }
+ if (null == username) {
+ throw new IllegalArgumentException("Invalid 'username': null.");
+ }
+ if (null == password) {
+ throw new IllegalArgumentException("Invalid 'password': null.");
+ }
+ //
+ this.driver = driver;
+ this.url = url;
+ this.username = username;
+ this.password = password;
+ this.setLogListener(logListener);
+ }
+
+ /* Driver. */
+
+ public String getDriver() {
+ return this.driver;
+ }
+
+ /* Url. */
+
+ public String getUrl() {
+ return this.url;
+ }
+
+ /* Username. */
+
+ public String getUsername() {
+ return this.username;
+ }
+
+ /* Password. */
+
+ public String getPassword() {
+ return this.password;
+ }
+
+ /* Log listener. */
+
+ public MLogListener getLogListener() {
+ return this.logListener;
+ }
+
+ public void setLogListener(MLogListener logListener) {
+ this.logListener = logListener;
+ }
+
+ /* Generator. */
+
+ public MDatabaseConnection getNewDatabaseConnection() throws MDatabaseConnectionFailureException {
+ return new MDatabaseConnection(this.getDriver(), this.getUrl(), this.getUsername(), this.getPassword(), this.getLogListener());
+ }
+
+ public boolean isGeneratorFor(MDatabaseConnection databaseConnection) {
+ if (null == databaseConnection) {
+ throw new IllegalArgumentException("Invalid 'databaseConnection': null.");
+ }
+ //
+ if (!databaseConnection.getDriver().equals(this.getDriver())) {
+ return false;
+ }
+ if (!databaseConnection.getUrl().equals(this.getUrl())) {
+ return false;
+ }
+ if (!databaseConnection.getUsername().equals(this.getUsername())) {
+ return false;
+ }
+ if (!databaseConnection.getPassword().equals(this.getPassword())) {
+ return false;
+ }
+ if (databaseConnection.getLogListener() != this.getLogListener()) {
+ return false;
+ }
+ //
+ return true;
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.database;
+
+import com.marcozanon.macaco.MObject;
+import com.marcozanon.macaco.logging.MLogListener;
+import java.util.LinkedList;
+
+public class MDatabaseConnectionPool extends MObject {
+
+ protected MDatabaseConnectionGenerator databaseConnectionGenerator = null;
+
+ protected LinkedList<MDatabaseConnection> databaseConnections = new LinkedList<MDatabaseConnection>();
+ protected int minimumSize = 0;
+ protected int maximumSize = 0;
+
+ /* */
+
+ public MDatabaseConnectionPool(String driver, String url, String username, String password, int minimumSize, int maximumSize, MLogListener logListener) throws MDatabaseConnectionFailureException {
+ super();
+ //
+ if (0 > minimumSize) {
+ throw new IllegalArgumentException(String.format("Invalid 'minimumSize': %s.", minimumSize));
+ }
+ if (1 > maximumSize) {
+ throw new IllegalArgumentException(String.format("Invalid 'maximumSize': %s.", maximumSize));
+ }
+ else if (minimumSize > maximumSize) {
+ throw new IllegalArgumentException(String.format("Invalid 'maximumSize': %s < %s ('minimumSize').", maximumSize, minimumSize));
+ }
+ //
+ this.databaseConnectionGenerator = new MDatabaseConnectionGenerator(driver, url, username, password, logListener);
+ this.minimumSize = minimumSize;
+ this.maximumSize = maximumSize;
+ //
+ this.initialize();
+ }
+
+ protected void initialize() throws MDatabaseConnectionFailureException {
+ for (int c = 0; c < this.getMinimumSize(); c++) {
+ MDatabaseConnection databaseConnection = this.getDatabaseConnectionGenerator().getNewDatabaseConnection();
+ this.pushDatabaseConnection(databaseConnection);
+ }
+ }
+
+ /* Database connection generator. */
+
+ protected MDatabaseConnectionGenerator getDatabaseConnectionGenerator() {
+ return this.databaseConnectionGenerator;
+ }
+
+ /* Database connections. */
+
+ protected LinkedList<MDatabaseConnection> getDatabaseConnections() {
+ return this.databaseConnections;
+ }
+
+ protected int getMinimumSize() {
+ return this.minimumSize;
+ }
+
+ protected int getMaximumSize() {
+ return this.maximumSize;
+ }
+
+ public synchronized MDatabaseConnection popDatabaseConnection() throws MDatabaseConnectionFailureException {
+ LinkedList<MDatabaseConnection> databaseConnections = this.getDatabaseConnections();
+ MDatabaseConnection databaseConnection = null;
+ if (0 == databaseConnections.size()) {
+ databaseConnection = this.getDatabaseConnectionGenerator().getNewDatabaseConnection();
+ }
+ else {
+ databaseConnection = databaseConnections.removeLast();
+ }
+ if (this.getMinimumSize() > databaseConnections.size()) {
+ databaseConnections.add(this.getDatabaseConnectionGenerator().getNewDatabaseConnection());
+ }
+ return databaseConnection;
+ }
+
+ public synchronized void pushDatabaseConnection(MDatabaseConnection databaseConnection) throws MDatabaseConnectionFailureException {
+ if (null == databaseConnection) {
+ throw new IllegalArgumentException("Invalid 'databaseConnection': null.");
+ }
+ else if (!this.getDatabaseConnectionGenerator().isGeneratorFor(databaseConnection)) {
+ throw new IllegalArgumentException("Invalid 'databaseConnection': not compatible with this database connection pool.");
+ }
+ else if (databaseConnection.isClosed()) {
+ throw new IllegalArgumentException("Invalid 'databaseConnection': closed.");
+ }
+ //
+ LinkedList<MDatabaseConnection> databaseConnections = this.getDatabaseConnections();
+ if (this.getMaximumSize() >= databaseConnections.size()) {
+ databaseConnections.add(databaseConnection);
+ }
+ }
+
+ /* Logging. */
+
+ public synchronized void setLogListener(MLogListener logListener) {
+ this.getDatabaseConnectionGenerator().setLogListener(logListener);
+ //
+ for (MDatabaseConnection databaseConnection: this.getDatabaseConnections()) {
+ databaseConnection.setLogListener(logListener);
+ }
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.database;
+
+import com.marcozanon.macaco.MException;
+
+public abstract class MDatabaseException extends MException {
+
+ /* */
+
+ public MDatabaseException() {
+ super();
+ }
+
+ public MDatabaseException(String message) {
+ super(message);
+ }
+
+ public MDatabaseException(Throwable error) {
+ super(error);
+ }
+
+ public MDatabaseException(String message, Throwable error) {
+ super(message, error);
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.database;
+
+@SuppressWarnings("serial")
+public class MSqlStatementException extends MDatabaseException {
+
+ /* */
+
+ public MSqlStatementException() {
+ super();
+ }
+
+ public MSqlStatementException(String message) {
+ super(message);
+ }
+
+ public MSqlStatementException(Throwable error) {
+ super(error);
+ }
+
+ public MSqlStatementException(String message, Throwable error) {
+ super(message, error);
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.database;
+
+import com.marcozanon.macaco.MObject;
+import com.marcozanon.macaco.text.MText;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+
+public class MSqlStatementResults extends MObject {
+
+ protected PreparedStatement preparedStatement = null;
+ protected ResultSet resultSet = null;
+
+ protected LinkedList<LinkedHashMap<String, Object>> records = new LinkedList<LinkedHashMap<String, Object>>();
+
+ protected LinkedHashSet<Object> generatedKeys = new LinkedHashSet<Object>();
+
+ /* */
+
+ public MSqlStatementResults(PreparedStatement preparedStatement) throws MSqlStatementException {
+ super();
+ //
+ if (null == preparedStatement) {
+ throw new IllegalArgumentException("Invalid 'preparedStatement': null.");
+ }
+ //
+ this.preparedStatement = preparedStatement;
+ //
+ try {
+ this.resultSet = this.getPreparedStatement().getResultSet();
+ if (null != this.getResultSet()) {
+ while (this.getResultSet().next()) {
+ ResultSetMetaData resultSetMetaData = this.getResultSet().getMetaData();
+ LinkedHashMap<String, Object> record = new LinkedHashMap<String, Object>();
+ for (int f = 0; resultSetMetaData.getColumnCount() > f; f++) {
+ String field = resultSetMetaData.getColumnLabel(f + 1);
+ record.put(field, this.getResultSet().getObject(field));
+ }
+ this.getRecords().add(record);
+ }
+ }
+ }
+ catch (SQLException exception) {
+ throw new MSqlStatementException("Could not retrieve statement records.", exception);
+ }
+ //
+ try {
+ ResultSet generatedKeysResultSet = this.getPreparedStatement().getGeneratedKeys();
+ while (generatedKeysResultSet.next()) {
+ this.getGeneratedKeys().add(generatedKeysResultSet.getObject(1));
+ }
+ generatedKeysResultSet.close();
+ }
+ catch (SQLException exception) {
+ throw new MSqlStatementException("Could not retrieve generated keys.", exception);
+ }
+ }
+
+ public void close() throws MDatabaseConnectionFailureException {
+ try {
+ if (null != this.getResultSet()) {
+ this.getResultSet().close();
+ }
+ this.getPreparedStatement().close();
+ }
+ catch (SQLException exception) {
+ throw new MDatabaseConnectionFailureException("Could not close statement and/or result set references.", exception);
+ }
+ }
+
+ protected void finalize() {
+ try {
+ this.close();
+ }
+ catch (Exception exception) {
+ }
+ }
+
+ /* References. */
+
+ protected PreparedStatement getPreparedStatement() {
+ return this.preparedStatement;
+ }
+
+ public ResultSet getResultSet() {
+ return this.resultSet;
+ }
+
+ /* Records. */
+
+ public LinkedList<LinkedHashMap<String, Object>> getRecords() {
+ return this.records;
+ }
+
+ public LinkedList<Object> getRecordsByField(String field) {
+ if (MText.isBlank(field)) {
+ throw new IllegalArgumentException("Invalid 'field': null or empty.");
+ }
+ //
+ LinkedList<Object> recordsByField = new LinkedList<Object>();
+ for (LinkedHashMap<String, Object> record: this.getRecords()) {
+ Object r = record.get(field);
+ if (null == r) {
+ throw new IllegalArgumentException(String.format("Invalid 'field': %s.", field));
+ }
+ recordsByField.add(r);
+ }
+ return recordsByField;
+ }
+
+ /* Generated keys. */
+
+ public LinkedHashSet<Object> getGeneratedKeys() {
+ return this.generatedKeys;
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.database;
+
+import com.marcozanon.macaco.MObject;
+import com.marcozanon.macaco.text.MText;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+
+public class MSqlTable extends MObject {
+
+ protected MDatabaseConnection databaseConnection = null;
+ protected String table = null;
+ protected String primaryKey = null;
+
+ /* */
+
+ public MSqlTable(MDatabaseConnection databaseConnection, String table, String primaryKey) {
+ super();
+ //
+ if (null == databaseConnection) {
+ throw new IllegalArgumentException("Invalid 'databaseConnection': null.");
+ }
+ if (MText.isBlank(table)) {
+ throw new IllegalArgumentException("Invalid 'table': null or empty.");
+ }
+ if (MText.isBlank(primaryKey)) {
+ throw new IllegalArgumentException("Invalid 'primaryKey': null or empty.");
+ }
+ //
+ this.databaseConnection = databaseConnection;
+ this.table = table;
+ this.primaryKey = primaryKey;
+ }
+
+ /* Database connection. */
+
+ protected MDatabaseConnection getDatabaseConnection() {
+ return this.databaseConnection;
+ }
+
+ /* Table. */
+
+ public String getTable() {
+ return this.table;
+ }
+
+ /* Primary key. */
+
+ public String getPrimaryKey() {
+ return this.primaryKey;
+ }
+
+ /* Statements. */
+
+ protected MSqlStatementResults insertRecord(LinkedHashMap<String, Object> map) throws MSqlStatementException {
+ if (null == map) {
+ throw new IllegalArgumentException("Invalid 'map': null.");
+ }
+ //
+ LinkedList<Object> p = new LinkedList<Object>();
+ StringBuilder fields = new StringBuilder("");
+ StringBuilder s = new StringBuilder("");
+ for (String field: map.keySet()) {
+ fields.append("\"" + this.getTable() + "\".\"" + field + "\", ");
+ s.append("?, ");
+ p.add(map.get(field));
+ }
+ fields.delete(fields.length() - 2, fields.length());
+ s.delete(s.length() - 2, s.length());
+ return this.getDatabaseConnection().executePreparedStatement(" INSERT INTO \"" + this.getTable() + "\""
+ + " (" + fields + ")"
+ + " VALUES (" + s + ")",
+ p);
+ }
+
+ protected MSqlStatementResults updateRecord(LinkedHashMap<String, Object> map, Object id) throws MSqlStatementException {
+ if (null == map) {
+ throw new IllegalArgumentException("Invalid 'map': null.");
+ }
+ if (null == id) {
+ throw new IllegalArgumentException("Invalid 'id': null.");
+ }
+ //
+ LinkedList<Object> p = new LinkedList<Object>();
+ StringBuilder s = new StringBuilder("");
+ for (String field: map.keySet()) {
+ s.append("\"" + this.getTable() + "\".\"" + field + "\" = ?, ");
+ p.add(map.get(field));
+ }
+ s.delete(s.length() - 2, s.length());
+ p.add(id);
+ return this.getDatabaseConnection().executePreparedStatement(" UPDATE \"" + this.getTable() + "\""
+ + " SET " + s
+ + " WHERE (\"" + this.getTable() + "\".\"" + this.getPrimaryKey() + "\" = ?)",
+ p);
+ }
+
+ public MSqlStatementResults setRecord(LinkedHashMap<String, Object> map, Object id) throws MSqlStatementException {
+ if (null == id) {
+ return this.insertRecord(map);
+ }
+ else {
+ return this.updateRecord(map, id);
+ }
+ }
+
+ public MSqlStatementResults getRecord(Object id) throws MSqlStatementException {
+ if (null == id) {
+ throw new IllegalArgumentException("Invalid 'id': null.");
+ }
+ //
+ LinkedList<Object> p = new LinkedList<Object>();
+ p.add(id);
+ return this.getDatabaseConnection().executePreparedStatement(" SELECT *"
+ + " FROM \"" + this.getTable() + "\""
+ + " WHERE (\"" + this.getTable() + "\".\"" + this.getPrimaryKey() + "\" = ?)",
+ p);
+ }
+
+ public MSqlStatementResults deleteRecord(Object id) throws MSqlStatementException {
+ if (null == id) {
+ throw new IllegalArgumentException("Invalid 'id': null.");
+ }
+ //
+ LinkedList<Object> p = new LinkedList<Object>();
+ p.add(id);
+ return this.getDatabaseConnection().executePreparedStatement(" DELETE FROM \"" + this.getTable() + "\""
+ + " WHERE (\"" + this.getTable() + "\".\"" + this.getPrimaryKey() + "\" = ?)",
+ p);
+ }
+
+ public MSqlStatementResults deleteRecords(LinkedHashSet idSet) throws MSqlStatementException {
+ if (null == idSet) {
+ throw new IllegalArgumentException("Invalid 'idSet': null.");
+ }
+ //
+ StringBuilder whereClause = new StringBuilder("(0)");
+ LinkedList<Object> p = new LinkedList<Object>();
+ for (Object id: idSet) {
+ whereClause.append(" OR (\"" + this.getTable() + "\".\"" + this.getPrimaryKey() + "\" = ?)");
+ p.add(id);
+ }
+ return this.getDatabaseConnection().executePreparedStatement(" DELETE FROM \"" + this.getTable() + "\""
+ + " WHERE (" + whereClause + ")",
+ p);
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.database;
+
+@SuppressWarnings("serial")
+public class MSqlTransactionException extends MDatabaseException {
+
+ /* */
+
+ public MSqlTransactionException() {
+ super();
+ }
+
+ public MSqlTransactionException(String message) {
+ super(message);
+ }
+
+ public MSqlTransactionException(Throwable error) {
+ super(error);
+ }
+
+ public MSqlTransactionException(String message, Throwable error) {
+ super(message, error);
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.json;
+
+@SuppressWarnings("serial")
+public class MInvalidJsonValueException extends MJsonException {
+
+ /* */
+
+ public MInvalidJsonValueException() {
+ super();
+ }
+
+ public MInvalidJsonValueException(String message) {
+ super(message);
+ }
+
+ public MInvalidJsonValueException(Throwable error) {
+ super(error);
+ }
+
+ public MInvalidJsonValueException(String message, Throwable error) {
+ super(message, error);
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.json;
+
+import com.marcozanon.macaco.text.MText;
+import java.util.LinkedList;
+
+public class MJsonArray extends MJsonValue {
+
+ protected static enum ParsingMode {
+ START,
+ POST_VALUE,
+ POST_VALUE_SEPARATOR,
+ ERROR
+ };
+
+ protected LinkedList<MJsonValue> values = new LinkedList<MJsonValue>();
+
+ /* */
+
+ public MJsonArray() throws MInvalidJsonValueException {
+ this("[]");
+ }
+
+ public MJsonArray(String x) throws MInvalidJsonValueException {
+ super();
+ //
+ this.parseString(x);
+ }
+
+ public MJsonArray clone() {
+ MJsonArray tmpMJsonArray = null;
+ try {
+ tmpMJsonArray = new MJsonArray(this.getJsonValue());
+ }
+ catch (MInvalidJsonValueException exception) { // cannot happen
+ }
+ return tmpMJsonArray;
+ }
+
+ /* Value handlers. */
+
+ public void addValue(MJsonValue x) {
+ if (null == x) {
+ throw new IllegalArgumentException("Invalid 'x': null.");
+ }
+ //
+ this.getValues().add(x);
+ }
+
+ public void setValue(int index, MJsonValue x) {
+ if ((0 > index) || ((this.getValueCount() - 1) < index)) {
+ throw new IllegalArgumentException(String.format("Invalid 'index': %s: out of range.", index));
+ }
+ if (null == x) {
+ throw new IllegalArgumentException("Invalid 'x': null.");
+ }
+ //
+ this.getValues().set(index, x);
+ }
+
+ protected LinkedList<MJsonValue> getValues() {
+ return this.values;
+ }
+
+ public MJsonValue getValue(int index) {
+ if ((0 > index) || ((this.getValueCount() - 1) < index)) {
+ throw new IllegalArgumentException(String.format("Invalid 'index': %s: out of range.", index));
+ }
+ //
+ return this.getValues().get(index).clone();
+ }
+
+ public int getValueCount() {
+ return this.getValues().size();
+ }
+
+ public void removeValue(int index) {
+ if ((0 > index) || ((this.getValueCount() - 1) < index)) {
+ throw new IllegalArgumentException(String.format("Invalid 'index': %s: out of range.", index));
+ }
+ //
+ this.getValues().remove(index);
+ }
+
+ public void clearValues() {
+ this.getValues().clear();
+ }
+
+ /* Parsers. */
+
+ protected static int getTokenLength(String x) {
+ if (null == x) {
+ throw new IllegalArgumentException("Invalid 'x': null.");
+ }
+ //
+ if ((2 > x.length()) || ('[' != x.charAt(0))) {
+ return 0;
+ }
+ int position = x.indexOf("]", 1);
+ while (-1 < position) {
+ try {
+ MJsonArray testValue = new MJsonArray();
+ testValue.parseString(x.substring(0, position + 1));
+ return position + 1;
+ }
+ catch (MInvalidJsonValueException exception) {
+ position = x.indexOf("]", position + 1);
+ }
+ }
+ return 0;
+ }
+
+ public void parseString(String x) throws MInvalidJsonValueException {
+ if (MText.isBlank(x)) {
+ throw new IllegalArgumentException("Invalid 'x': null or empty.");
+ }
+ //
+ if ((2 > x.length()) || ('[' != x.charAt(0)) || (']' != x.charAt(x.length() - 1))) {
+ throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: must begin and end with square brackets.", x));
+ }
+ String y = x.substring(1, x.length() - 1);
+ int parsingPosition = 0;
+ MJsonArray.ParsingMode parsingMode = MJsonArray.ParsingMode.START;
+ while ((y.length() > parsingPosition) && (MJsonArray.ParsingMode.ERROR != parsingMode)) {
+ int c = y.codePointAt(parsingPosition);
+ switch (parsingMode) {
+ case START:
+ case POST_VALUE_SEPARATOR: // a value is expected
+ if (MJsonValue.isWhitespace(c)) {
+ parsingPosition++;
+ }
+ else if ((MJsonValue.isValueSeparator(c)) || (MJsonValue.isNameSeparator(c)) || (MJsonValue.isClosingStructuralCharacter(c))) {
+ parsingMode = MJsonArray.ParsingMode.ERROR;
+ }
+ else {
+ int offset = 0;
+ if (0 < (offset = MJsonNull.getTokenLength(y.substring(parsingPosition)))) {
+ this.addValue(new MJsonNull(y.substring(parsingPosition, parsingPosition + offset)));
+ }
+ else if (0 < (offset = MJsonBoolean.getTokenLength(y.substring(parsingPosition)))) {
+ this.addValue(new MJsonBoolean(y.substring(parsingPosition, parsingPosition + offset)));
+ }
+ else if (0 < (offset = MJsonNumber.getTokenLength(y.substring(parsingPosition)))) {
+ this.addValue(new MJsonNumber(y.substring(parsingPosition, parsingPosition + offset)));
+ }
+ else if (0 < (offset = MJsonString.getTokenLength(y.substring(parsingPosition)))) {
+ this.addValue(new MJsonString(y.substring(parsingPosition, parsingPosition + offset)));
+ }
+ else if (0 < (offset = MJsonArray.getTokenLength(y.substring(parsingPosition)))) {
+ this.addValue(new MJsonArray(y.substring(parsingPosition, parsingPosition + offset)));
+ }
+ else if (0 < (offset = MJsonObject.getTokenLength(y.substring(parsingPosition)))) {
+ this.addValue(new MJsonObject(y.substring(parsingPosition, parsingPosition + offset)));
+ }
+ if (0 < offset) {
+ parsingPosition += offset;
+ parsingMode = MJsonArray.ParsingMode.POST_VALUE;
+ }
+ else {
+ parsingMode = MJsonArray.ParsingMode.ERROR;
+ }
+ }
+ break;
+ case POST_VALUE:
+ if (MJsonValue.isWhitespace(c)) {
+ parsingPosition++;
+ }
+ else if (MJsonValue.isValueSeparator(c)) {
+ parsingPosition++;
+ parsingMode = MJsonArray.ParsingMode.POST_VALUE_SEPARATOR;
+ }
+ else {
+ parsingMode = MJsonArray.ParsingMode.ERROR;
+ }
+ break;
+ case ERROR: // malformed string
+ break;
+ }
+ }
+ if (MJsonArray.ParsingMode.POST_VALUE_SEPARATOR == parsingMode) {
+ parsingMode = MJsonArray.ParsingMode.ERROR;
+ }
+ if (MJsonArray.ParsingMode.ERROR == parsingMode) {
+ throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json array.", x));
+ }
+ }
+
+ /* Formatter. */
+
+ public String getJsonValue() {
+ StringBuilder s = new StringBuilder("");
+ s.append("[");
+ for (int i = 0; this.getValueCount() > i; i++) {
+ if (0 < i) {
+ s.append(", ");
+ }
+ s.append(this.getValues().get(i).getJsonValue());
+ }
+ s.append("]");
+ return s.toString();
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.json;
+
+import com.marcozanon.macaco.text.MText;
+
+public class MJsonBoolean extends MJsonValue {
+
+ protected Boolean value = null;
+
+ /* */
+
+ public MJsonBoolean() throws MInvalidJsonValueException {
+ this("false");
+ }
+
+ public MJsonBoolean(String x) throws MInvalidJsonValueException {
+ super();
+ //
+ this.parseString(x);
+ }
+
+ public MJsonBoolean clone() {
+ MJsonBoolean tmpMJsonBoolean = null;
+ try {
+ tmpMJsonBoolean = new MJsonBoolean(this.getJsonValue());
+ }
+ catch (MInvalidJsonValueException exception) { // cannot happen
+ }
+ return tmpMJsonBoolean;
+ }
+
+ /* Value handlers. */
+
+ public void setValue(Boolean x) {
+ if (null == x) {
+ throw new IllegalArgumentException("Invalid 'x': null.");
+ }
+ //
+ this.value = x;
+ }
+
+ public Boolean getValue() {
+ return this.value;
+ }
+
+ /* Parsers. */
+
+ protected static int getTokenLength(String x) {
+ if (null == x) {
+ throw new IllegalArgumentException("Invalid 'x': null.");
+ }
+ //
+ if (x.startsWith("true")) {
+ return "true".length();
+ }
+ else if (x.startsWith("false")) {
+ return "false".length();
+ }
+ return 0;
+ }
+
+ public void parseString(String x) throws MInvalidJsonValueException {
+ if (MText.isBlank(x)) {
+ throw new IllegalArgumentException("Invalid 'x': null or empty.");
+ }
+ //
+ if ((!"true".equals(x)) && (!"false".equals(x))) {
+ throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json boolean.", x));
+ }
+ this.setValue(Boolean.valueOf(x));
+ }
+
+ /* Formatter. */
+
+ public String getJsonValue() {
+ return this.getValue().toString();
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.json;
+
+import com.marcozanon.macaco.MException;
+
+public abstract class MJsonException extends MException {
+
+ /* */
+
+ public MJsonException() {
+ super();
+ }
+
+ public MJsonException(String message) {
+ super(message);
+ }
+
+ public MJsonException(Throwable error) {
+ super(error);
+ }
+
+ public MJsonException(String message, Throwable error) {
+ super(message, error);
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.json;
+
+import com.marcozanon.macaco.text.MText;
+
+public class MJsonNull extends MJsonValue {
+
+ /* */
+
+ public MJsonNull() throws MInvalidJsonValueException {
+ this("null");
+ }
+
+ public MJsonNull(String x) throws MInvalidJsonValueException {
+ super();
+ //
+ this.parseString(x);
+ }
+
+ public MJsonNull clone() {
+ MJsonNull tmpMJsonNull = null;
+ try {
+ tmpMJsonNull = new MJsonNull(this.getJsonValue());
+ }
+ catch (MInvalidJsonValueException exception) { // cannot happen
+ }
+ return tmpMJsonNull;
+ }
+
+ /* Parsers. */
+
+ protected static int getTokenLength(String x) {
+ if (null == x) {
+ throw new IllegalArgumentException("Invalid 'x': null.");
+ }
+ //
+ if (x.startsWith("null")) {
+ return "null".length();
+ }
+ return 0;
+ }
+
+ public void parseString(String x) throws MInvalidJsonValueException {
+ if (MText.isBlank(x)) {
+ throw new IllegalArgumentException("Invalid 'x': null or empty.");
+ }
+ //
+ if (!"null".equals(x)) {
+ throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json null.", x));
+ }
+ }
+
+ /* Formatter. */
+
+ public String getJsonValue() {
+ return "null";
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.json;
+
+import com.marcozanon.macaco.text.MText;
+import java.math.BigDecimal;
+
+public class MJsonNumber extends MJsonValue {
+
+ protected BigDecimal value = null;
+
+ /* */
+
+ public MJsonNumber() throws MInvalidJsonValueException {
+ this("0");
+ }
+
+ public MJsonNumber(String x) throws MInvalidJsonValueException {
+ super();
+ //
+ this.parseString(x);
+ }
+
+ public MJsonNumber clone() {
+ MJsonNumber tmpMJsonNumber = null;
+ try {
+ tmpMJsonNumber = new MJsonNumber(this.getJsonValue());
+ }
+ catch (MInvalidJsonValueException exception) { // cannot happen
+ }
+ return tmpMJsonNumber;
+ }
+
+ /* Value handlers. */
+
+ public void setValue(Number x) {
+ if (null == x) {
+ throw new IllegalArgumentException("Invalid 'x': null.");
+ }
+ //
+ this.value = new BigDecimal(x.toString());
+ }
+
+ public BigDecimal getValue() {
+ return this.value;
+ }
+
+ /* Parsers. */
+
+ protected static int getTokenLength(String x) {
+ if (null == x) {
+ throw new IllegalArgumentException("Invalid 'x': null.");
+ }
+ //
+ if (' ' == x.charAt(0)) {
+ return 0;
+ }
+ int position = 0;
+ int validPosition = 0;
+ BigDecimal y = null;
+ while (x.length() > position) {
+ if (' ' == x.charAt(position)) {
+ break;
+ }
+ else {
+ try {
+ y = new BigDecimal(x.substring(0, position + 1));
+ position++;
+ validPosition = position;
+ }
+ catch (NumberFormatException exception) {
+ position++;
+ }
+ }
+ }
+ return validPosition;
+ }
+
+ public void parseString(String x) throws MInvalidJsonValueException {
+ if (MText.isBlank(x)) {
+ throw new IllegalArgumentException("Invalid 'x': null or empty.");
+ }
+ //
+ BigDecimal y = null;
+ try {
+ y = new BigDecimal(x);
+ }
+ catch (NumberFormatException exception) {
+ throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json number.", x)); // no need to propagate exception
+ }
+ this.setValue(y);
+ }
+
+ /* Formatter. */
+
+ public String getJsonValue() {
+ return this.getValue().toString();
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.json;
+
+import com.marcozanon.macaco.text.MText;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+
+public class MJsonObject extends MJsonValue {
+
+ protected static enum ParsingMode {
+ START,
+ POST_NAME,
+ POST_NAME_SEPARATOR,
+ POST_VALUE,
+ POST_VALUE_SEPARATOR,
+ ERROR
+ };
+
+ protected LinkedHashMap<String, MJsonValue> values = new LinkedHashMap<String, MJsonValue>();
+
+ /* */
+
+ public MJsonObject() throws MInvalidJsonValueException {
+ this("{}");
+ }
+
+ public MJsonObject(String x) throws MInvalidJsonValueException {
+ super();
+ //
+ this.parseString(x);
+ }
+
+ public MJsonObject clone() {
+ MJsonObject tmpMJsonObject = null;
+ try {
+ tmpMJsonObject = new MJsonObject(this.getJsonValue());
+ }
+ catch (MInvalidJsonValueException exception) { // cannot happen
+ }
+ return tmpMJsonObject;
+ }
+
+ /* Value handlers. */
+
+ public void setValue(String key, MJsonValue x) {
+ if (MText.isBlank(key)) {
+ throw new IllegalArgumentException("Invalid 'key': null or empty.");
+ }
+ if (null == x) {
+ throw new IllegalArgumentException("Invalid 'x': null.");
+ }
+ //
+ this.getValues().put(key, x);
+ }
+
+ protected LinkedHashMap<String, MJsonValue> getValues() {
+ return this.values;
+ }
+
+ public MJsonValue getValue(String key) {
+ if (MText.isBlank(key)) {
+ throw new IllegalArgumentException("Invalid 'key': null or empty.");
+ }
+ //
+ if (!this.containsKey(key)) {
+ throw new IllegalArgumentException(String.format("Invalid 'key': %s: not available.", key));
+ }
+ return this.getValues().get(key).clone();
+ }
+
+ public int getValueCount() {
+ return this.getValues().size();
+ }
+
+ public void removeValue(String key) {
+ if (MText.isBlank(key)) {
+ throw new IllegalArgumentException("Invalid 'key': null or empty.");
+ }
+ //
+ if (!this.containsKey(key)) {
+ throw new IllegalArgumentException(String.format("Invalid 'key': %s: not available.", key));
+ }
+ this.getValues().remove(key);
+ }
+
+ public void clearValues() {
+ this.getValues().clear();
+ }
+
+ public boolean containsKey(String key) {
+ if (MText.isBlank(key)) {
+ throw new IllegalArgumentException("Invalid 'key': null or empty.");
+ }
+ //
+ return this.getValues().containsKey(key);
+ }
+
+ public LinkedHashSet<String> getKeys() {
+ LinkedHashSet<String> keys = new LinkedHashSet<String>();
+ for (String key: this.getValues().keySet()) {
+ keys.add(key);
+ }
+ return keys;
+ }
+
+ /* Parsers. */
+
+ protected static int getTokenLength(String x) {
+ if (null == x) {
+ throw new IllegalArgumentException("Invalid 'x': null.");
+ }
+ //
+ if ((2 > x.length()) || ('{' != x.charAt(0))) {
+ return 0;
+ }
+ int position = x.indexOf("}", 1);
+ while (-1 < position) {
+ try {
+ MJsonObject testValue = new MJsonObject();
+ testValue.parseString(x.substring(0, position + 1));
+ return position + 1;
+ }
+ catch (MInvalidJsonValueException exception) {
+ position = x.indexOf("}", position + 1);
+ }
+ }
+ return 0;
+ }
+
+ public void parseString(String x) throws MInvalidJsonValueException {
+ if (MText.isBlank(x)) {
+ throw new IllegalArgumentException("Invalid 'x': null or empty.");
+ }
+ //
+ if ((2 > x.length()) || ('{' != x.charAt(0)) || ('}' != x.charAt(x.length() - 1))) {
+ throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: must begin and end with curly brackets.", x));
+ }
+ String y = x.substring(1, x.length() - 1);
+ int parsingPosition = 0;
+ MJsonObject.ParsingMode parsingMode = MJsonObject.ParsingMode.START;
+ String lastKey = "";
+ while ((y.length() > parsingPosition) && (MJsonObject.ParsingMode.ERROR != parsingMode)) {
+ int c = y.codePointAt(parsingPosition);
+ switch (parsingMode) {
+ case START:
+ case POST_VALUE_SEPARATOR: // a name is expected
+ if (MJsonValue.isWhitespace(c)) {
+ parsingPosition++;
+ }
+ else if ((MJsonValue.isValueSeparator(c)) || (MJsonValue.isNameSeparator(c)) || (MJsonValue.isOpeningStructuralCharacter(c)) || (MJsonValue.isClosingStructuralCharacter(c))) {
+ parsingMode = MJsonObject.ParsingMode.ERROR;
+ }
+ else {
+ int offset = MJsonString.getTokenLength(y.substring(parsingPosition));
+ if (0 < offset) {
+ lastKey = (new MJsonString(y.substring(parsingPosition, parsingPosition + offset))).getValue();
+ parsingPosition += offset;
+ parsingMode = MJsonObject.ParsingMode.POST_NAME;
+ }
+ else {
+ parsingMode = MJsonObject.ParsingMode.ERROR;
+ }
+ }
+ break;
+ case POST_NAME:
+ if (MJsonValue.isWhitespace(c)) {
+ parsingPosition++;
+ }
+ else if (MJsonValue.isNameSeparator(c)) {
+ parsingPosition++;
+ parsingMode = MJsonObject.ParsingMode.POST_NAME_SEPARATOR;
+ }
+ else {
+ parsingMode = MJsonObject.ParsingMode.ERROR;
+ }
+ break;
+ case POST_NAME_SEPARATOR: // a value is expected
+ if (MJsonValue.isWhitespace(c)) {
+ parsingPosition++;
+ }
+ else if ((MJsonValue.isValueSeparator(c)) || (MJsonValue.isNameSeparator(c)) || (MJsonValue.isClosingStructuralCharacter(c))) {
+ parsingMode = MJsonObject.ParsingMode.ERROR;
+ }
+ else {
+ int offset = 0;
+ if (0 < (offset = MJsonNull.getTokenLength(y.substring(parsingPosition)))) {
+ this.setValue(lastKey, new MJsonNull(y.substring(parsingPosition, parsingPosition + offset)));
+ }
+ else if (0 < (offset = MJsonBoolean.getTokenLength(y.substring(parsingPosition)))) {
+ this.setValue(lastKey, new MJsonBoolean(y.substring(parsingPosition, parsingPosition + offset)));
+ }
+ else if (0 < (offset = MJsonNumber.getTokenLength(y.substring(parsingPosition)))) {
+ this.setValue(lastKey, new MJsonNumber(y.substring(parsingPosition, parsingPosition + offset)));
+ }
+ else if (0 < (offset = MJsonString.getTokenLength(y.substring(parsingPosition)))) {
+ this.setValue(lastKey, new MJsonString(y.substring(parsingPosition, parsingPosition + offset)));
+ }
+ else if (0 < (offset = MJsonArray.getTokenLength(y.substring(parsingPosition)))) {
+ this.setValue(lastKey, new MJsonArray(y.substring(parsingPosition, parsingPosition + offset)));
+ }
+ else if (0 < (offset = MJsonObject.getTokenLength(y.substring(parsingPosition)))) {
+ this.setValue(lastKey, new MJsonObject(y.substring(parsingPosition, parsingPosition + offset)));
+ }
+ if (0 < offset) {
+ lastKey = "";
+ parsingPosition += offset;
+ parsingMode = MJsonObject.ParsingMode.POST_VALUE;
+ }
+ else {
+ parsingMode = MJsonObject.ParsingMode.ERROR;
+ }
+ }
+ break;
+ case POST_VALUE:
+ if (MJsonValue.isWhitespace(c)) {
+ parsingPosition++;
+ }
+ else if (MJsonValue.isValueSeparator(c)) {
+ parsingPosition++;
+ parsingMode = MJsonObject.ParsingMode.POST_VALUE_SEPARATOR;
+ }
+ else {
+ parsingMode = MJsonObject.ParsingMode.ERROR;
+ }
+ break;
+ case ERROR: // malformed string
+ break;
+ }
+ }
+ if ((MJsonObject.ParsingMode.POST_NAME == parsingMode) || (MJsonObject.ParsingMode.POST_NAME_SEPARATOR == parsingMode) || (MJsonObject.ParsingMode.POST_VALUE_SEPARATOR == parsingMode)) {
+ parsingMode = MJsonObject.ParsingMode.ERROR;
+ }
+ if (MJsonObject.ParsingMode.ERROR == parsingMode) {
+ throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json object.", x));
+ }
+ }
+
+ /* Formatter. */
+
+ public String getJsonValue() {
+ StringBuilder s = new StringBuilder("");
+ s.append("{");
+ int k = 0;
+ for (String key: this.getValues().keySet()) {
+ if (0 < k) {
+ s.append(", ");
+ }
+ s.append("\"" + MJsonString.getEscapedString(key, false) + "\"");
+ s.append(": ");
+ s.append(this.getValues().get(key).getJsonValue());
+ k++;
+ }
+ s.append("}");
+ return s.toString();
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.json;
+
+import com.marcozanon.macaco.text.MText;
+
+public class MJsonString extends MJsonValue {
+
+ protected boolean extendedEscapeMode = false;
+
+ protected String value = null;
+
+ /* */
+
+ public MJsonString() throws MInvalidJsonValueException {
+ this("\"\"");
+ }
+
+ public MJsonString(String x) throws MInvalidJsonValueException {
+ this(x, false);
+ }
+
+ public MJsonString(String x, boolean extendedEscapeMode) throws MInvalidJsonValueException {
+ super();
+ //
+ this.setExtendedEscapeMode(extendedEscapeMode);
+ this.parseString(x);
+ }
+
+ public MJsonString clone() {
+ MJsonString tmpMJsonString = null;
+ try {
+ tmpMJsonString = new MJsonString(this.getJsonValue());
+ }
+ catch (MInvalidJsonValueException exception) { // cannot happen
+ }
+ return tmpMJsonString;
+ }
+
+ /* Extended escape mode. */
+
+ public void setExtendedEscapeMode(boolean extendedEscapeMode) {
+ this.extendedEscapeMode = extendedEscapeMode;
+ }
+
+ public boolean getExtendedEscapeMode() {
+ return this.extendedEscapeMode;
+ }
+
+ /* Value handlers. */
+
+ public void setValue(String x) {
+ if (null == x) {
+ throw new IllegalArgumentException("Invalid 'x': null.");
+ }
+ //
+ this.value = x;
+ }
+
+ public String getValue() {
+ return this.value;
+ }
+
+ /* Helpers. */
+
+ protected static String getEscapedString(String x, boolean extendedEscapeMode) {
+ if (null == x) {
+ throw new IllegalArgumentException("Invalid 'x': null.");
+ }
+ //
+ StringBuilder y = new StringBuilder();
+ for (int i = 0; x.length() > i; i++) {
+ int c = x.codePointAt(i);
+ if (extendedEscapeMode) {
+ if (0x00001F >= c) { // control Ascii character (0x000000-0x00001F)
+ y.append("\\u" + String.format("%4H", c).replace(" ", "0"));
+ }
+ else if (0x00007F >= c) { // non-control Ascii character (0x000020-0x00007F)
+ if ('"' == c) {
+ y.append("\\\"");
+ }
+ else if ('\\' == c) {
+ y.append("\\\\");
+ }
+ else if ('/' == c) {
+ y.append("\\/");
+ }
+ else {
+ y.appendCodePoint(c);
+ }
+ }
+ else if (0x00FFFF >= c) { // non-Ascii Bmp character (0x000080-0x00FFFF)
+ y.append("\\u" + String.format("%4H", c).replace(" ", "0"));
+ }
+ else { // non-Bmp character (0x010000-0x10FFFF)
+ int cc = c - 0x010000;
+ int ch = 0xD800 + (cc >> 10);
+ int cl = 0xDC00 + (cc & 0x03FF);
+ y.append("\\u" + String.format("%4H", ch).replace(" ", "0") + "\\u" + String.format("%4H", cl).replace(" ", "0"));
+ i++;
+ }
+ }
+ else {
+ if ('"' == c) {
+ y.append("\\\"");
+ }
+ else if ('\\' == c) {
+ y.append("\\\\");
+ }
+ else if ('/' == c) {
+ y.append("\\/");
+ }
+ else if ('\b' == c) {
+ y.append("\\b");
+ }
+ else if ('\f' == c) {
+ y.append("\\f");
+ }
+ else if ('\n' == c) {
+ y.append("\\n");
+ }
+ else if ('\r' == c) {
+ y.append("\\r");
+ }
+ else if ('\t' == c) {
+ y.append("\\t");
+ }
+ else if (0x00001F >= c) { // non-special control Ascii character (0x000000-0x00001F)
+ y.append("\\u" + String.format("%4H", c).replace(" ", "0"));
+ }
+ else {
+ y.appendCodePoint(c);
+ if (0x010000 <= c) {
+ i++;
+ }
+ }
+ }
+ }
+ return y.toString();
+ }
+
+ protected static String getUnescapedString(String x) throws MInvalidJsonValueException {
+ if (null == x) {
+ throw new IllegalArgumentException("Invalid 'x': null.");
+ }
+ //
+ StringBuilder y = new StringBuilder();
+ for (int i = 0; x.length() > i; i++) {
+ if (x.indexOf("\\\"", i) == i) {
+ y.append("\"");
+ i++;
+ }
+ else if (x.indexOf("\\\\", i) == i) {
+ y.append("\\");
+ i++;
+ }
+ else if (x.indexOf("\\/", i) == i) {
+ y.append("/");
+ i++;
+ }
+ else if (x.indexOf("\\b", i) == i) {
+ y.append("\b");
+ i++;
+ }
+ else if (x.indexOf("\\f", i) == i) {
+ y.append("\f");
+ i++;
+ }
+ else if (x.indexOf("\\n", i) == i) {
+ y.append("\n");
+ i++;
+ }
+ else if (x.indexOf("\\r", i) == i) {
+ y.append("\r");
+ i++;
+ }
+ else if (x.indexOf("\\t", i) == i) {
+ y.append("\t");
+ i++;
+ }
+ else if (x.indexOf("\\u", i) == i) {
+ if ((i + 2 + 4) > x.length()) {
+ throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json string.", x));
+ }
+ else {
+ int ch = -1;
+ try {
+ ch = Integer.parseInt(x.substring(i + 2, i + 2 + 4), 16);
+ i += 5;
+ if ((0xD800 <= ch) && (0xDBFF >= ch)) {
+ i++;
+ if (x.indexOf("\\u", i) != i) {
+ throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json string.", x));
+ }
+ else if ((i + 2 + 4) > x.length()) {
+ throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json string.", x));
+ }
+ else {
+ int cl = -1;
+ try {
+ cl = Integer.parseInt(x.substring(i + 2, i + 2 + 4), 16);
+ i += 5;
+ if ((0xDC00 >= cl) || (0xDFFF <= cl)) {
+ throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json string.", x));
+ }
+ int c = ((ch - 0xD800) << 10) + (cl - 0xDC00) + 0x010000;
+ y.appendCodePoint(c);
+ }
+ catch (NumberFormatException exception) {
+ throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json string.", x)); // no need to propagate exception
+ }
+ }
+ }
+ else {
+ y.appendCodePoint(ch);
+ }
+ }
+ catch (NumberFormatException exception) {
+ throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json string.", x)); // no need to propagate exception
+ }
+ }
+ }
+ else {
+ int c = x.codePointAt(i);
+ if (0x010000 <= c) {
+ i++;
+ }
+ y.appendCodePoint(c);
+ }
+ }
+ return y.toString();
+ }
+
+ /* Parsers. */
+
+ protected static int getTokenLength(String x) {
+ if (null == x) {
+ throw new IllegalArgumentException("Invalid 'x': null.");
+ }
+ //
+ if ((2 > x.length()) || ('"' != x.charAt(0))) {
+ return 0;
+ }
+ if ('"' == x.charAt(1)) {
+ return 1 + 1;
+ }
+ String y = x.substring(1);
+ int quotationMarkPosition = 0;
+ while (-1 < (quotationMarkPosition = y.indexOf("\"", quotationMarkPosition + 1))) {
+ int c = 0;
+ StringBuilder s = new StringBuilder("");
+ while ((c <= quotationMarkPosition) && (y.substring(quotationMarkPosition - c, quotationMarkPosition).equals(s.toString()))) {
+ c++;
+ s.append("\\");
+ }
+ c--;
+ if (0 == (c % 2)) {
+ break;
+ }
+ }
+ if (-1 == quotationMarkPosition) {
+ return 0;
+ }
+ return (quotationMarkPosition + 1) + 1;
+ }
+
+ public void parseString(String x) throws MInvalidJsonValueException {
+ if (MText.isBlank(x)) {
+ throw new IllegalArgumentException("Invalid 'x': null or empty.");
+ }
+ //
+ if ((2 > x.length()) || ('"' != x.charAt(0)) || ('"' != x.charAt(x.length() - 1))) {
+ throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json string.", x));
+ }
+ String y = x.substring(1, x.length() - 1);
+ if (-1 < y.replaceAll("\\\\\"", "__").indexOf("\"")) {
+ throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json string.", x));
+ }
+ this.setValue(MJsonString.getUnescapedString(y));
+ }
+
+ /* Formatter. */
+
+ public String getJsonValue() {
+ return "\"" + MJsonString.getEscapedString(this.getValue(), this.getExtendedEscapeMode()) + "\"";
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.json;
+
+import com.marcozanon.macaco.MObject;
+
+public abstract class MJsonValue extends MObject {
+
+ /* */
+
+ public abstract MJsonValue clone();
+
+ /* Helpers. */
+
+ protected static boolean isWhitespace(int c) {
+ return ((' ' == c) || ('\t' == c) || ('\n' == c) || ('\r' == c));
+ }
+
+ protected static boolean isValueSeparator(int c) {
+ return (',' == c);
+ }
+
+ protected static boolean isNameSeparator(int c) {
+ return (':' == c);
+ }
+
+ protected static boolean isOpeningStructuralCharacter(int c) {
+ return (('[' == c) || ('{' == c));
+ }
+
+ protected static boolean isClosingStructuralCharacter(int c) {
+ return ((']' == c) || ('}' == c));
+ }
+
+ /* Formatter. */
+
+ public abstract String getJsonValue();
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.logging;
+
+import com.marcozanon.macaco.database.MDatabaseConnection;
+import com.marcozanon.macaco.database.MDatabaseConnectionFailureException;
+import com.marcozanon.macaco.database.MDatabaseConnectionPool;
+import com.marcozanon.macaco.database.MSqlStatementException;
+import com.marcozanon.macaco.database.MSqlTable;
+import com.marcozanon.macaco.text.MText;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.LinkedHashMap;
+
+public class MLogDatabaseTable extends MLogTarget {
+
+ protected MDatabaseConnectionPool databaseConnectionPool = null;
+
+ protected String logDatabaseTable = null;
+ protected String logDatabaseTablePrimaryKey = null;
+ protected String logDatabaseField = null;
+
+ protected MDatabaseConnection databaseConnection = null;
+
+ /* */
+
+ public MLogDatabaseTable(MDatabaseConnectionPool databaseConnectionPool, String logDatabaseTable, String logDatabaseTablePrimaryKey, String logDatabaseField) throws MLoggingException {
+ super();
+ //
+ if (null == databaseConnectionPool) {
+ throw new IllegalArgumentException("Invalid 'databaseConnectionPool': null.");
+ }
+ if (MText.isEmpty(logDatabaseTable)) {
+ throw new IllegalArgumentException("Invalid 'logDatabaseTable': null or empty.");
+ }
+ if (MText.isEmpty(logDatabaseTablePrimaryKey)) {
+ throw new IllegalArgumentException("Invalid 'logDatabaseTablePrimaryKey': null or empty.");
+ }
+ if (MText.isEmpty(logDatabaseField)) {
+ throw new IllegalArgumentException("Invalid 'logDatabaseField': null or empty.");
+ }
+ //
+ this.databaseConnectionPool = databaseConnectionPool;
+ this.logDatabaseTable = logDatabaseTable;
+ this.logDatabaseTablePrimaryKey = logDatabaseTablePrimaryKey;
+ this.logDatabaseField = logDatabaseField;
+ }
+
+ public void close() throws MLoggingException {
+ }
+
+ /* Database connection pool. */
+
+ protected MDatabaseConnectionPool getDatabaseConnectionPool() {
+ return this.databaseConnectionPool;
+ }
+
+ /* Database logging parameters. */
+
+ protected String getLogDatabaseTable() {
+ return this.logDatabaseTable;
+ }
+
+ protected String getLogDatabaseTablePrimaryKey() {
+ return this.logDatabaseTablePrimaryKey;
+ }
+
+ protected String getLogDatabaseField() {
+ return this.logDatabaseField;
+ }
+
+ /* Output. */
+
+ public void appendMessage(String message) throws MLoggingException {
+ this.appendMessage(message, 0);
+ }
+
+ public void appendMessage(String message, int indentation) throws MLoggingException {
+ if (null == message) {
+ throw new IllegalArgumentException("Invalid 'message': null.");
+ }
+ if (0 > indentation) {
+ throw new IllegalArgumentException(String.format("Invalid 'indentation': %s: cannot be negative.", indentation));
+ }
+ //
+ try {
+ String timestamp = (new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")).format(new Date());
+ //
+ StringBuilder separator = new StringBuilder(" ");
+ for (int i = 0; i < indentation; i++) {
+ separator.append(" ");
+ }
+ //
+ MDatabaseConnection databaseConnection = this.getDatabaseConnectionPool().popDatabaseConnection();
+ //
+ LinkedHashMap<String, Object> p = new LinkedHashMap<String, Object>();
+ p.put(this.getLogDatabaseField(), timestamp + separator + message);
+ (new MSqlTable(databaseConnection, this.getLogDatabaseTable(), this.getLogDatabaseTablePrimaryKey())).setRecord(p, null);
+ //
+ this.getDatabaseConnectionPool().pushDatabaseConnection(databaseConnection);
+ }
+ catch (MDatabaseConnectionFailureException exception) {
+ throw new MLoggingException("Could not write to database table.", exception);
+ }
+ catch (MSqlStatementException exception) {
+ throw new MLoggingException("Could not write to database table.", exception);
+ }
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.logging;
+
+import com.marcozanon.macaco.MObject;
+import java.util.LinkedList;
+
+public class MLogFilter extends MObject {
+
+ public static enum Threshold {
+ STANDARD,
+ DEBUG
+ };
+
+ protected MLogFilter.Threshold threshold = null;
+
+ protected LinkedList<MLogTarget> logTargets = new LinkedList<MLogTarget>();
+
+ protected boolean pausedState = false;
+ protected LinkedList<MLogMessage> logMessageQueue = new LinkedList<MLogMessage>();
+
+ /* */
+
+ public MLogFilter(MLogFilter.Threshold threshold) {
+ super();
+ //
+ this.setThreshold(threshold);
+ }
+
+ public void close() throws MLoggingException {
+ for (MLogTarget t: this.getLogTargets()) {
+ t.close();
+ }
+ }
+
+ protected void finalize() {
+ try {
+ this.close();
+ }
+ catch (Exception exception) {
+ }
+ }
+
+ /* Threshold. */
+
+ public void setThreshold(MLogFilter.Threshold threshold) {
+ if (null == threshold) {
+ throw new IllegalArgumentException("Invalid 'threshold': null.");
+ }
+ //
+ this.threshold = threshold;
+ }
+
+ public MLogFilter.Threshold getThreshold() {
+ return this.threshold;
+ }
+
+ /* Log targets. */
+
+ public void addLogTarget(MLogTarget logTarget) {
+ if (null == logTarget) {
+ throw new IllegalArgumentException("Invalid 'logTarget': null.");
+ }
+ //
+ synchronized (this.logTargets) {
+ this.logTargets.add(logTarget);
+ }
+ }
+
+ protected LinkedList<MLogTarget> getLogTargets() {
+ return this.logTargets;
+ }
+
+ /* Output. */
+
+ public void setPausedState(boolean pausedState) throws MLoggingException {
+ this.pausedState = pausedState;
+ //
+ if (!this.getPausedState()) {
+ this.flushMessages();
+ }
+ }
+
+ public boolean getPausedState() {
+ return this.pausedState;
+ }
+
+ public void appendMessage(MLogFilter.Threshold level, String message) throws MLoggingException {
+ this.appendMessage(level, message, 0);
+ }
+
+ public void appendMessage(MLogFilter.Threshold level, String message, int indentation) throws MLoggingException {
+ if (null == level) {
+ throw new IllegalArgumentException("Invalid 'level': null.");
+ }
+ //
+ if (level.ordinal() > this.getThreshold().ordinal()) {
+ return;
+ }
+ //
+ this.logMessageQueue.add(new MLogMessage(message, indentation));
+ //
+ if (!this.getPausedState()) {
+ this.flushMessages();
+ }
+ }
+
+ protected void flushMessages() throws MLoggingException {
+ while (0 < this.logMessageQueue.size()) {
+ MLogMessage logMessage = this.logMessageQueue.remove();
+ String message = logMessage.getMessage();
+ int indentation = logMessage.getIndentation();
+ //
+ for (MLogTarget logTarget: this.getLogTargets()) {
+ logTarget.appendMessage(message, indentation);
+ }
+ }
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.logging;
+
+public interface MLogListener {
+
+ /* Logging. */
+
+ public void onMessageLogging(String message);
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.logging;
+
+import com.marcozanon.macaco.MObject;
+
+public class MLogMessage extends MObject {
+
+ protected String message = null;
+ protected int indentation = 0;
+
+ /* */
+
+ public MLogMessage(String message, int indentation) {
+ if (null == message) {
+ throw new IllegalArgumentException("Invalid 'message': null.");
+ }
+ if (0 > indentation) {
+ throw new IllegalArgumentException(String.format("Invalid 'indentation': %s: cannot be negative.", indentation));
+ }
+ //
+ this.message = message;
+ this.indentation = indentation;
+ }
+
+ /* Values. */
+
+ public String getMessage() {
+ return this.message;
+ }
+
+ public int getIndentation() {
+ return this.indentation;
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.logging;
+
+import com.marcozanon.macaco.MInformation;
+import com.marcozanon.macaco.text.MText;
+import java.io.BufferedWriter;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class MLogPlainTextFile extends MLogTarget {
+
+ protected String file = null;
+
+ protected BufferedWriter buffer = null;
+
+ /* */
+
+ public MLogPlainTextFile(String file) throws MLoggingException {
+ super();
+ //
+ if (MText.isBlank(file)) {
+ throw new IllegalArgumentException("Invalid 'file': null or empty.");
+ }
+ //
+ this.file = file;
+ try {
+ this.buffer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(this.getFile(), true), MInformation.TEXT_ENCODING));
+ }
+ catch (FileNotFoundException exception) {
+ throw new MLoggingException("Could not open file.", exception);
+ }
+ catch (UnsupportedEncodingException exception) { // cannot happen
+ }
+ }
+
+ public void close() throws MLoggingException {
+ try {
+ this.getBuffer().close();
+ }
+ catch (IOException exception) {
+ throw new MLoggingException("Could not close file.", exception);
+ }
+ }
+
+ /* File. */
+
+ protected String getFile() {
+ return this.file;
+ }
+
+ /* Buffer. */
+
+ protected BufferedWriter getBuffer() {
+ return this.buffer;
+ }
+
+ /* Output. */
+
+ public void appendMessage(String message) throws MLoggingException {
+ this.appendMessage(message, 0);
+ }
+
+ public void appendMessage(String message, int indentation) throws MLoggingException {
+ if (null == message) {
+ throw new IllegalArgumentException("Invalid 'message': null.");
+ }
+ if (0 > indentation) {
+ throw new IllegalArgumentException(String.format("Invalid 'indentation': %s: cannot be negative.", indentation));
+ }
+ //
+ try {
+ String timestamp = (new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")).format(new Date());
+ //
+ StringBuilder separator = new StringBuilder(" ");
+ for (int i = 0; i < indentation; i++) {
+ separator.append(" ");
+ }
+ //
+ synchronized (this.getBuffer()) {
+ this.getBuffer().write(timestamp + separator + message);
+ this.getBuffer().newLine();
+ this.getBuffer().flush();
+ }
+ }
+ catch (IOException exception) {
+ throw new MLoggingException("Could not write to file.", exception);
+ }
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.logging;
+
+import com.marcozanon.macaco.MObject;
+
+public abstract class MLogTarget extends MObject {
+
+ /* */
+
+ public abstract void close() throws MLoggingException;
+
+ protected void finalize() {
+ try {
+ this.close();
+ }
+ catch (Exception exception) {
+ }
+ }
+
+ /* Output. */
+
+ public abstract void appendMessage(String message) throws MLoggingException;
+
+ public abstract void appendMessage(String message, int indentation) throws MLoggingException;
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.logging;
+
+import com.marcozanon.macaco.MException;
+
+@SuppressWarnings("serial")
+public class MLoggingException extends MException {
+
+ /* */
+
+ public MLoggingException() {
+ super();
+ }
+
+ public MLoggingException(String message) {
+ super(message);
+ }
+
+ public MLoggingException(Throwable error) {
+ super(error);
+ }
+
+ public MLoggingException(String message, Throwable error) {
+ super(message, error);
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.text;
+
+import com.marcozanon.macaco.MInformation;
+import com.marcozanon.macaco.MObject;
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import org.xml.sax.Attributes;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+
+public class MText extends MObject {
+
+ /* Checks. */
+
+ public static boolean isEmpty(String x) {
+ if ((null != x) && ("".equals(x))) {
+ return true;
+ }
+ return false;
+ }
+
+ public static boolean isBlank(String x) {
+ if ((null == x) || ("".equals(x))) {
+ return true;
+ }
+ return false;
+ }
+
+ /* Utility methods. */
+
+ public static String repeat(String x, int times) {
+ if (1 > times) {
+ throw new IllegalArgumentException(String.format("Invalid 'times': %s.", times));
+ }
+ //
+ StringBuilder y = new StringBuilder("");
+ for (int i = 0; i < times; i++) {
+ y = y.append(x);
+ }
+ return y.toString();
+ }
+
+ public static String getRandomToken(int length) {
+ if (0 > length) {
+ throw new IllegalArgumentException(String.format("Invalid 'length': %s.", length));
+ }
+ //
+ StringBuilder y = new StringBuilder("");
+ for (int i = 0; i < length; i++) {
+ char c = (char)(65 + (25 * Math.random()));
+ y = y.append(c);
+ }
+ return y.toString();
+ }
+
+ /* Javascript escape. */
+
+ public static String getJavascriptEscapedString(String x) {
+ if (null == x) {
+ return null;
+ }
+ //
+ String text = x;
+ //
+ text = text.replace("\\", "\\\\");
+ text = text.replace("'", "\\\'");
+ //
+ return text;
+ }
+
+ /* Xhtml escape and safety. */
+
+ public static String getXhtmlEscapedString(String x) { // similar to PHP's htmlspecialchars()
+ if (null == x) {
+ return null;
+ }
+ //
+ String text = x;
+ //
+ text = text.replace("&", "&");
+ text = text.replace("\"", """);
+ text = text.replace("'", "'");
+ text = text.replace("<", "<");
+ text = text.replace(">", ">");
+ //
+ text = text.replace("\n", "<br />");
+ //
+ return text;
+ }
+
+ public static String getXhtmlNumericEntitiesString(String x) { // for compatibility with innerHTML
+ if (null == x) {
+ return null;
+ }
+ //
+ String text = x;
+ //
+ text = text.replace(" ", " ");
+ text = text.replace("¡", "¡");
+ text = text.replace("¢", "¢");
+ text = text.replace("£", "£");
+ text = text.replace("¤", "¤");
+ text = text.replace("¥", "¥");
+ text = text.replace("¦", "¦");
+ text = text.replace("§", "§");
+ text = text.replace("¨", "¨");
+ text = text.replace("©", "©");
+ text = text.replace("ª", "ª");
+ text = text.replace("«", "«");
+ text = text.replace("¬", "¬");
+ text = text.replace("­", "­");
+ text = text.replace("®", "®");
+ text = text.replace("¯", "¯");
+ text = text.replace("°", "°");
+ text = text.replace("±", "±");
+ text = text.replace("²", "²");
+ text = text.replace("³", "³");
+ text = text.replace("´", "´");
+ text = text.replace("µ", "µ");
+ text = text.replace("¶", "¶");
+ text = text.replace("·", "·");
+ text = text.replace("¸", "¸");
+ text = text.replace("¹", "¹");
+ text = text.replace("º", "º");
+ text = text.replace("»", "»");
+ text = text.replace("¼", "¼");
+ text = text.replace("½", "½");
+ text = text.replace("¾", "¾");
+ text = text.replace("¿", "¿");
+ text = text.replace("À", "À");
+ text = text.replace("Á", "Á");
+ text = text.replace("Â", "Â");
+ text = text.replace("Ã", "Ã");
+ text = text.replace("Ä", "Ä");
+ text = text.replace("Å", "Å");
+ text = text.replace("Æ", "Æ");
+ text = text.replace("Ç", "Ç");
+ text = text.replace("È", "È");
+ text = text.replace("É", "É");
+ text = text.replace("Ê", "Ê");
+ text = text.replace("Ë", "Ë");
+ text = text.replace("Ì", "Ì");
+ text = text.replace("Í", "Í");
+ text = text.replace("Î", "Î");
+ text = text.replace("Ï", "Ï");
+ text = text.replace("Ð", "Ð");
+ text = text.replace("Ñ", "Ñ");
+ text = text.replace("Ò", "Ò");
+ text = text.replace("Ó", "Ó");
+ text = text.replace("Ô", "Ô");
+ text = text.replace("Õ", "Õ");
+ text = text.replace("Ö", "Ö");
+ text = text.replace("×", "×");
+ text = text.replace("Ø", "Ø");
+ text = text.replace("Ù", "Ù");
+ text = text.replace("Ú", "Ú");
+ text = text.replace("Û", "Û");
+ text = text.replace("Ü", "Ü");
+ text = text.replace("Ý", "Ý");
+ text = text.replace("Þ", "Þ");
+ text = text.replace("ß", "ß");
+ text = text.replace("à", "à");
+ text = text.replace("á", "á");
+ text = text.replace("â", "â");
+ text = text.replace("ã", "ã");
+ text = text.replace("ä", "ä");
+ text = text.replace("å", "å");
+ text = text.replace("æ", "æ");
+ text = text.replace("ç", "ç");
+ text = text.replace("è", "è");
+ text = text.replace("é", "é");
+ text = text.replace("ê", "ê");
+ text = text.replace("ë", "ë");
+ text = text.replace("ì", "ì");
+ text = text.replace("í", "í");
+ text = text.replace("î", "î");
+ text = text.replace("ï", "ï");
+ text = text.replace("ð", "ð");
+ text = text.replace("ñ", "ñ");
+ text = text.replace("ò", "ò");
+ text = text.replace("ó", "ó");
+ text = text.replace("ô", "ô");
+ text = text.replace("õ", "õ");
+ text = text.replace("ö", "ö");
+ text = text.replace("÷", "÷");
+ text = text.replace("ø", "ø");
+ text = text.replace("ù", "ù");
+ text = text.replace("ú", "ú");
+ text = text.replace("û", "û");
+ text = text.replace("ü", "ü");
+ text = text.replace("ý", "ý");
+ text = text.replace("þ", "þ");
+ text = text.replace("ÿ", "ÿ");
+ text = text.replace("Œ", "Œ");
+ text = text.replace("œ", "œ");
+ text = text.replace("Š", "Š");
+ text = text.replace("š", "š");
+ text = text.replace("Ÿ", "Ÿ");
+ text = text.replace("ƒ", "ƒ");
+ text = text.replace("ˆ", "ˆ");
+ text = text.replace("˜", "˜");
+ text = text.replace("Α", "Α");
+ text = text.replace("Β", "Β");
+ text = text.replace("Γ", "Γ");
+ text = text.replace("Δ", "Δ");
+ text = text.replace("Ε", "Ε");
+ text = text.replace("Ζ", "Ζ");
+ text = text.replace("Η", "Η");
+ text = text.replace("Θ", "Θ");
+ text = text.replace("Ι", "Ι");
+ text = text.replace("Κ", "Κ");
+ text = text.replace("Λ", "Λ");
+ text = text.replace("Μ", "Μ");
+ text = text.replace("Ν", "Ν");
+ text = text.replace("Ξ", "Ξ");
+ text = text.replace("Ο", "Ο");
+ text = text.replace("Π", "Π");
+ text = text.replace("Ρ", "Ρ");
+ text = text.replace("Σ", "Σ");
+ text = text.replace("Τ", "Τ");
+ text = text.replace("Υ", "Υ");
+ text = text.replace("Φ", "Φ");
+ text = text.replace("Χ", "Χ");
+ text = text.replace("Ψ", "Ψ");
+ text = text.replace("Ω", "Ω");
+ text = text.replace("α", "α");
+ text = text.replace("β", "β");
+ text = text.replace("γ", "γ");
+ text = text.replace("δ", "δ");
+ text = text.replace("ε", "ε");
+ text = text.replace("ζ", "ζ");
+ text = text.replace("η", "η");
+ text = text.replace("θ", "θ");
+ text = text.replace("ι", "ι");
+ text = text.replace("κ", "κ");
+ text = text.replace("λ", "λ");
+ text = text.replace("μ", "μ");
+ text = text.replace("ν", "ν");
+ text = text.replace("ξ", "ξ");
+ text = text.replace("ο", "ο");
+ text = text.replace("π", "π");
+ text = text.replace("ρ", "ρ");
+ text = text.replace("ς", "ς");
+ text = text.replace("σ", "σ");
+ text = text.replace("τ", "τ");
+ text = text.replace("υ", "υ");
+ text = text.replace("φ", "φ");
+ text = text.replace("χ", "χ");
+ text = text.replace("ψ", "ψ");
+ text = text.replace("ω", "ω");
+ text = text.replace("ϑ", "ϑ");
+ text = text.replace("ϒ", "ϒ");
+ text = text.replace("ϖ", "ϖ");
+ text = text.replace(" ", " ");
+ text = text.replace(" ", " ");
+ text = text.replace(" ", " ");
+ text = text.replace("‌", "‌");
+ text = text.replace("‍", "‍");
+ text = text.replace("‎", "‎");
+ text = text.replace("‏", "‏");
+ text = text.replace("–", "–");
+ text = text.replace("—", "—");
+ text = text.replace("‘", "‘");
+ text = text.replace("’", "’");
+ text = text.replace("‚", "‚");
+ text = text.replace("“", "“");
+ text = text.replace("”", "”");
+ text = text.replace("„", "„");
+ text = text.replace("†", "†");
+ text = text.replace("‡", "‡");
+ text = text.replace("•", "•");
+ text = text.replace("…", "…");
+ text = text.replace("‰", "‰");
+ text = text.replace("′", "′");
+ text = text.replace("″", "″");
+ text = text.replace("‹", "‹");
+ text = text.replace("›", "›");
+ text = text.replace("‾", "‾");
+ text = text.replace("⁄", "⁄");
+ text = text.replace("€", "€");
+ text = text.replace("ℑ", "ℑ");
+ text = text.replace("℘", "℘");
+ text = text.replace("ℜ", "ℜ");
+ text = text.replace("™", "™");
+ text = text.replace("ℵ", "ℵ");
+ text = text.replace("←", "←");
+ text = text.replace("↑", "↑");
+ text = text.replace("→", "→");
+ text = text.replace("↓", "↓");
+ text = text.replace("↔", "↔");
+ text = text.replace("↵", "↵");
+ text = text.replace("⇐", "⇐");
+ text = text.replace("⇑", "⇑");
+ text = text.replace("⇒", "⇒");
+ text = text.replace("⇓", "⇓");
+ text = text.replace("⇔", "⇔");
+ text = text.replace("∀", "∀");
+ text = text.replace("∂", "∂");
+ text = text.replace("∃", "∃");
+ text = text.replace("∅", "∅");
+ text = text.replace("∇", "∇");
+ text = text.replace("∈", "∈");
+ text = text.replace("∉", "∉");
+ text = text.replace("∋", "∋");
+ text = text.replace("∏", "∏");
+ text = text.replace("∑", "∑");
+ text = text.replace("−", "−");
+ text = text.replace("∗", "∗");
+ text = text.replace("√", "√");
+ text = text.replace("∝", "∝");
+ text = text.replace("∞", "∞");
+ text = text.replace("∠", "∠");
+ text = text.replace("∧", "∧");
+ text = text.replace("∨", "∨");
+ text = text.replace("∩", "∩");
+ text = text.replace("∪", "∪");
+ text = text.replace("∫", "∫");
+ text = text.replace("∴", "∴");
+ text = text.replace("∼", "∼");
+ text = text.replace("≅", "≅");
+ text = text.replace("≈", "≈");
+ text = text.replace("≠", "≠");
+ text = text.replace("≡", "≡");
+ text = text.replace("≤", "≤");
+ text = text.replace("≥", "≥");
+ text = text.replace("⊂", "⊂");
+ text = text.replace("⊃", "⊃");
+ text = text.replace("⊄", "⊄");
+ text = text.replace("⊆", "⊆");
+ text = text.replace("⊇", "⊇");
+ text = text.replace("⊕", "⊕");
+ text = text.replace("⊗", "⊗");
+ text = text.replace("⊥", "⊥");
+ text = text.replace("⋅", "⋅");
+ text = text.replace("⌈", "⌈");
+ text = text.replace("⌉", "⌉");
+ text = text.replace("⌊", "⌊");
+ text = text.replace("⌋", "⌋");
+ text = text.replace("⟨", "〈");
+ text = text.replace("⟩", "〉");
+ text = text.replace("◊", "◊");
+ text = text.replace("♠", "♠");
+ text = text.replace("♣", "♣");
+ text = text.replace("♥", "♥");
+ text = text.replace("♦", "♦");
+ //
+ return text;
+ }
+
+ public static String getXhtmlSafeString(String x) throws MXhtmlUnsafeStringException {
+ if (null == x) {
+ return null;
+ }
+ //
+ String text = x;
+ //
+ StringBuilder fakeXhtmlPageContent = new StringBuilder("");
+ fakeXhtmlPageContent.append(String.format("<?xml version=\"1.0\" encoding=\"%s\" ?>", MInformation.TEXT_ENCODING));
+ fakeXhtmlPageContent.append("<!DOCTYPE html SYSTEM \"fake-dtd\" >");
+ fakeXhtmlPageContent.append("<html xmlns=\"http://www.w3.org/1999/xhtml\">");
+ fakeXhtmlPageContent.append("<head><title /></head>");
+ fakeXhtmlPageContent.append(String.format("<body>%s</body>", text));
+ fakeXhtmlPageContent.append("</html>");
+ // Validate Xhtml (with no dangerous tags and event attributes).
+ try {
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+ factory.setValidating(true);
+ factory.setNamespaceAware(true);
+ SAXParser parser = factory.newSAXParser();
+ XMLReader reader = parser.getXMLReader();
+ reader.setEntityResolver(new EntityResolver() {
+
+ public InputSource resolveEntity(String publicId, String systemId) {
+ return new InputSource(new BufferedInputStream(this.getClass().getClassLoader().getResourceAsStream("dtd/xhtml1-transitional-macaco-edit.dtd")));
+ }
+
+ });
+ reader.setContentHandler(new DefaultHandler() {
+
+ public void startElement(String namespaceUri, String strippedName, String tagName, Attributes attributes) throws SAXException {
+ if ("script".equalsIgnoreCase(tagName)) {
+ throw new SAXException(String.format("Tag not allowed: %s.", tagName));
+ }
+ for (int a = 0; attributes.getLength() > a; a++) {
+ if (attributes.getLocalName(a).toLowerCase().startsWith("on")) {
+ throw new SAXException(String.format("Attribute not allowed: %s.", attributes.getLocalName(a)));
+ }
+ }
+ }
+
+ });
+ reader.setErrorHandler(new ErrorHandler() {
+
+ public void error(SAXParseException exception) throws SAXException {
+ throw new SAXException(exception);
+ }
+
+ public void fatalError(SAXParseException exception) throws SAXException {
+ throw new SAXException(exception);
+ }
+
+ public void warning(SAXParseException exception) throws SAXException {
+ throw new SAXException(exception);
+ }
+
+ });
+ //
+ reader.parse(new InputSource(new ByteArrayInputStream(fakeXhtmlPageContent.toString().getBytes(MInformation.TEXT_ENCODING))));
+ }
+ catch (ParserConfigurationException exception) { // cannot happen
+ }
+ catch (SAXException exception) {
+ throw new MXhtmlUnsafeStringException("Invalid 'x': unsafe tags or attributes inside.", exception);
+ }
+ catch (UnsupportedEncodingException exception) { // cannot happen
+ }
+ catch (IOException exception) { // cannot happen; put here not to bypass UnsupportedEncodingException
+ }
+ // Also convert named entities to numeric entities.
+ return MText.getXhtmlNumericEntitiesString(text);
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.text;
+
+import com.marcozanon.macaco.MException;
+
+public abstract class MTextException extends MException {
+
+ /* */
+
+ public MTextException() {
+ super();
+ }
+
+ public MTextException(String message) {
+ super(message);
+ }
+
+ public MTextException(Throwable error) {
+ super(error);
+ }
+
+ public MTextException(String message, Throwable error) {
+ super(message, error);
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.text;
+
+public abstract class MTranslationException extends MTextException {
+
+ /* */
+
+ public MTranslationException() {
+ super();
+ }
+
+ public MTranslationException(String message) {
+ super(message);
+ }
+
+ public MTranslationException(Throwable error) {
+ super(error);
+ }
+
+ public MTranslationException(String message, Throwable error) {
+ super(message, error);
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.text;
+
+@SuppressWarnings("serial")
+public class MTranslationFileParsingException extends MTranslationException {
+
+ /* */
+
+ public MTranslationFileParsingException() {
+ super();
+ }
+
+ public MTranslationFileParsingException(String message) {
+ super(message);
+ }
+
+ public MTranslationFileParsingException(Throwable error) {
+ super(error);
+ }
+
+ public MTranslationFileParsingException(String message, Throwable error) {
+ super(message, error);
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.text;
+
+@SuppressWarnings("serial")
+public class MTranslationValueNotFoundException extends MTranslationException {
+
+ /* */
+
+ public MTranslationValueNotFoundException() {
+ super();
+ }
+
+ public MTranslationValueNotFoundException(String message) {
+ super(message);
+ }
+
+ public MTranslationValueNotFoundException(Throwable error) {
+ super(error);
+ }
+
+ public MTranslationValueNotFoundException(String message, Throwable error) {
+ super(message, error);
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.text;
+
+import com.marcozanon.macaco.MInformation;
+import com.marcozanon.macaco.MObject;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.io.LineNumberReader;
+import java.io.UnsupportedEncodingException;
+import java.util.LinkedHashMap;
+import java.util.Locale;
+
+public class MTranslator extends MObject {
+
+ protected String file = null;
+ protected Locale basicLocale = null;
+
+ protected LinkedHashMap<String, LinkedHashMap<String, String>> messages = new LinkedHashMap<String, LinkedHashMap<String, String>>();
+
+ /* */
+
+ public MTranslator(String file, Locale basicLocale) throws MTranslationFileParsingException {
+ super();
+ //
+ if (null == basicLocale) {
+ throw new IllegalArgumentException("Invalid 'basicLocale': null.");
+ }
+ //
+ this.file = file;
+ this.basicLocale = basicLocale;
+ //
+ this.parseFile(this.getFile());
+ }
+
+ public MTranslator clone() {
+ MTranslator tmpMTranslator = null;
+ try {
+ tmpMTranslator = new MTranslator(this.getFile(), this.getBasicLocale());
+ }
+ catch (MTranslationFileParsingException exception) { // should not happen
+ }
+ return tmpMTranslator;
+ }
+
+ /* File. */
+
+ protected String getFile() {
+ return this.file;
+ }
+
+ /* Locale. */
+
+ protected Locale getBasicLocale() {
+ return this.basicLocale;
+ }
+
+ /* String management. */
+
+ protected LinkedHashMap<String, LinkedHashMap<String, String>> getMessages() {
+ return this.messages;
+ }
+
+ public void parseFile(String file) throws MTranslationFileParsingException {
+ if (MText.isBlank(file)) {
+ throw new IllegalArgumentException("Invalid 'file': null or empty.");
+ }
+ //
+ LineNumberReader buffer = null;
+ try {
+ buffer = new LineNumberReader(new InputStreamReader(new FileInputStream(file), MInformation.TEXT_ENCODING));
+ }
+ catch (FileNotFoundException exception) {
+ throw new MTranslationFileParsingException("Could not open file.", exception);
+ }
+ catch (UnsupportedEncodingException exception) { // cannot happen
+ }
+ String message = null;
+ String line = null;
+ synchronized (this.getMessages()) {
+ while (true) {
+ try {
+ line = buffer.readLine();
+ }
+ catch (IOException exception) {
+ throw new MTranslationFileParsingException("Could not read file.", exception);
+ }
+ if (null == line) {
+ break;
+ }
+ line = line.trim();
+ if (MText.isEmpty(line)) {
+ message = null;
+ continue;
+ }
+ else if ((line.startsWith("#")) || (line.startsWith(";"))) {
+ continue;
+ }
+ else if (null == message) {
+ message = line;
+ if (this.getMessages().containsKey(message)) {
+ throw new MTranslationFileParsingException(String.format("Invalid line: %s: duplicated message: %s.", buffer.getLineNumber(), message));
+ }
+ this.getMessages().put(message, new LinkedHashMap<String, String>());
+ }
+ else {
+ int a = line.indexOf("=");
+ if (-1 == a) {
+ throw new MTranslationFileParsingException(String.format("Invalid line: %s: string malformed: %s.", buffer.getLineNumber(), line));
+ }
+ String localeRepresentation = line.substring(0, a).trim();
+ String translation = line.substring(a + 1).trim();
+ if (this.getMessages().get(message).containsKey(localeRepresentation)) {
+ throw new MTranslationFileParsingException(String.format("Invalid line: %s: duplicated translation for locale: %s.", buffer.getLineNumber(), message));
+ }
+ this.getMessages().get(message).put(localeRepresentation, translation);
+ }
+ }
+ }
+ try {
+ buffer.close();
+ }
+ catch (IOException exception) {
+ throw new MTranslationFileParsingException("Could not close file.", exception);
+ }
+ }
+
+ public void clear() {
+ synchronized (this.getMessages()) {
+ this.getMessages().clear();
+ }
+ }
+
+ public String t(String message, Locale locale) {
+ return this.getLenientTranslation(message, locale);
+ }
+
+ public String getLenientTranslation(String message, Locale locale) {
+ String translation = null;
+ try {
+ translation = this.getTranslation(message, locale, false);
+ }
+ catch (MTranslationValueNotFoundException exception) { // cannot happen
+ }
+ return translation;
+ }
+
+ public String getStrictTranslation(String message, Locale locale) throws MTranslationValueNotFoundException {
+ return this.getTranslation(message, locale, true);
+ }
+
+ protected String getTranslation(String message, Locale locale, boolean strictMode) throws MTranslationValueNotFoundException {
+ if (MText.isBlank(message)) {
+ throw new IllegalArgumentException("Invalid 'message': null or empty.");
+ }
+ if (null == locale) {
+ throw new IllegalArgumentException("Invalid 'locale': null.");
+ }
+ //
+ if (!this.getMessages().containsKey(message)) {
+ if (strictMode) {
+ throw new MTranslationValueNotFoundException(String.format("Invalid 'message': %s: not available.", message));
+ }
+ return message;
+ }
+ if (this.getBasicLocale().equals(locale)) {
+ return message;
+ }
+ LinkedHashMap<String, String> messageTranslations = this.getMessages().get(message);
+ String localeRepresentation = locale.toString();
+ if (!messageTranslations.containsKey(localeRepresentation)) {
+ if (strictMode) {
+ throw new MTranslationValueNotFoundException(String.format("Invalid 'locale': %s: translation not available for message: %s.", localeRepresentation, message));
+ }
+ String localeFallbackRepresentation = locale.getLanguage();
+ if (!messageTranslations.containsKey(localeFallbackRepresentation)) {
+ return message;
+ }
+ return messageTranslations.get(localeFallbackRepresentation);
+ }
+ return messageTranslations.get(localeRepresentation);
+ }
+
+}
--- /dev/null
+/**
+ * Macaco
+ * Copyright (c) 2009-2017 Marco Zanon <info@marcozanon.com>.
+ * Released under MIT license (see LICENSE for details).
+ */
+
+package com.marcozanon.macaco.text;
+
+@SuppressWarnings("serial")
+public class MXhtmlUnsafeStringException extends MTextException {
+
+ /* */
+
+ public MXhtmlUnsafeStringException() {
+ super();
+ }
+
+ public MXhtmlUnsafeStringException(String message) {
+ super(message);
+ }
+
+ public MXhtmlUnsafeStringException(Throwable error) {
+ super(error);
+ }
+
+ public MXhtmlUnsafeStringException(String message, Throwable error) {
+ super(message, error);
+ }
+
+}