From 4b7341869cca3350300cbb4046e62106200c172b Mon Sep 17 00:00:00 2001 From: Marco Zanon Date: Thu, 22 Oct 2015 12:56:37 +0000 Subject: [PATCH] Committed trunk 4.x. Also fixed a copyright notice. --- 4.x/LICENSE | 25 + 4.x/build.properties | 0 4.x/build.xml | 34 ++ .../com/marcozanon/macaco/MException.java | 29 ++ .../com/marcozanon/macaco/MInformation.java | 54 +++ .../java/com/marcozanon/macaco/MObject.java | 21 + .../conversion/MConversionException.java | 31 ++ .../macaco/conversion/MDateConverter.java | 249 ++++++++++ .../MFormatConversionException.java | 30 ++ .../macaco/conversion/MNumberConverter.java | 170 +++++++ .../json/MInvalidValueJsonException.java | 30 ++ .../marcozanon/macaco/json/MJsonArray.java | 208 +++++++++ .../marcozanon/macaco/json/MJsonBoolean.java | 84 ++++ .../macaco/json/MJsonException.java | 31 ++ .../com/marcozanon/macaco/json/MJsonNull.java | 64 +++ .../marcozanon/macaco/json/MJsonNumber.java | 104 +++++ .../marcozanon/macaco/json/MJsonObject.java | 262 +++++++++++ .../marcozanon/macaco/json/MJsonString.java | 292 ++++++++++++ .../marcozanon/macaco/json/MJsonValue.java | 43 ++ .../macaco/logging/MLogDatabaseTable.java | 126 +++++ .../marcozanon/macaco/logging/MLogFilter.java | 125 +++++ .../macaco/logging/MLogMessage.java | 40 ++ .../macaco/logging/MLogPlainTextFile.java | 100 ++++ .../marcozanon/macaco/logging/MLogTarget.java | 31 ++ .../macaco/logging/MLoggingException.java | 32 ++ .../macaco/sql/MConnectionSqlException.java | 30 ++ .../marcozanon/macaco/sql/MSqlConnection.java | 237 ++++++++++ .../macaco/sql/MSqlConnectionGenerator.java | 73 +++ .../marcozanon/macaco/sql/MSqlException.java | 31 ++ .../macaco/sql/MSqlStatementResults.java | 149 ++++++ .../com/marcozanon/macaco/sql/MSqlTable.java | 154 ++++++ .../macaco/sql/MStatementSqlException.java | 30 ++ .../macaco/sql/MTransactionSqlException.java | 30 ++ .../com/marcozanon/macaco/text/MText.java | 439 ++++++++++++++++++ .../macaco/text/MTextException.java | 31 ++ .../macaco/text/MTranslationException.java | 29 ++ .../MTranslationFileParsingTextException.java | 30 ++ ...TranslationValueNotFoundTextException.java | 30 ++ .../marcozanon/macaco/text/MTranslator.java | 190 ++++++++ .../text/MXhtmlUnsafeStringTextException.java | 30 ++ 40 files changed, 3728 insertions(+) create mode 100644 4.x/LICENSE create mode 100644 4.x/build.properties create mode 100644 4.x/build.xml create mode 100644 4.x/src/java/com/marcozanon/macaco/MException.java create mode 100644 4.x/src/java/com/marcozanon/macaco/MInformation.java create mode 100644 4.x/src/java/com/marcozanon/macaco/MObject.java create mode 100644 4.x/src/java/com/marcozanon/macaco/conversion/MConversionException.java create mode 100644 4.x/src/java/com/marcozanon/macaco/conversion/MDateConverter.java create mode 100644 4.x/src/java/com/marcozanon/macaco/conversion/MFormatConversionException.java create mode 100644 4.x/src/java/com/marcozanon/macaco/conversion/MNumberConverter.java create mode 100644 4.x/src/java/com/marcozanon/macaco/json/MInvalidValueJsonException.java create mode 100644 4.x/src/java/com/marcozanon/macaco/json/MJsonArray.java create mode 100644 4.x/src/java/com/marcozanon/macaco/json/MJsonBoolean.java create mode 100644 4.x/src/java/com/marcozanon/macaco/json/MJsonException.java create mode 100644 4.x/src/java/com/marcozanon/macaco/json/MJsonNull.java create mode 100644 4.x/src/java/com/marcozanon/macaco/json/MJsonNumber.java create mode 100644 4.x/src/java/com/marcozanon/macaco/json/MJsonObject.java create mode 100644 4.x/src/java/com/marcozanon/macaco/json/MJsonString.java create mode 100644 4.x/src/java/com/marcozanon/macaco/json/MJsonValue.java create mode 100644 4.x/src/java/com/marcozanon/macaco/logging/MLogDatabaseTable.java create mode 100644 4.x/src/java/com/marcozanon/macaco/logging/MLogFilter.java create mode 100644 4.x/src/java/com/marcozanon/macaco/logging/MLogMessage.java create mode 100644 4.x/src/java/com/marcozanon/macaco/logging/MLogPlainTextFile.java create mode 100644 4.x/src/java/com/marcozanon/macaco/logging/MLogTarget.java create mode 100644 4.x/src/java/com/marcozanon/macaco/logging/MLoggingException.java create mode 100644 4.x/src/java/com/marcozanon/macaco/sql/MConnectionSqlException.java create mode 100644 4.x/src/java/com/marcozanon/macaco/sql/MSqlConnection.java create mode 100644 4.x/src/java/com/marcozanon/macaco/sql/MSqlConnectionGenerator.java create mode 100644 4.x/src/java/com/marcozanon/macaco/sql/MSqlException.java create mode 100644 4.x/src/java/com/marcozanon/macaco/sql/MSqlStatementResults.java create mode 100644 4.x/src/java/com/marcozanon/macaco/sql/MSqlTable.java create mode 100644 4.x/src/java/com/marcozanon/macaco/sql/MStatementSqlException.java create mode 100644 4.x/src/java/com/marcozanon/macaco/sql/MTransactionSqlException.java create mode 100644 4.x/src/java/com/marcozanon/macaco/text/MText.java create mode 100644 4.x/src/java/com/marcozanon/macaco/text/MTextException.java create mode 100644 4.x/src/java/com/marcozanon/macaco/text/MTranslationException.java create mode 100644 4.x/src/java/com/marcozanon/macaco/text/MTranslationFileParsingTextException.java create mode 100644 4.x/src/java/com/marcozanon/macaco/text/MTranslationValueNotFoundTextException.java create mode 100644 4.x/src/java/com/marcozanon/macaco/text/MTranslator.java create mode 100644 4.x/src/java/com/marcozanon/macaco/text/MXhtmlUnsafeStringTextException.java diff --git a/4.x/LICENSE b/4.x/LICENSE new file mode 100644 index 0000000..4485173 --- /dev/null +++ b/4.x/LICENSE @@ -0,0 +1,25 @@ +Macaco +Copyright (c) 2009-2015 Marco Zanon . + +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. diff --git a/4.x/build.properties b/4.x/build.properties new file mode 100644 index 0000000..e69de29 diff --git a/4.x/build.xml b/4.x/build.xml new file mode 100644 index 0000000..74777ba --- /dev/null +++ b/4.x/build.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/4.x/src/java/com/marcozanon/macaco/MException.java b/4.x/src/java/com/marcozanon/macaco/MException.java new file mode 100644 index 0000000..e85f295 --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/MException.java @@ -0,0 +1,29 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon . + * 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); + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/MInformation.java b/4.x/src/java/com/marcozanon/macaco/MInformation.java new file mode 100644 index 0000000..a7d0a67 --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/MInformation.java @@ -0,0 +1,54 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon . + * 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 = "4.x"; + + public static final String TEXT_ENCODING = "UTF-8"; + + /* Macaco 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-2015 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(); + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/MObject.java b/4.x/src/java/com/marcozanon/macaco/MObject.java new file mode 100644 index 0000000..465a774 --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/MObject.java @@ -0,0 +1,21 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon . + * 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)."); + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/conversion/MConversionException.java b/4.x/src/java/com/marcozanon/macaco/conversion/MConversionException.java new file mode 100644 index 0000000..43cc827 --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/conversion/MConversionException.java @@ -0,0 +1,31 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon . + * 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); + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/conversion/MDateConverter.java b/4.x/src/java/com/marcozanon/macaco/conversion/MDateConverter.java new file mode 100644 index 0000000..d482d5f --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/conversion/MDateConverter.java @@ -0,0 +1,249 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon . + * 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 dateFormats = new LinkedHashSet(); + 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 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 dateFormats) { + if (null == dateFormats) { + throw new IllegalArgumentException("Invalid 'dateFormats': null."); + } + else { + Iterator i = dateFormats.iterator(); + while (i.hasNext()) { + MDateConverter.checkDateFormat((String)i.next()); + } + } + // + synchronized (this.dateFormats) { + this.dateFormats = dateFormats; + } + } + + public void addDateFormat(String dateFormat) { + MDateConverter.checkDateFormat(dateFormat); + // + synchronized (this.dateFormats) { + this.dateFormats.add(dateFormat); + } + } + + protected LinkedHashSet getDateFormatsReference() { + return this.dateFormats; + } + + public LinkedHashSet getDateFormats() { + LinkedHashSet tmpDateFormats = new LinkedHashSet(); + Iterator i = this.getDateFormatsReference().iterator(); + while (i.hasNext()) { + tmpDateFormats.add((String)i.next()); + } + return tmpDateFormats; + } + + public String getDefaultDateFormat() { + return this.getDateFormatsReference().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; + } + + protected TimeZone getTimeZoneReference() { + return this.timeZone; + } + + public TimeZone getTimeZone() { + return (TimeZone)this.getTimeZoneReference().clone(); + } + + /* Conversion */ + + 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 MFormatConversionException { + 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 MFormatConversionException(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 MFormatConversionException(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 MFormatConversionException { + Date y = null; + for (String dateFormat: this.getDateFormatsReference()) { + try { + y = this.getDateFromStringByParameters(x, dateFormat, this.getLocale(), this.getTimeZoneReference()); + return y; + } + catch (MFormatConversionException exception) { + } + } + if (null == y) { + throw new MFormatConversionException(String.format("Invalid 'x': %s.", x)); + } + return y; // necessary to avoid Java compilation errors + } + + public String getStringFromDate(Date x) { + return this.getStringFromDateByParameters(x, this.getDefaultDateFormat(), this.getLocale(), this.getTimeZoneReference()); + } + + /* Helper methods */ + + 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(); + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/conversion/MFormatConversionException.java b/4.x/src/java/com/marcozanon/macaco/conversion/MFormatConversionException.java new file mode 100644 index 0000000..a94ff8c --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/conversion/MFormatConversionException.java @@ -0,0 +1,30 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon . + * Released under MIT license (see LICENSE for details). + */ + +package com.marcozanon.macaco.conversion; + +@SuppressWarnings("serial") +public class MFormatConversionException extends MConversionException { + + /* */ + + public MFormatConversionException() { + super(); + } + + public MFormatConversionException(String message) { + super(message); + } + + public MFormatConversionException(Throwable error) { + super(error); + } + + public MFormatConversionException(String message, Throwable error) { + super(message, error); + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/conversion/MNumberConverter.java b/4.x/src/java/com/marcozanon/macaco/conversion/MNumberConverter.java new file mode 100644 index 0000000..4d3ba8a --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/conversion/MNumberConverter.java @@ -0,0 +1,170 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon . + * 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.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 numberFormats = new LinkedHashSet(); + protected Locale locale = null; + + /* */ + + public MNumberConverter(String defaultNumberFormat, Locale locale) { + super(); + // + this.addNumberFormat(defaultNumberFormat); + this.setLocale(locale); + } + + public MNumberConverter(LinkedHashSet 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 numberFormats) { + if (null == numberFormats) { + throw new IllegalArgumentException("Invalid 'numberFormats': null."); + } + else { + Iterator i = numberFormats.iterator(); + while (i.hasNext()) { + MNumberConverter.checkNumberFormat((String)i.next()); + } + } + // + synchronized (this.numberFormats) { + this.numberFormats = numberFormats; + } + } + + public void addNumberFormat(String numberFormat) { + MNumberConverter.checkNumberFormat(numberFormat); + // + synchronized (this.numberFormats) { + this.numberFormats.add(numberFormat); + } + } + + protected LinkedHashSet getNumberFormatsReference() { + return this.numberFormats; + } + + public LinkedHashSet getNumberFormats() { + LinkedHashSet tmpNumberFormats = new LinkedHashSet(); + Iterator i = this.getNumberFormatsReference().iterator(); + while (i.hasNext()) { + tmpNumberFormats.add((String)i.next()); + } + return tmpNumberFormats; + } + + public String getDefaultNumberFormat() { + return this.getNumberFormatsReference().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; + } + + /* Conversion */ + + 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 MFormatConversionException { + 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 MFormatConversionException(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); + return df.format(number); + } + + public BigDecimal getNumberFromString(String x) throws MFormatConversionException { + BigDecimal y = null; + for (String numberFormat: this.getNumberFormatsReference()) { + try { + y = this.getNumberFromStringByParameters(x, numberFormat, this.getLocale()); + return y; + } + catch (MFormatConversionException exception) { + } + } + if (null == y) { + throw new MFormatConversionException(String.format("Invalid 'x': %s.", x)); + } + return y; // necessary to avoid Java compilation errors + } + + public String getStringFromNumber(BigDecimal x) { + return this.getStringFromNumberByParameters(x, this.getDefaultNumberFormat(), this.getLocale()); + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/json/MInvalidValueJsonException.java b/4.x/src/java/com/marcozanon/macaco/json/MInvalidValueJsonException.java new file mode 100644 index 0000000..5e021b2 --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/json/MInvalidValueJsonException.java @@ -0,0 +1,30 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon . + * Released under MIT license (see LICENSE for details). + */ + +package com.marcozanon.macaco.json; + +@SuppressWarnings("serial") +public class MInvalidValueJsonException extends MJsonException { + + /* */ + + public MInvalidValueJsonException() { + super(); + } + + public MInvalidValueJsonException(String message) { + super(message); + } + + public MInvalidValueJsonException(Throwable error) { + super(error); + } + + public MInvalidValueJsonException(String message, Throwable error) { + super(message, error); + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/json/MJsonArray.java b/4.x/src/java/com/marcozanon/macaco/json/MJsonArray.java new file mode 100644 index 0000000..87ccea6 --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/json/MJsonArray.java @@ -0,0 +1,208 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon . + * 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 values = new LinkedList(); + + /* */ + + public MJsonArray() throws MInvalidValueJsonException { + this("[]"); + } + + public MJsonArray(String x) throws MInvalidValueJsonException { + super(); + // + this.parseString(x); + } + + public MJsonArray clone() { + MJsonArray tmpMJsonArray = null; + try { + tmpMJsonArray = new MJsonArray(this.getJsonValue()); + } + catch (MInvalidValueJsonException exception) { // cannot happen + } + return tmpMJsonArray; + } + + /* Values handlers */ + + public void addValue(MJsonValue x) { + if (null == x) { + throw new IllegalArgumentException("Invalid 'x': null."); + } + // + this.getValuesReference().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.getValuesReference().set(index, x); + } + + protected LinkedList getValuesReference() { + 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.getValuesReference().get(index).clone(); + } + + public int getValueCount() { + return this.getValuesReference().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.getValuesReference().remove(index); + } + + public void clearValues() { + this.getValuesReference().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 (MInvalidValueJsonException exception) { + position = x.indexOf("]", position + 1); + } + } + return 0; + } + + public void parseString(String x) throws MInvalidValueJsonException { + 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 MInvalidValueJsonException(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 MInvalidValueJsonException(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.getValuesReference().get(i).getJsonValue()); + } + s.append("]"); + return s.toString(); + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/json/MJsonBoolean.java b/4.x/src/java/com/marcozanon/macaco/json/MJsonBoolean.java new file mode 100644 index 0000000..734d2cc --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/json/MJsonBoolean.java @@ -0,0 +1,84 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon . + * 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 MInvalidValueJsonException { + this("false"); + } + + public MJsonBoolean(String x) throws MInvalidValueJsonException { + super(); + // + this.parseString(x); + } + + public MJsonBoolean clone() { + MJsonBoolean tmpMJsonBoolean = null; + try { + tmpMJsonBoolean = new MJsonBoolean(this.getJsonValue()); + } + catch (MInvalidValueJsonException 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 MInvalidValueJsonException { + if (MText.isBlank(x)) { + throw new IllegalArgumentException("Invalid 'x': null or empty."); + } + // + if ((!"true".equals(x)) && (!"false".equals(x))) { + throw new MInvalidValueJsonException(String.format("Invalid 'x': %s: not a Json boolean.", x)); + } + this.setValue(Boolean.valueOf(x)); + } + + /* Formatter */ + + public String getJsonValue() { + return this.getValue().toString(); + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/json/MJsonException.java b/4.x/src/java/com/marcozanon/macaco/json/MJsonException.java new file mode 100644 index 0000000..4a07e50 --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/json/MJsonException.java @@ -0,0 +1,31 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon . + * 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); + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/json/MJsonNull.java b/4.x/src/java/com/marcozanon/macaco/json/MJsonNull.java new file mode 100644 index 0000000..222072a --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/json/MJsonNull.java @@ -0,0 +1,64 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon . + * 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 MInvalidValueJsonException { + this("null"); + } + + public MJsonNull(String x) throws MInvalidValueJsonException { + super(); + // + this.parseString(x); + } + + public MJsonNull clone() { + MJsonNull tmpMJsonNull = null; + try { + tmpMJsonNull = new MJsonNull(this.getJsonValue()); + } + catch (MInvalidValueJsonException 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 MInvalidValueJsonException { + if (MText.isBlank(x)) { + throw new IllegalArgumentException("Invalid 'x': null or empty."); + } + // + if (!"null".equals(x)) { + throw new MInvalidValueJsonException(String.format("Invalid 'x': %s: not a Json null.", x)); + } + } + + /* Formatter */ + + public String getJsonValue() { + return "null"; + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/json/MJsonNumber.java b/4.x/src/java/com/marcozanon/macaco/json/MJsonNumber.java new file mode 100644 index 0000000..5a31459 --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/json/MJsonNumber.java @@ -0,0 +1,104 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon . + * 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 MInvalidValueJsonException { + this("0"); + } + + public MJsonNumber(String x) throws MInvalidValueJsonException { + super(); + // + this.parseString(x); + } + + public MJsonNumber clone() { + MJsonNumber tmpMJsonNumber = null; + try { + tmpMJsonNumber = new MJsonNumber(this.getJsonValue()); + } + catch (MInvalidValueJsonException 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 MInvalidValueJsonException { + 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 MInvalidValueJsonException(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(); + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/json/MJsonObject.java b/4.x/src/java/com/marcozanon/macaco/json/MJsonObject.java new file mode 100644 index 0000000..a6c73e0 --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/json/MJsonObject.java @@ -0,0 +1,262 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon . + * 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 values = new LinkedHashMap(); + + /* */ + + public MJsonObject() throws MInvalidValueJsonException { + this("{}"); + } + + public MJsonObject(String x) throws MInvalidValueJsonException { + super(); + // + this.parseString(x); + } + + public MJsonObject clone() { + MJsonObject tmpMJsonObject = null; + try { + tmpMJsonObject = new MJsonObject(this.getJsonValue()); + } + catch (MInvalidValueJsonException exception) { // cannot happen + } + return tmpMJsonObject; + } + + /* Values 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.getValuesReference().put(key, x); + } + + protected LinkedHashMap getValuesReference() { + 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.getValuesReference().get(key).clone(); + } + + public int getValueCount() { + return this.getValuesReference().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.getValuesReference().remove(key); + } + + public void clearValues() { + this.getValuesReference().clear(); + } + + public boolean containsKey(String key) { + if (MText.isBlank(key)) { + throw new IllegalArgumentException("Invalid 'key': null or empty."); + } + // + return this.getValuesReference().containsKey(key); + } + + public LinkedHashSet getKeys() { + LinkedHashSet keys = new LinkedHashSet(); + for (String key: this.getValuesReference().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 (MInvalidValueJsonException exception) { + position = x.indexOf("}", position + 1); + } + } + return 0; + } + + public void parseString(String x) throws MInvalidValueJsonException { + 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 MInvalidValueJsonException(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 MInvalidValueJsonException(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.getValuesReference().keySet()) { + if (0 < k) { + s.append(", "); + } + s.append("\"" + MJsonString.getEscapedString(key, false) + "\""); + s.append(": "); + s.append(this.getValuesReference().get(key).getJsonValue()); + k++; + } + s.append("}"); + return s.toString(); + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/json/MJsonString.java b/4.x/src/java/com/marcozanon/macaco/json/MJsonString.java new file mode 100644 index 0000000..b0db6ec --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/json/MJsonString.java @@ -0,0 +1,292 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon . + * 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 MInvalidValueJsonException { + this("\"\""); + } + + public MJsonString(String x) throws MInvalidValueJsonException { + this(x, false); + } + + public MJsonString(String x, boolean extendedEscapeMode) throws MInvalidValueJsonException { + super(); + // + this.setExtendedEscapeMode(extendedEscapeMode); + this.parseString(x); + } + + public MJsonString clone() { + MJsonString tmpMJsonString = null; + try { + tmpMJsonString = new MJsonString(this.getJsonValue()); + } + catch (MInvalidValueJsonException 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 MInvalidValueJsonException { + 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 MInvalidValueJsonException(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 MInvalidValueJsonException(String.format("Invalid 'x': %s: not a Json string.", x)); + } + else if ((i + 2 + 4) > x.length()) { + throw new MInvalidValueJsonException(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 MInvalidValueJsonException(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 MInvalidValueJsonException(String.format("Invalid 'x': %s: not a Json string.", x)); // no need to propagate exception + } + } + } + else { + y.appendCodePoint(ch); + } + } + catch (NumberFormatException exception) { + throw new MInvalidValueJsonException(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 MInvalidValueJsonException { + 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 MInvalidValueJsonException(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 MInvalidValueJsonException(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()) + "\""; + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/json/MJsonValue.java b/4.x/src/java/com/marcozanon/macaco/json/MJsonValue.java new file mode 100644 index 0000000..e08b7a9 --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/json/MJsonValue.java @@ -0,0 +1,43 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon . + * 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(); + +} diff --git a/4.x/src/java/com/marcozanon/macaco/logging/MLogDatabaseTable.java b/4.x/src/java/com/marcozanon/macaco/logging/MLogDatabaseTable.java new file mode 100644 index 0000000..d4cb2a4 --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/logging/MLogDatabaseTable.java @@ -0,0 +1,126 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon . + * Released under MIT license (see LICENSE for details). + */ + +package com.marcozanon.macaco.logging; + +import com.marcozanon.macaco.sql.MConnectionSqlException; +import com.marcozanon.macaco.sql.MSqlConnection; +import com.marcozanon.macaco.sql.MSqlConnectionGenerator; +import com.marcozanon.macaco.sql.MSqlTable; +import com.marcozanon.macaco.sql.MStatementSqlException; +import com.marcozanon.macaco.text.MText; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.LinkedHashMap; + +public class MLogDatabaseTable extends MLogTarget { + + protected MSqlConnectionGenerator sqlConnectionGenerator = null; + + protected String logDatabaseTable = null; + protected String logDatabaseTablePrimaryKey = null; + protected String logDatabaseField = null; + + protected MSqlConnection sqlConnection = null; + + /* */ + + public MLogDatabaseTable(MSqlConnectionGenerator sqlConnectionGenerator, String logDatabaseTable, String logDatabaseTablePrimaryKey, String logDatabaseField) throws MLoggingException { + super(); + // + if (null == sqlConnectionGenerator) { + throw new IllegalArgumentException("Invalid 'sqlConnectionGenerator': 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.sqlConnectionGenerator = sqlConnectionGenerator; + this.logDatabaseTable = logDatabaseTable; + this.logDatabaseTablePrimaryKey = logDatabaseTablePrimaryKey; + this.logDatabaseField = logDatabaseField; + } + + public void close() throws MLoggingException { + try { + this.getSqlConnectionReference().close(); + } + catch (MConnectionSqlException exception) { + throw new MLoggingException("Could not close Sql connection.", exception); + } + } + + /* Sql connection generator */ + + protected MSqlConnectionGenerator getSqlConnectionGeneratorReference() { + return this.sqlConnectionGenerator; + } + + /* Logging database parameters */ + + protected String getLogDatabaseTable() { + return this.logDatabaseTable; + } + + protected String getLogDatabaseTablePrimaryKey() { + return this.logDatabaseTablePrimaryKey; + } + + protected String getLogDatabaseField() { + return this.logDatabaseField; + } + + /* Sql connection */ + + protected MSqlConnection getSqlConnectionReference() { + return this.sqlConnection; + } + + /* 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 HH:mm:ss")).format(new Date()); + // + StringBuilder separator = new StringBuilder(" "); + for (int i = 0; i < indentation; i++) { + separator.append(" "); + } + // + if (null == this.getSqlConnectionReference()) { + this.sqlConnection = this.getSqlConnectionGeneratorReference().getConnection(); + } + // + LinkedHashMap p = new LinkedHashMap(); + p.put(this.getLogDatabaseField(), timestamp + separator + message); + (new MSqlTable(this.getSqlConnectionReference(), this.getLogDatabaseTable(), this.getLogDatabaseTablePrimaryKey())).setRecord(p, null); + } + catch (MConnectionSqlException exception) { + throw new MLoggingException("Could not write to database table.", exception); + } + catch (MStatementSqlException exception) { + throw new MLoggingException("Could not write to database table.", exception); + } + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/logging/MLogFilter.java b/4.x/src/java/com/marcozanon/macaco/logging/MLogFilter.java new file mode 100644 index 0000000..5a380af --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/logging/MLogFilter.java @@ -0,0 +1,125 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon . + * 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, + HIGH, + DEBUG + }; + + protected MLogFilter.Threshold threshold = null; + + protected LinkedList logTargets = new LinkedList(); + + protected boolean pausedState = false; + protected LinkedList logMessageQueue = new LinkedList(); + + /* */ + + public MLogFilter(MLogFilter.Threshold threshold) { + super(); + // + this.setThreshold(threshold); + } + + public void close() throws MLoggingException { + for (MLogTarget t: this.getLogTargetsReference()) { + 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 getLogTargetsReference() { + 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.getLogTargetsReference()) { + logTarget.appendMessage(message, indentation); + } + } + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/logging/MLogMessage.java b/4.x/src/java/com/marcozanon/macaco/logging/MLogMessage.java new file mode 100644 index 0000000..becf101 --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/logging/MLogMessage.java @@ -0,0 +1,40 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon . + * 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; + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/logging/MLogPlainTextFile.java b/4.x/src/java/com/marcozanon/macaco/logging/MLogPlainTextFile.java new file mode 100644 index 0000000..45f0a64 --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/logging/MLogPlainTextFile.java @@ -0,0 +1,100 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon . + * 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.getBufferReference().close(); + } + catch (IOException exception) { + throw new MLoggingException("Could not close file.", exception); + } + } + + /* File */ + + protected String getFile() { + return this.file; + } + + /* Buffer */ + + protected BufferedWriter getBufferReference() { + 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 HH:mm:ss")).format(new Date()); + // + StringBuilder separator = new StringBuilder(" "); + for (int i = 0; i < indentation; i++) { + separator.append(" "); + } + // + synchronized (this.getBufferReference()) { + this.getBufferReference().write(timestamp + separator + message); + this.getBufferReference().newLine(); + this.getBufferReference().flush(); + } + } + catch (IOException exception) { + throw new MLoggingException("Could not write to file.", exception); + } + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/logging/MLogTarget.java b/4.x/src/java/com/marcozanon/macaco/logging/MLogTarget.java new file mode 100644 index 0000000..01a256f --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/logging/MLogTarget.java @@ -0,0 +1,31 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon . + * 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; + +} diff --git a/4.x/src/java/com/marcozanon/macaco/logging/MLoggingException.java b/4.x/src/java/com/marcozanon/macaco/logging/MLoggingException.java new file mode 100644 index 0000000..b5938a5 --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/logging/MLoggingException.java @@ -0,0 +1,32 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon . + * 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); + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/sql/MConnectionSqlException.java b/4.x/src/java/com/marcozanon/macaco/sql/MConnectionSqlException.java new file mode 100644 index 0000000..6f7f33a --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/sql/MConnectionSqlException.java @@ -0,0 +1,30 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon . + * Released under MIT license (see LICENSE for details). + */ + +package com.marcozanon.macaco.sql; + +@SuppressWarnings("serial") +public class MConnectionSqlException extends MSqlException { + + /* */ + + public MConnectionSqlException() { + super(); + } + + public MConnectionSqlException(String message) { + super(message); + } + + public MConnectionSqlException(Throwable error) { + super(error); + } + + public MConnectionSqlException(String message, Throwable error) { + super(message, error); + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/sql/MSqlConnection.java b/4.x/src/java/com/marcozanon/macaco/sql/MSqlConnection.java new file mode 100644 index 0000000..44ee453 --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/sql/MSqlConnection.java @@ -0,0 +1,237 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon . + * Released under MIT license (see LICENSE for details). + */ + +package com.marcozanon.macaco.sql; + +import com.marcozanon.macaco.MObject; +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.LinkedList; + +public class MSqlConnection 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 Connection connection = null; + + protected MSqlConnection.TransactionStatus transactionStatus = MSqlConnection.TransactionStatus.CLOSED; + + /* */ + + public MSqlConnection(String driver, String url, String username, String password) throws MConnectionSqlException { + 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; + // load driver + try { + Class.forName(this.getDriver()).newInstance(); + } + catch (ClassNotFoundException exception) { + throw new MConnectionSqlException("Could not load driver.", exception); + } + catch (IllegalAccessException exception) { + throw new MConnectionSqlException("Could not load driver.", exception); + } + catch (InstantiationException exception) { + throw new MConnectionSqlException("Could not load driver.", exception); + } + // establish connection + try { + this.connection = DriverManager.getConnection(this.getUrl(), this.getUsername(), this.getPassword()); + } + catch (SQLException exception) { + throw new MConnectionSqlException("Could not get connection.", exception); + } + // set SQL mode + try { + this.getConnectionReference().createStatement().executeUpdate("SET SQL_MODE='ANSI'"); + } + catch (SQLException exception) { + throw new MConnectionSqlException("Could not set SQL mode to ANSI.", exception); + } + } + + public void close() throws MConnectionSqlException { + try { + this.getConnectionReference().close(); + } + catch (SQLException exception) { + throw new MConnectionSqlException("Could not close connection.", 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 getConnectionReference() { + return this.connection; + } + + /* Transactions */ + + protected void setTransactionStatus(MSqlConnection.TransactionStatus transactionStatus) { + if (null == transactionStatus) { + throw new IllegalArgumentException("Invalid 'transactionStatus': null."); + } + // + this.transactionStatus = transactionStatus; + } + + public MSqlConnection.TransactionStatus getTransactionStatus() { + return this.transactionStatus; + } + + public void startTransaction() throws MTransactionSqlException { + if (MSqlConnection.TransactionStatus.CLOSED != this.getTransactionStatus()) { + throw new MTransactionSqlException("Nested transactions not allowed."); + } + try { + this.getConnectionReference().setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); + this.getConnectionReference().setAutoCommit(false); + this.setTransactionStatus(MSqlConnection.TransactionStatus.SUCCESSFUL); + } + catch (SQLException exception) { + throw new MTransactionSqlException("Could not start transaction.", exception); + } + } + + public void rollBackTransaction() throws MTransactionSqlException { + if (MSqlConnection.TransactionStatus.CLOSED == this.getTransactionStatus()) { + throw new MTransactionSqlException("Transaction not started."); + } + try { + this.getConnectionReference().rollback(); + this.getConnectionReference().setAutoCommit(true); + this.setTransactionStatus(MSqlConnection.TransactionStatus.CLOSED); + } + catch (SQLException exception) { + this.setTransactionStatus(MSqlConnection.TransactionStatus.FAILED); + throw new MTransactionSqlException("Could not roll back transaction.", exception); + } + } + + public MSqlConnection.TransactionStatus commitTransaction() throws MTransactionSqlException { + switch (this.getTransactionStatus()) { + case SUCCESSFUL: + try { + this.getConnectionReference().commit(); + this.getConnectionReference().setAutoCommit(true); + this.setTransactionStatus(MSqlConnection.TransactionStatus.CLOSED); + return MSqlConnection.TransactionStatus.SUCCESSFUL; + } + catch (SQLException exception) { + this.rollBackTransaction(); + return MSqlConnection.TransactionStatus.FAILED; + } + case FAILED: + this.rollBackTransaction(); + return MSqlConnection.TransactionStatus.FAILED; + default: // instead of "case CLOSED:" (necessary to avoid Java compilation errors) + throw new MTransactionSqlException("Transaction not started."); + } + } + + /* Statements */ + + public MSqlStatementResults executePreparedStatement(String statement) throws MStatementSqlException { + return this.executePreparedStatement(statement, new LinkedList()); + } + + public MSqlStatementResults executePreparedStatement(String statement, LinkedList parameters) throws MStatementSqlException { + 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.getConnectionReference().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); + } + catch (SQLException exception) { + if (MSqlConnection.TransactionStatus.SUCCESSFUL == this.getTransactionStatus()) { + this.setTransactionStatus(MSqlConnection.TransactionStatus.FAILED); + } + throw new MStatementSqlException(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); + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/sql/MSqlConnectionGenerator.java b/4.x/src/java/com/marcozanon/macaco/sql/MSqlConnectionGenerator.java new file mode 100644 index 0000000..e141ebc --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/sql/MSqlConnectionGenerator.java @@ -0,0 +1,73 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon . + * Released under MIT license (see LICENSE for details). + */ + +package com.marcozanon.macaco.sql; + +import com.marcozanon.macaco.MObject; +import com.marcozanon.macaco.text.MText; + +public class MSqlConnectionGenerator extends MObject { + + protected String driver = null; + protected String url = null; + protected String username = null; + protected String password = null; + + /* */ + + public MSqlConnectionGenerator(String driver, String url, String username, String password) { + 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; + } + + /* 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; + } + + /* Generator */ + + public MSqlConnection getConnection() throws MConnectionSqlException { + return new MSqlConnection(this.getDriver(), this.getUrl(), this.getUsername(), this.getPassword()); + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/sql/MSqlException.java b/4.x/src/java/com/marcozanon/macaco/sql/MSqlException.java new file mode 100644 index 0000000..6309000 --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/sql/MSqlException.java @@ -0,0 +1,31 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon . + * Released under MIT license (see LICENSE for details). + */ + +package com.marcozanon.macaco.sql; + +import com.marcozanon.macaco.MException; + +public abstract class MSqlException extends MException { + + /* */ + + public MSqlException() { + super(); + } + + public MSqlException(String message) { + super(message); + } + + public MSqlException(Throwable error) { + super(error); + } + + public MSqlException(String message, Throwable error) { + super(message, error); + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/sql/MSqlStatementResults.java b/4.x/src/java/com/marcozanon/macaco/sql/MSqlStatementResults.java new file mode 100644 index 0000000..0cd1180 --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/sql/MSqlStatementResults.java @@ -0,0 +1,149 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon . + * Released under MIT license (see LICENSE for details). + */ + +package com.marcozanon.macaco.sql; + +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> records = new LinkedList>(); + + protected LinkedHashSet generatedKeys = new LinkedHashSet(); + + /* */ + + public MSqlStatementResults(PreparedStatement preparedStatement) throws MStatementSqlException { + super(); + // + if (null == preparedStatement) { + throw new IllegalArgumentException("Invalid 'preparedStatement': null."); + } + // + this.preparedStatement = preparedStatement; + // + try { + this.resultSet = this.getPreparedStatementReference().getResultSet(); + if (null != this.getResultSetReference()) { + while (this.getResultSetReference().next()) { + ResultSetMetaData resultSetMetaData = this.getResultSetReference().getMetaData(); + LinkedHashMap record = new LinkedHashMap(); + for (int f = 0; resultSetMetaData.getColumnCount() > f; f++) { + String field = resultSetMetaData.getColumnLabel(f + 1); + record.put(field, this.getResultSetReference().getObject(field)); + } + this.getRecordsReference().add(record); + } + } + } + catch (SQLException exception) { + throw new MStatementSqlException("Could not retrieve statement records.", exception); + } + // + try { + ResultSet generatedKeysResultSet = this.getPreparedStatementReference().getGeneratedKeys(); + while (generatedKeysResultSet.next()) { + this.getGeneratedKeysReference().add(generatedKeysResultSet.getObject(1)); + } + generatedKeysResultSet.close(); + } + catch (SQLException exception) { + throw new MStatementSqlException("Could not retrieve generated keys.", exception); + } + } + + public void close() throws MConnectionSqlException { + try { + if (null != this.getResultSetReference()) { + this.getResultSetReference().close(); + } + this.getPreparedStatementReference().close(); + } + catch (SQLException exception) { + throw new MConnectionSqlException("Could not close statement and/or result set references.", exception); + } + } + + protected void finalize() { + try { + this.close(); + } + catch (Exception exception) { + } + } + + /* References */ + + protected PreparedStatement getPreparedStatementReference() { + return this.preparedStatement; + } + + public ResultSet getResultSetReference() { + return this.resultSet; + } + + /* Records */ + + protected LinkedList> getRecordsReference() { + return this.records; + } + + public LinkedList> getRecords() { + LinkedList> tmpRecords = new LinkedList>(); + for (LinkedHashMap record: this.getRecordsReference()) { + LinkedHashMap tmpRecord = new LinkedHashMap(); + for (String key: record.keySet()) { + tmpRecord.put(key, record.get(key)); + } + tmpRecords.add(tmpRecord); + } + return tmpRecords; + } + + public LinkedList getRecordsByField(String field) { + if (MText.isBlank(field)) { + throw new IllegalArgumentException("Invalid 'field': null or empty."); + } + // + LinkedList recordsByField = new LinkedList(); + for (LinkedHashMap record: this.getRecordsReference()) { + Object r = record.get(field); + if (null == r) { + throw new IllegalArgumentException(String.format("Invalid 'field': %s.", field)); + } + recordsByField.add(r); + } + return recordsByField; + } + + /* Generated keys */ + + protected LinkedHashSet getGeneratedKeysReference() { + return this.generatedKeys; + } + + public LinkedHashSet getGeneratedKeys() { + LinkedHashSet tmpGeneratedKeys = new LinkedHashSet(); + Iterator i = this.getGeneratedKeysReference().iterator(); + while (i.hasNext()) { + tmpGeneratedKeys.add(i.next()); + } + return tmpGeneratedKeys; + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/sql/MSqlTable.java b/4.x/src/java/com/marcozanon/macaco/sql/MSqlTable.java new file mode 100644 index 0000000..a4084b3 --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/sql/MSqlTable.java @@ -0,0 +1,154 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon . + * Released under MIT license (see LICENSE for details). + */ + +package com.marcozanon.macaco.sql; + +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 MSqlConnection connection = null; + protected String table = null; + protected String primaryKey = null; + + /* */ + + public MSqlTable(MSqlConnection connection, String table, String primaryKey) { + super(); + // + if (null == connection) { + throw new IllegalArgumentException("Invalid 'connection': 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.connection = connection; + this.table = table; + this.primaryKey = primaryKey; + } + + /* Connection */ + + protected MSqlConnection getConnectionReference() { + return this.connection; + } + + /* Table */ + + public String getTable() { + return this.table; + } + + /* Primary key */ + + public String getPrimaryKey() { + return this.primaryKey; + } + + /* Statements */ + + protected MSqlStatementResults insertRecord(LinkedHashMap map) throws MStatementSqlException { + if (null == map) { + throw new IllegalArgumentException("Invalid 'map': null."); + } + // + LinkedList p = new LinkedList(); + 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.getConnectionReference().executePreparedStatement(" INSERT INTO \"" + this.getTable() + "\"" + + " (" + fields + ")" + + " VALUES (" + s + ")", + p); + } + + protected MSqlStatementResults updateRecord(LinkedHashMap map, Object id) throws MStatementSqlException { + if (null == map) { + throw new IllegalArgumentException("Invalid 'map': null."); + } + if (null == id) { + throw new IllegalArgumentException("Invalid 'id': null."); + } + // + LinkedList p = new LinkedList(); + 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.getConnectionReference().executePreparedStatement(" UPDATE \"" + this.getTable() + "\"" + + " SET " + s + + " WHERE (\"" + this.getTable() + "\".\"" + this.getPrimaryKey() + "\" = ?)", + p); + } + + public MSqlStatementResults setRecord(LinkedHashMap map, Object id) throws MStatementSqlException { + if (null == id) { + return this.insertRecord(map); + } + else { + return this.updateRecord(map, id); + } + } + + public MSqlStatementResults getRecord(Object id) throws MStatementSqlException { + if (null == id) { + throw new IllegalArgumentException("Invalid 'id': null."); + } + // + LinkedList p = new LinkedList(); + p.add(id); + return this.getConnectionReference().executePreparedStatement(" SELECT *" + + " FROM \"" + this.getTable() + "\"" + + " WHERE (\"" + this.getTable() + "\".\"" + this.getPrimaryKey() + "\" = ?)", + p); + } + + public MSqlStatementResults deleteRecord(Object id) throws MStatementSqlException { + if (null == id) { + throw new IllegalArgumentException("Invalid 'id': null."); + } + // + LinkedList p = new LinkedList(); + p.add(id); + return this.getConnectionReference().executePreparedStatement(" DELETE FROM \"" + this.getTable() + "\"" + + " WHERE (\"" + this.getTable() + "\".\"" + this.getPrimaryKey() + "\" = ?)", + p); + } + + public MSqlStatementResults deleteRecords(LinkedHashSet idSet) throws MStatementSqlException { + if (null == idSet) { + throw new IllegalArgumentException("Invalid 'idSet': null."); + } + // + StringBuilder whereClause = new StringBuilder("(0)"); + LinkedList p = new LinkedList(); + for (Object id: idSet) { + whereClause.append(" OR (\"" + this.getTable() + "\".\"" + this.getPrimaryKey() + "\" = ?)"); + p.add(id); + } + return this.getConnectionReference().executePreparedStatement(" DELETE FROM \"" + this.getTable() + "\"" + + " WHERE (" + whereClause + ")", + p); + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/sql/MStatementSqlException.java b/4.x/src/java/com/marcozanon/macaco/sql/MStatementSqlException.java new file mode 100644 index 0000000..ff363b0 --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/sql/MStatementSqlException.java @@ -0,0 +1,30 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon . + * Released under MIT license (see LICENSE for details). + */ + +package com.marcozanon.macaco.sql; + +@SuppressWarnings("serial") +public class MStatementSqlException extends MSqlException { + + /* */ + + public MStatementSqlException() { + super(); + } + + public MStatementSqlException(String message) { + super(message); + } + + public MStatementSqlException(Throwable error) { + super(error); + } + + public MStatementSqlException(String message, Throwable error) { + super(message, error); + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/sql/MTransactionSqlException.java b/4.x/src/java/com/marcozanon/macaco/sql/MTransactionSqlException.java new file mode 100644 index 0000000..2987b4b --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/sql/MTransactionSqlException.java @@ -0,0 +1,30 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon . + * Released under MIT license (see LICENSE for details). + */ + +package com.marcozanon.macaco.sql; + +@SuppressWarnings("serial") +public class MTransactionSqlException extends MSqlException { + + /* */ + + public MTransactionSqlException() { + super(); + } + + public MTransactionSqlException(String message) { + super(message); + } + + public MTransactionSqlException(Throwable error) { + super(error); + } + + public MTransactionSqlException(String message, Throwable error) { + super(message, error); + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/text/MText.java b/4.x/src/java/com/marcozanon/macaco/text/MText.java new file mode 100644 index 0000000..e33d778 --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/text/MText.java @@ -0,0 +1,439 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon . + * 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", "
"); + // + 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 MXhtmlUnsafeStringTextException { + if (null == x) { + return null; + } + // + String text = x; + // + StringBuilder fakeXhtmlPageContent = new StringBuilder(""); + fakeXhtmlPageContent.append(String.format("", MInformation.TEXT_ENCODING)); + fakeXhtmlPageContent.append(""); + fakeXhtmlPageContent.append(""); + fakeXhtmlPageContent.append("</head>"); + fakeXhtmlPageContent.append(String.format("<body>%s</body>", text)); + fakeXhtmlPageContent.append("</html>"); + // validate Xhtml (without 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 MXhtmlUnsafeStringTextException("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); + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/text/MTextException.java b/4.x/src/java/com/marcozanon/macaco/text/MTextException.java new file mode 100644 index 0000000..fc73ab0 --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/text/MTextException.java @@ -0,0 +1,31 @@ +/** + * Macaco + * Copyright (c) 2009-2015 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); + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/text/MTranslationException.java b/4.x/src/java/com/marcozanon/macaco/text/MTranslationException.java new file mode 100644 index 0000000..9f2f6ba --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/text/MTranslationException.java @@ -0,0 +1,29 @@ +/** + * Macaco + * Copyright (c) 2009-2015 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); + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/text/MTranslationFileParsingTextException.java b/4.x/src/java/com/marcozanon/macaco/text/MTranslationFileParsingTextException.java new file mode 100644 index 0000000..2149ed2 --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/text/MTranslationFileParsingTextException.java @@ -0,0 +1,30 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon <info@marcozanon.com>. + * Released under MIT license (see LICENSE for details). + */ + +package com.marcozanon.macaco.text; + +@SuppressWarnings("serial") +public class MTranslationFileParsingTextException extends MTranslationException { + + /* */ + + public MTranslationFileParsingTextException() { + super(); + } + + public MTranslationFileParsingTextException(String message) { + super(message); + } + + public MTranslationFileParsingTextException(Throwable error) { + super(error); + } + + public MTranslationFileParsingTextException(String message, Throwable error) { + super(message, error); + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/text/MTranslationValueNotFoundTextException.java b/4.x/src/java/com/marcozanon/macaco/text/MTranslationValueNotFoundTextException.java new file mode 100644 index 0000000..33a016f --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/text/MTranslationValueNotFoundTextException.java @@ -0,0 +1,30 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon <info@marcozanon.com>. + * Released under MIT license (see LICENSE for details). + */ + +package com.marcozanon.macaco.text; + +@SuppressWarnings("serial") +public class MTranslationValueNotFoundTextException extends MTranslationException { + + /* */ + + public MTranslationValueNotFoundTextException() { + super(); + } + + public MTranslationValueNotFoundTextException(String message) { + super(message); + } + + public MTranslationValueNotFoundTextException(Throwable error) { + super(error); + } + + public MTranslationValueNotFoundTextException(String message, Throwable error) { + super(message, error); + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/text/MTranslator.java b/4.x/src/java/com/marcozanon/macaco/text/MTranslator.java new file mode 100644 index 0000000..b861837 --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/text/MTranslator.java @@ -0,0 +1,190 @@ +/** + * Macaco + * Copyright (c) 2009-2015 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 MTranslationFileParsingTextException { + 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 (MTranslationFileParsingTextException exception) { // should not happen + } + return tmpMTranslator; + } + + /* File */ + + protected String getFile() { + return this.file; + } + + /* Locale */ + + protected Locale getBasicLocale() { + return this.basicLocale; + } + + /* Strings management */ + + protected LinkedHashMap<String, LinkedHashMap<String, String>> getMessagesReference() { + return this.messages; + } + + public void parseFile(String file) throws MTranslationFileParsingTextException { + 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 MTranslationFileParsingTextException("Could not open file.", exception); + } + catch (UnsupportedEncodingException exception) { // cannot happen + } + String message = null; + String line = null; + synchronized (this.getMessagesReference()) { + while (true) { + try { + line = buffer.readLine(); + } + catch (IOException exception) { + throw new MTranslationFileParsingTextException("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.getMessagesReference().containsKey(message)) { + throw new MTranslationFileParsingTextException(String.format("Invalid line: %s: duplicated message: %s.", buffer.getLineNumber(), message)); + } + this.getMessagesReference().put(message, new LinkedHashMap<String, String>()); + } + else { + int a = line.indexOf("="); + if (-1 == a) { + throw new MTranslationFileParsingTextException(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.getMessagesReference().get(message).containsKey(localeRepresentation)) { + throw new MTranslationFileParsingTextException(String.format("Invalid line: %s: duplicated translation for locale: %s.", buffer.getLineNumber(), message)); + } + this.getMessagesReference().get(message).put(localeRepresentation, translation); + } + } + } + try { + buffer.close(); + } + catch (IOException exception) { + throw new MTranslationFileParsingTextException("Could not close file.", exception); + } + } + + public void clear() { + synchronized (this.getMessagesReference()) { + this.getMessagesReference().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 (MTranslationValueNotFoundTextException exception) { // cannot happen + } + return translation; + } + + public String getStrictTranslation(String message, Locale locale) throws MTranslationValueNotFoundTextException { + return this.getTranslation(message, locale, true); + } + + protected String getTranslation(String message, Locale locale, boolean strictMode) throws MTranslationValueNotFoundTextException { + if (MText.isBlank(message)) { + throw new IllegalArgumentException("Invalid 'message': null or empty."); + } + if (null == locale) { + throw new IllegalArgumentException("Invalid 'locale': null."); + } + // + if (!this.getMessagesReference().containsKey(message)) { + if (strictMode) { + throw new MTranslationValueNotFoundTextException(String.format("Invalid 'message': %s: not available.", message)); + } + return message; + } + if (this.getBasicLocale().equals(locale)) { + return message; + } + LinkedHashMap<String, String> messageTranslations = this.getMessagesReference().get(message); + String localeRepresentation = locale.toString(); + if (!messageTranslations.containsKey(localeRepresentation)) { + if (strictMode) { + throw new MTranslationValueNotFoundTextException(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); + } + +} diff --git a/4.x/src/java/com/marcozanon/macaco/text/MXhtmlUnsafeStringTextException.java b/4.x/src/java/com/marcozanon/macaco/text/MXhtmlUnsafeStringTextException.java new file mode 100644 index 0000000..bbfdf45 --- /dev/null +++ b/4.x/src/java/com/marcozanon/macaco/text/MXhtmlUnsafeStringTextException.java @@ -0,0 +1,30 @@ +/** + * Macaco + * Copyright (c) 2009-2015 Marco Zanon <info@marcozanon.com>. + * Released under MIT license (see LICENSE for details). + */ + +package com.marcozanon.macaco.text; + +@SuppressWarnings("serial") +public class MXhtmlUnsafeStringTextException extends MTextException { + + /* */ + + public MXhtmlUnsafeStringTextException() { + super(); + } + + public MXhtmlUnsafeStringTextException(String message) { + super(message); + } + + public MXhtmlUnsafeStringTextException(Throwable error) { + super(error); + } + + public MXhtmlUnsafeStringTextException(String message, Throwable error) { + super(message, error); + } + +} -- 2.30.2