From: Marco Zanon Date: Mon, 21 Mar 2022 10:17:39 +0000 (+0000) Subject: Refactored the source tree. X-Git-Tag: SVN-to-Git~38 X-Git-Url: https://gitweb.marcozanon.com/?a=commitdiff_plain;h=93743b8f307e99caf396aae757cda93659a657fe;p=Macaco Refactored the source tree. --- diff --git a/7.x/build.xml b/7.x/build.xml index 4e07f77..5439e98 100644 --- a/7.x/build.xml +++ b/7.x/build.xml @@ -21,7 +21,7 @@ - + diff --git a/7.x/src/java/com/marcozanon/macaco/MConstants.java b/7.x/src/java/com/marcozanon/macaco/MConstants.java deleted file mode 100644 index 00a0c8a..0000000 --- a/7.x/src/java/com/marcozanon/macaco/MConstants.java +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * See LICENSE for details. - */ - -package com.marcozanon.macaco; - - -public class MConstants extends MObject { - - public static final String MACACO_VERSION = "7.x"; - - public static final String TEXT_ENCODING = "UTF-8"; - -} diff --git a/7.x/src/java/com/marcozanon/macaco/MException.java b/7.x/src/java/com/marcozanon/macaco/MException.java deleted file mode 100644 index 1de6b77..0000000 --- a/7.x/src/java/com/marcozanon/macaco/MException.java +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * 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/7.x/src/java/com/marcozanon/macaco/MInformation.java b/7.x/src/java/com/marcozanon/macaco/MInformation.java deleted file mode 100644 index b50ebd7..0000000 --- a/7.x/src/java/com/marcozanon/macaco/MInformation.java +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * See LICENSE for details. - */ - -package com.marcozanon.macaco; - -import java.io.PrintWriter; -import java.io.StringWriter; - -public class MInformation extends MObject { - - /* Generic information. */ - - public static String getMacacoVersion() { - return MConstants.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-2022 by Marco Zanon." + System.getProperty("line.separator")); - s.append("Released under MIT license (see LICENSE.MIT.sample for details)." + System.getProperty("line.separator")); - s.append("Small portions inspired by other projects or web pages. See source code for additional information."); - return s.toString(); - } - - /* Throwables. */ - - public static String getThrowableAsString(Throwable throwable) { - if (null == throwable) { - throw new IllegalArgumentException("Invalid 'throwable': null."); - } - // - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - throwable.printStackTrace(pw); - pw.flush(); - sw.flush(); - // - return sw.toString(); - } - -} diff --git a/7.x/src/java/com/marcozanon/macaco/MObject.java b/7.x/src/java/com/marcozanon/macaco/MObject.java deleted file mode 100644 index a1d6233..0000000 --- a/7.x/src/java/com/marcozanon/macaco/MObject.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * See LICENSE for details. - */ - -package com.marcozanon.macaco; - -public abstract class MObject { - - /* */ - - @Override - protected MObject clone() { - throw new UnsupportedOperationException("Please provide manually by yourself."); - } - - @Override - public String toString() { - throw new UnsupportedOperationException("Please use appropriate methods (if any)."); - } - -} diff --git a/7.x/src/java/com/marcozanon/macaco/conversion/MConversionException.java b/7.x/src/java/com/marcozanon/macaco/conversion/MConversionException.java deleted file mode 100644 index 36efa74..0000000 --- a/7.x/src/java/com/marcozanon/macaco/conversion/MConversionException.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * 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/7.x/src/java/com/marcozanon/macaco/conversion/MDateConverter.java b/7.x/src/java/com/marcozanon/macaco/conversion/MDateConverter.java deleted file mode 100644 index 43dec3b..0000000 --- a/7.x/src/java/com/marcozanon/macaco/conversion/MDateConverter.java +++ /dev/null @@ -1,238 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * 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.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); - } - - @Override - 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 if (0 == dateFormats.size()) { - throw new IllegalArgumentException("Invalid 'dateFormats': empty."); - } - else { - for (String dateFormat: dateFormats) { - MDateConverter.checkDateFormat(dateFormat); - } - } - // - synchronized (this.dateFormats) { - this.dateFormats = dateFormats; - } - } - - public void addDateFormat(String dateFormat) { - MDateConverter.checkDateFormat(dateFormat); - // - synchronized (this.dateFormats) { - this.dateFormats.add(dateFormat); - } - } - - public LinkedHashSet getDateFormats() { - return this.dateFormats; - } - - public String getDefaultDateFormat() { - return this.getDateFormats().iterator().next(); - } - - /* Locale. */ - - protected void setLocale(Locale locale) { - if (null == locale) { - throw new IllegalArgumentException("Invalid 'locale': null."); - } - // - this.locale = locale; - } - - public Locale getLocale() { - return this.locale; - } - - /* Time zone. */ - - protected void setTimeZone(TimeZone timeZone) { - if (null == timeZone) { - throw new IllegalArgumentException("Invalid 'timeZone': null."); - } - // - this.timeZone = timeZone; - } - - public TimeZone getTimeZone() { - return this.timeZone; - } - - /* Conversions. */ - - protected static void checkDateFormat(String dateFormat) { - if (MText.isBlank(dateFormat)) { - throw new IllegalArgumentException("Invalid 'dateFormat': null or empty."); - } - // - try { - SimpleDateFormat testDateFormat = new SimpleDateFormat(dateFormat); - } - catch (IllegalArgumentException exception) { - throw new IllegalArgumentException(String.format("Invalid 'dateFormat': %s.", dateFormat)); // no need to propagate exception - } - } - - protected static Date getDateFromStringByParameters(String x, String inputDateFormat, Locale inputLocale, TimeZone inputTimeZone) throws MInvalidConversionFormatException { - if (MText.isBlank(x)) { - throw new IllegalArgumentException("Invalid 'x': null or empty."); - } - MDateConverter.checkDateFormat(inputDateFormat); - if (null == inputLocale) { - throw new IllegalArgumentException("Invalid 'inputLocale': null."); - } - if (null == inputTimeZone) { - throw new IllegalArgumentException("Invalid 'inputTimeZone': null."); - } - // - Calendar c1 = Calendar.getInstance(inputTimeZone, inputLocale); - c1.clear(); - c1.setLenient(false); - SimpleDateFormat sdf = new SimpleDateFormat(inputDateFormat, inputLocale); - sdf.setCalendar(c1); - Date d1 = null; - try { - d1 = sdf.parse(x); - } - catch (ParseException exception) { - throw new MInvalidConversionFormatException(String.format("Invalid 'x' or parsing: %s (input format: %s).", x, inputDateFormat)); // no need to propagate exception - } - Calendar c2 = Calendar.getInstance(inputTimeZone, inputLocale); - c2.clear(); - c2.set(Calendar.YEAR, c1.get(Calendar.YEAR)); - c2.set(Calendar.MONTH, c1.get(Calendar.MONTH)); - c2.set(Calendar.DAY_OF_MONTH, c1.get(Calendar.DAY_OF_MONTH)); - c2.set(Calendar.HOUR_OF_DAY, c1.get(Calendar.HOUR_OF_DAY)); - c2.set(Calendar.MINUTE, c1.get(Calendar.MINUTE)); - c2.set(Calendar.SECOND, c1.get(Calendar.SECOND)); - Date d2 = c2.getTime(); - if (!x.equals(sdf.format(d2))) { - throw new MInvalidConversionFormatException(String.format("Invalid 'x' or parsing: %s (input format: %s).", x, inputDateFormat)); - } - return d2; - } - - protected static String getStringFromDateByParameters(Date date, String outputDateFormat, Locale outputLocale, TimeZone outputTimeZone) { - if (null == date) { - throw new IllegalArgumentException("Invalid 'date': null."); - } - MDateConverter.checkDateFormat(outputDateFormat); - if (null == outputLocale) { - throw new IllegalArgumentException("Invalid 'outputLocale': null."); - } - if (null == outputTimeZone) { - throw new IllegalArgumentException("Invalid 'outputTimeZone': null."); - } - // - SimpleDateFormat sdf = new SimpleDateFormat(outputDateFormat, outputLocale); - sdf.setCalendar(Calendar.getInstance(outputTimeZone, outputLocale)); - return sdf.format(date); - } - - public Date getDateFromString(String x) throws MInvalidConversionFormatException { - Date y = null; - for (String dateFormat: this.getDateFormats()) { - try { - y = MDateConverter.getDateFromStringByParameters(x, dateFormat, this.getLocale(), this.getTimeZone()); - return y; - } - catch (MInvalidConversionFormatException exception) { - } - } - if (null == y) { - throw new MInvalidConversionFormatException(String.format("Invalid 'x': %s.", x)); - } - return y; // necessary to avoid Java compilation errors - } - - public String getStringFromDate(Date x) { - return MDateConverter.getStringFromDateByParameters(x, this.getDefaultDateFormat(), this.getLocale(), this.getTimeZone()); - } - - /* Helpers. */ - - public static Date getFlatDate(Date x) { - if (null == x) { - return x; - } - // - Calendar calendar = new GregorianCalendar(); - calendar.setTime(x); - calendar.set(Calendar.HOUR_OF_DAY, 0); - calendar.set(Calendar.MINUTE, 0); - calendar.set(Calendar.SECOND, 0); - calendar.set(Calendar.MILLISECOND, 0); - return calendar.getTime(); - } - - public static Date getCeilDate(Date x) { - if (null == x) { - return x; - } - // - Calendar calendar = new GregorianCalendar(); - calendar.setTime(x); - calendar.set(Calendar.HOUR_OF_DAY, 23); - calendar.set(Calendar.MINUTE, 59); - calendar.set(Calendar.SECOND, 59); - calendar.set(Calendar.MILLISECOND, 999); - return calendar.getTime(); - } - -} diff --git a/7.x/src/java/com/marcozanon/macaco/conversion/MInvalidConversionFormatException.java b/7.x/src/java/com/marcozanon/macaco/conversion/MInvalidConversionFormatException.java deleted file mode 100644 index 95179d3..0000000 --- a/7.x/src/java/com/marcozanon/macaco/conversion/MInvalidConversionFormatException.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * See LICENSE for details. - */ - -package com.marcozanon.macaco.conversion; - -@SuppressWarnings("serial") -public class MInvalidConversionFormatException extends MConversionException { - - /* */ - - public MInvalidConversionFormatException() { - super(); - } - - public MInvalidConversionFormatException(String message) { - super(message); - } - - public MInvalidConversionFormatException(Throwable error) { - super(error); - } - - public MInvalidConversionFormatException(String message, Throwable error) { - super(message, error); - } - -} diff --git a/7.x/src/java/com/marcozanon/macaco/conversion/MLocalDateConverter.java b/7.x/src/java/com/marcozanon/macaco/conversion/MLocalDateConverter.java deleted file mode 100644 index fc52143..0000000 --- a/7.x/src/java/com/marcozanon/macaco/conversion/MLocalDateConverter.java +++ /dev/null @@ -1,161 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * See LICENSE for details. - */ - -package com.marcozanon.macaco.conversion; - -import com.marcozanon.macaco.MObject; -import com.marcozanon.macaco.text.MText; -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; -import java.time.format.ResolverStyle; -import java.util.LinkedHashSet; -import java.util.Locale; - -public class MLocalDateConverter extends MObject { - - protected LinkedHashSet dateFormats = new LinkedHashSet(); - protected Locale locale = null; - - /* */ - - public MLocalDateConverter(String defaultDateFormat, Locale locale) { - super(); - // - this.addDateFormat(defaultDateFormat); - this.setLocale(locale); - } - - public MLocalDateConverter(LinkedHashSet dateFormats, Locale locale) { - super(); - // - this.setDateFormats(dateFormats); - this.setLocale(locale); - } - - @Override - public MLocalDateConverter clone() { - return new MLocalDateConverter(this.getDateFormats(), this.getLocale()); - } - - /* Date formats. */ - - public void setDateFormats(LinkedHashSet dateFormats) { - if (null == dateFormats) { - throw new IllegalArgumentException("Invalid 'dateFormats': null."); - } - else if (0 == dateFormats.size()) { - throw new IllegalArgumentException("Invalid 'dateFormats': empty."); - } - else { - for (String dateFormat: dateFormats) { - MLocalDateConverter.checkDateFormat(dateFormat); - } - } - // - synchronized (this.dateFormats) { - this.dateFormats = dateFormats; - } - } - - public void addDateFormat(String dateFormat) { - MLocalDateConverter.checkDateFormat(dateFormat); - // - synchronized (this.dateFormats) { - this.dateFormats.add(dateFormat); - } - } - - public LinkedHashSet getDateFormats() { - return this.dateFormats; - } - - public String getDefaultDateFormat() { - return this.getDateFormats().iterator().next(); - } - - /* Locale. */ - - protected void setLocale(Locale locale) { - if (null == locale) { - throw new IllegalArgumentException("Invalid 'locale': null."); - } - // - this.locale = locale; - } - - public Locale getLocale() { - return this.locale; - } - - /* Conversions. */ - - protected static void checkDateFormat(String dateFormat) { - if (MText.isBlank(dateFormat)) { - throw new IllegalArgumentException("Invalid 'dateFormat': null or empty."); - } - // - try { - DateTimeFormatter.ofPattern(dateFormat).withResolverStyle(ResolverStyle.STRICT); - } - catch (IllegalArgumentException exception) { - throw new IllegalArgumentException(String.format("Invalid 'dateFormat': %s.", dateFormat)); // no need to propagate exception - } - } - - protected static LocalDate getDateFromStringByParameters(String x, String inputDateFormat, Locale inputLocale) throws MInvalidConversionFormatException { - if (MText.isBlank(x)) { - throw new IllegalArgumentException("Invalid 'x': null or empty."); - } - MLocalDateConverter.checkDateFormat(inputDateFormat); - if (null == inputLocale) { - throw new IllegalArgumentException("Invalid 'inputLocale': null."); - } - // - LocalDate d = null; - try { - d = LocalDate.parse(x, DateTimeFormatter.ofPattern(inputDateFormat, inputLocale).withResolverStyle(ResolverStyle.STRICT)); - } - catch (DateTimeParseException exception) { - throw new MInvalidConversionFormatException(String.format("Invalid 'x' or parsing: %s (input format: %s).", x, inputDateFormat)); // no need to propagate exception - } - // - return d; - } - - protected static String getStringFromDateByParameters(LocalDate date, String outputDateFormat, Locale outputLocale) { - if (null == date) { - throw new IllegalArgumentException("Invalid 'date': null."); - } - MLocalDateConverter.checkDateFormat(outputDateFormat); - if (null == outputLocale) { - throw new IllegalArgumentException("Invalid 'outputLocale': null."); - } - // - return date.format(DateTimeFormatter.ofPattern(outputDateFormat, outputLocale).withResolverStyle(ResolverStyle.STRICT)); - } - - public LocalDate getDateFromString(String x) throws MInvalidConversionFormatException { - LocalDate y = null; - for (String dateFormat: this.getDateFormats()) { - try { - y = MLocalDateConverter.getDateFromStringByParameters(x, dateFormat, this.getLocale()); - return y; - } - catch (MInvalidConversionFormatException exception) { - } - } - if (null == y) { - throw new MInvalidConversionFormatException(String.format("Invalid 'x': %s.", x)); - } - return y; // necessary to avoid Java compilation errors - } - - public String getStringFromDate(LocalDate x) { - return MLocalDateConverter.getStringFromDateByParameters(x, this.getDefaultDateFormat(), this.getLocale()); - } - -} diff --git a/7.x/src/java/com/marcozanon/macaco/conversion/MLocalDateTimeConverter.java b/7.x/src/java/com/marcozanon/macaco/conversion/MLocalDateTimeConverter.java deleted file mode 100644 index fdfa86c..0000000 --- a/7.x/src/java/com/marcozanon/macaco/conversion/MLocalDateTimeConverter.java +++ /dev/null @@ -1,161 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * See LICENSE for details. - */ - -package com.marcozanon.macaco.conversion; - -import com.marcozanon.macaco.MObject; -import com.marcozanon.macaco.text.MText; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; -import java.time.format.ResolverStyle; -import java.util.LinkedHashSet; -import java.util.Locale; - -public class MLocalDateTimeConverter extends MObject { - - protected LinkedHashSet datetimeFormats = new LinkedHashSet(); - protected Locale locale = null; - - /* */ - - public MLocalDateTimeConverter(String defaultDatetimeFormat, Locale locale) { - super(); - // - this.addDatetimeFormat(defaultDatetimeFormat); - this.setLocale(locale); - } - - public MLocalDateTimeConverter(LinkedHashSet datetimeFormats, Locale locale) { - super(); - // - this.setDatetimeFormats(datetimeFormats); - this.setLocale(locale); - } - - @Override - public MLocalDateTimeConverter clone() { - return new MLocalDateTimeConverter(this.getDatetimeFormats(), this.getLocale()); - } - - /* Datetime formats. */ - - public void setDatetimeFormats(LinkedHashSet datetimeFormats) { - if (null == datetimeFormats) { - throw new IllegalArgumentException("Invalid 'datetimeFormats': null."); - } - else if (0 == datetimeFormats.size()) { - throw new IllegalArgumentException("Invalid 'datetimeFormats': empty."); - } - else { - for (String datetimeFormat: datetimeFormats) { - MLocalDateTimeConverter.checkDatetimeFormat(datetimeFormat); - } - } - // - synchronized (this.datetimeFormats) { - this.datetimeFormats = datetimeFormats; - } - } - - public void addDatetimeFormat(String datetimeFormat) { - MLocalDateTimeConverter.checkDatetimeFormat(datetimeFormat); - // - synchronized (this.datetimeFormats) { - this.datetimeFormats.add(datetimeFormat); - } - } - - public LinkedHashSet getDatetimeFormats() { - return this.datetimeFormats; - } - - public String getDefaultDatetimeFormat() { - return this.getDatetimeFormats().iterator().next(); - } - - /* Locale. */ - - protected void setLocale(Locale locale) { - if (null == locale) { - throw new IllegalArgumentException("Invalid 'locale': null."); - } - // - this.locale = locale; - } - - public Locale getLocale() { - return this.locale; - } - - /* Conversions. */ - - protected static void checkDatetimeFormat(String datetimeFormat) { - if (MText.isBlank(datetimeFormat)) { - throw new IllegalArgumentException("Invalid 'datetimeFormat': null or empty."); - } - // - try { - DateTimeFormatter.ofPattern(datetimeFormat).withResolverStyle(ResolverStyle.STRICT); - } - catch (IllegalArgumentException exception) { - throw new IllegalArgumentException(String.format("Invalid 'datetimeFormat': %s.", datetimeFormat)); // no need to propagate exception - } - } - - protected static LocalDateTime getDatetimeFromStringByParameters(String x, String inputDatetimeFormat, Locale inputLocale) throws MInvalidConversionFormatException { - if (MText.isBlank(x)) { - throw new IllegalArgumentException("Invalid 'x': null or empty."); - } - MLocalDateTimeConverter.checkDatetimeFormat(inputDatetimeFormat); - if (null == inputLocale) { - throw new IllegalArgumentException("Invalid 'inputLocale': null."); - } - // - LocalDateTime dt = null; - try { - dt = LocalDateTime.parse(x, DateTimeFormatter.ofPattern(inputDatetimeFormat, inputLocale).withResolverStyle(ResolverStyle.STRICT)); - } - catch (DateTimeParseException exception) { - throw new MInvalidConversionFormatException(String.format("Invalid 'x' or parsing: %s (input format: %s).", x, inputDatetimeFormat)); // no need to propagate exception - } - // - return dt; - } - - protected static String getStringFromDatetimeByParameters(LocalDateTime datetime, String outputDatetimeFormat, Locale outputLocale) { - if (null == datetime) { - throw new IllegalArgumentException("Invalid 'datetime': null."); - } - MLocalDateTimeConverter.checkDatetimeFormat(outputDatetimeFormat); - if (null == outputLocale) { - throw new IllegalArgumentException("Invalid 'outputLocale': null."); - } - // - return datetime.format(DateTimeFormatter.ofPattern(outputDatetimeFormat, outputLocale).withResolverStyle(ResolverStyle.STRICT)); - } - - public LocalDateTime getDatetimeFromString(String x) throws MInvalidConversionFormatException { - LocalDateTime y = null; - for (String datetimeFormat: this.getDatetimeFormats()) { - try { - y = MLocalDateTimeConverter.getDatetimeFromStringByParameters(x, datetimeFormat, this.getLocale()); - return y; - } - catch (MInvalidConversionFormatException exception) { - } - } - if (null == y) { - throw new MInvalidConversionFormatException(String.format("Invalid 'x': %s.", x)); - } - return y; // necessary to avoid Java compilation errors - } - - public String getStringFromDatetime(LocalDateTime x) { - return MLocalDateTimeConverter.getStringFromDatetimeByParameters(x, this.getDefaultDatetimeFormat(), this.getLocale()); - } - -} diff --git a/7.x/src/java/com/marcozanon/macaco/conversion/MNumberConverter.java b/7.x/src/java/com/marcozanon/macaco/conversion/MNumberConverter.java deleted file mode 100644 index 5543c6c..0000000 --- a/7.x/src/java/com/marcozanon/macaco/conversion/MNumberConverter.java +++ /dev/null @@ -1,165 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * See LICENSE for details. - */ - -package com.marcozanon.macaco.conversion; - -import com.marcozanon.macaco.MObject; -import com.marcozanon.macaco.text.MText; -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; -import java.text.ParsePosition; -import java.util.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); - } - - @Override - 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 if (0 == numberFormats.size()) { - throw new IllegalArgumentException("Invalid 'numberFormats': empty."); - } - else { - for (String numberFormat: numberFormats) { - MNumberConverter.checkNumberFormat(numberFormat); - } - } - // - synchronized (this.numberFormats) { - this.numberFormats = numberFormats; - } - } - - public void addNumberFormat(String numberFormat) { - MNumberConverter.checkNumberFormat(numberFormat); - // - synchronized (this.numberFormats) { - this.numberFormats.add(numberFormat); - } - } - - public LinkedHashSet getNumberFormats() { - return this.numberFormats; - } - - public String getDefaultNumberFormat() { - return this.getNumberFormats().iterator().next(); - } - - /* Locale. */ - - protected void setLocale(Locale locale) { - if (null == locale) { - throw new IllegalArgumentException("Invalid 'locale': null."); - } - // - this.locale = locale; - } - - public Locale getLocale() { - return this.locale; - } - - /* Conversions. */ - - protected static void checkNumberFormat(String numberFormat) { - if (MText.isBlank(numberFormat)) { - throw new IllegalArgumentException("Invalid 'numberFormat': null or empty."); - } - // - try { - DecimalFormat testNumberFormat = new DecimalFormat(numberFormat); - } - catch (IllegalArgumentException exception) { - throw new IllegalArgumentException(String.format("Invalid 'numberFormat': %s.", numberFormat)); // no need to propagate exception - } - } - - protected static BigDecimal getNumberFromStringByParameters(String x, String inputNumberFormat, Locale inputLocale) throws MInvalidConversionFormatException { - if (MText.isBlank(x)) { - throw new IllegalArgumentException("Invalid 'x': null or empty."); - } - MNumberConverter.checkNumberFormat(inputNumberFormat); - if (null == inputLocale) { - throw new IllegalArgumentException("Invalid 'inputLocale': null."); - } - // - DecimalFormatSymbols dfs = new DecimalFormatSymbols(inputLocale); - DecimalFormat df = new DecimalFormat(inputNumberFormat, dfs); - df.setParseBigDecimal(true); - ParsePosition validPosition = new ParsePosition(0); - BigDecimal bd = (BigDecimal)df.parse(x, validPosition); - if (validPosition.getIndex() < x.length()) { - throw new MInvalidConversionFormatException(String.format("Invalid 'x' or parsing: %s (input format: %s).", x, inputNumberFormat)); - } - return bd; - } - - protected static String getStringFromNumberByParameters(BigDecimal number, String outputNumberFormat, Locale outputLocale) { - if (null == number) { - throw new IllegalArgumentException("Invalid 'number': null."); - } - MNumberConverter.checkNumberFormat(outputNumberFormat); - if (null == outputLocale) { - throw new IllegalArgumentException("Invalid 'outputLocale': null."); - } - // - DecimalFormatSymbols dfs = new DecimalFormatSymbols(outputLocale); - DecimalFormat df = new DecimalFormat(outputNumberFormat, dfs); - df.setRoundingMode(RoundingMode.HALF_UP); - return df.format(number); - } - - public BigDecimal getNumberFromString(String x) throws MInvalidConversionFormatException { - BigDecimal y = null; - for (String numberFormat: this.getNumberFormats()) { - try { - y = MNumberConverter.getNumberFromStringByParameters(x, numberFormat, this.getLocale()); - return y; - } - catch (MInvalidConversionFormatException exception) { - } - } - if (null == y) { - throw new MInvalidConversionFormatException(String.format("Invalid 'x': %s.", x)); - } - return y; // necessary to avoid Java compilation errors - } - - public String getStringFromNumber(BigDecimal x) { - return MNumberConverter.getStringFromNumberByParameters(x, this.getDefaultNumberFormat(), this.getLocale()); - } - -} diff --git a/7.x/src/java/com/marcozanon/macaco/database/MDatabaseConnection.java b/7.x/src/java/com/marcozanon/macaco/database/MDatabaseConnection.java deleted file mode 100644 index 9aa684d..0000000 --- a/7.x/src/java/com/marcozanon/macaco/database/MDatabaseConnection.java +++ /dev/null @@ -1,342 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * See LICENSE for details. - */ - -package com.marcozanon.macaco.database; - -import com.marcozanon.macaco.MObject; -import com.marcozanon.macaco.logging.MLogListener; -import com.marcozanon.macaco.text.MText; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.util.LinkedHashMap; -import java.util.LinkedList; - -public class MDatabaseConnection extends MObject { - - public static enum TransactionStatus { - CLOSED, - SUCCESSFUL, - FAILED - }; - - protected String driver = null; - protected String url = null; - protected String username = null; - protected String password = null; - protected boolean localTypesMode = false; - protected MLogListener logListener = null; - - protected Connection connection = null; - - protected MDatabaseConnection.TransactionStatus transactionStatus = MDatabaseConnection.TransactionStatus.CLOSED; - - /* */ - - public MDatabaseConnection(String driver, String url, String username, String password, boolean localTypesMode, MLogListener logListener) throws MDatabaseConnectionFailureException { - super(); - // - if (MText.isBlank(driver)) { - throw new IllegalArgumentException("Invalid 'driver': null or empty."); - } - if (MText.isBlank(url)) { - throw new IllegalArgumentException("Invalid 'url': null or empty."); - } - if (null == username) { - throw new IllegalArgumentException("Invalid 'username': null."); - } - if (null == password) { - throw new IllegalArgumentException("Invalid 'password': null."); - } - // - this.driver = driver; - this.url = url; - this.username = username; - this.password = password; - this.localTypesMode = localTypesMode; - this.setLogListener(logListener); - // Load driver. - try { - Class.forName(this.getDriver()).newInstance(); - } - catch (ClassNotFoundException exception) { - throw new MDatabaseConnectionFailureException("Could not load driver.", exception); - } - catch (IllegalAccessException exception) { - throw new MDatabaseConnectionFailureException("Could not load driver.", exception); - } - catch (InstantiationException exception) { - throw new MDatabaseConnectionFailureException("Could not load driver.", exception); - } - // Initialize the connection. - this.initialize(); - } - - public void initialize() throws MDatabaseConnectionFailureException { - // Establish a connection. - try { - this.connection = DriverManager.getConnection(this.getUrl(), this.getUsername(), this.getPassword()); - } - catch (SQLException exception) { - throw new MDatabaseConnectionFailureException("Could not get database connection.", exception); - } - // Set SQL mode to standard ANSI and TRADITIONAL. - try { - this.getConnection().createStatement().executeUpdate("SET SQL_MODE = 'ANSI,TRADITIONAL'"); -/* Disabled to prevent an infinite loop. - this.logStatement("### SET SQL_MODE = 'ANSI,TRADITIONAL' ###"); -*/ - } - catch (SQLException exception) { - throw new MDatabaseConnectionFailureException("Could not set SQL mode to ANSI and TRADITIONAL.", exception); - } - } - - protected void check() throws MDatabaseConnectionFailureException { - try { - this.getConnection().createStatement().executeQuery("/* ping */ SELECT 1"); - } - catch (SQLException exception) { - this.initialize(); - } - } - - public void close() throws MDatabaseConnectionFailureException { - try { - this.getConnection().close(); - } - catch (SQLException exception) { - throw new MDatabaseConnectionFailureException("Could not close database connection.", exception); - } - } - - public boolean isClosed() throws MDatabaseConnectionFailureException { - try { - return this.getConnection().isClosed(); - } - catch (SQLException exception) { - throw new MDatabaseConnectionFailureException("Could not determine the state.", exception); - } - } - - @Override - 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; - } - - /* Local types mode. */ - - public boolean getLocalTypesMode() { - return this.localTypesMode; - } - - /* Connection. */ - - protected Connection getConnection() { - return this.connection; - } - - /* Transactions. */ - - protected void setTransactionStatus(MDatabaseConnection.TransactionStatus transactionStatus) { - if (null == transactionStatus) { - throw new IllegalArgumentException("Invalid 'transactionStatus': null."); - } - // - this.transactionStatus = transactionStatus; - } - - public MDatabaseConnection.TransactionStatus getTransactionStatus() { - return this.transactionStatus; - } - - public void startTransaction() throws MSqlTransactionException { - if (MDatabaseConnection.TransactionStatus.CLOSED != this.getTransactionStatus()) { - throw new MSqlTransactionException("Nested transactions not allowed."); - } - // - try { - // Check the connection. - this.check(); - // Start the transaction. - this.getConnection().setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); - this.getConnection().setAutoCommit(false); - this.setTransactionStatus(MDatabaseConnection.TransactionStatus.SUCCESSFUL); - this.logStatement("### BEGIN TRANSACTION ###"); - } - catch (MDatabaseConnectionFailureException exception) { - throw new MSqlTransactionException("Could not start transaction.", exception); - } - catch (SQLException exception) { - throw new MSqlTransactionException("Could not start transaction.", exception); - } - } - - public void rollBackTransaction() throws MSqlTransactionException { - if (MDatabaseConnection.TransactionStatus.CLOSED == this.getTransactionStatus()) { - throw new MSqlTransactionException("Transaction not started."); - } - // - try { -/* - // Check the connection. - this.check(); -*/ - // Cancel the transaction. - this.getConnection().rollback(); - this.getConnection().setAutoCommit(true); - this.setTransactionStatus(MDatabaseConnection.TransactionStatus.CLOSED); - this.logStatement("### ROLLBACK ###"); - } - catch (SQLException exception) { - this.setTransactionStatus(MDatabaseConnection.TransactionStatus.FAILED); - throw new MSqlTransactionException("Could not roll back transaction.", exception); - } - } - - public MDatabaseConnection.TransactionStatus commitTransaction() throws MSqlTransactionException { - switch (this.getTransactionStatus()) { - case SUCCESSFUL: - try { -/* - // Check the connection. - this.check(); -*/ - // Commit the transaction. - this.getConnection().commit(); - this.getConnection().setAutoCommit(true); - this.setTransactionStatus(MDatabaseConnection.TransactionStatus.CLOSED); - this.logStatement("### COMMIT ###"); - return MDatabaseConnection.TransactionStatus.SUCCESSFUL; - } - catch (SQLException exception) { - this.rollBackTransaction(); - return MDatabaseConnection.TransactionStatus.FAILED; - } - case FAILED: - this.rollBackTransaction(); - return MDatabaseConnection.TransactionStatus.FAILED; - default: // instead of "case CLOSED:" (necessary to avoid Java compilation errors) - throw new MSqlTransactionException("Transaction not started."); - } - } - - /* Statements. */ - - public MSqlStatementResults executePreparedStatement(String statement) throws MDatabaseConnectionFailureException, MSqlStatementException { - return this.executePreparedStatement(statement, new LinkedList()); - } - - public MSqlStatementResults executePreparedStatement(String statement, LinkedList parameters) throws MDatabaseConnectionFailureException, MSqlStatementException { - return this.executePreparedStatement(statement, parameters, this.getLocalTypesMode()); - } - - public MSqlStatementResults executePreparedStatement(String statement, LinkedList parameters, boolean localTypesMode) throws MDatabaseConnectionFailureException, MSqlStatementException { - return this.executePreparedStatement(statement, parameters, this.getLocalTypesMode(), true); - }; - - public MSqlStatementResults executePreparedStatement(String statement, LinkedList parameters, boolean localTypesMode, boolean loggableStatement) throws MDatabaseConnectionFailureException, MSqlStatementException { - if (MText.isBlank(statement)) { - throw new IllegalArgumentException("Invalid 'statement': null or empty."); - } - if (null == parameters) { - throw new IllegalArgumentException("Invalid 'parameters': null."); - } - // - MSqlStatementResults results = null; - PreparedStatement preparedStatement = null; - try { - // Check the connection. - this.check(); - // Prepare the statement. - preparedStatement = this.getConnection().prepareStatement(statement, PreparedStatement.RETURN_GENERATED_KEYS); - for (int p = 0; parameters.size() > p; p++) { - preparedStatement.setObject(p + 1, parameters.get(p)); - } - // Execute the statement and parse the results. - preparedStatement.execute(); - results = new MSqlStatementResults(preparedStatement, localTypesMode); - if (loggableStatement) { - this.logStatement(preparedStatement.toString()); - } - } - catch (MDatabaseConnectionFailureException exception) { - if (MDatabaseConnection.TransactionStatus.SUCCESSFUL == this.getTransactionStatus()) { - this.setTransactionStatus(MDatabaseConnection.TransactionStatus.FAILED); - } - throw new MSqlStatementException(String.format("Could not execute prepared statement: %s.", preparedStatement), exception); - } - catch (SQLException exception) { - if (MDatabaseConnection.TransactionStatus.SUCCESSFUL == this.getTransactionStatus()) { - this.setTransactionStatus(MDatabaseConnection.TransactionStatus.FAILED); - } - throw new MSqlStatementException(String.format("Could not execute prepared statement: %s.", preparedStatement), exception); - } - return results; - } - - /* Tables. */ - - public MSqlTable getTable(String table, String primaryKey) { - return new MSqlTable(this, table, primaryKey); - } - - /* Logging. */ - - public void setLogListener(MLogListener logListener) { - this.logListener = logListener; - } - - protected MLogListener getLogListener() { - return this.logListener; - } - - protected void logStatement(String statement) { - MLogListener logListener = this.getLogListener(); - if (null != logListener) { - logListener.onMessageLogging(statement); - } - } - - /* Engine version. */ - - public String getEngineVersion() throws MDatabaseConnectionFailureException, MSqlStatementException { - MSqlStatementResults results = this.executePreparedStatement("SELECT VERSION()"); - LinkedList> resultList = results.getRecords(); - // - return (String)resultList.get(0).get("VERSION()"); - } - -} diff --git a/7.x/src/java/com/marcozanon/macaco/database/MDatabaseConnectionFailureException.java b/7.x/src/java/com/marcozanon/macaco/database/MDatabaseConnectionFailureException.java deleted file mode 100644 index d96029a..0000000 --- a/7.x/src/java/com/marcozanon/macaco/database/MDatabaseConnectionFailureException.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * See LICENSE for details. - */ - -package com.marcozanon.macaco.database; - -@SuppressWarnings("serial") -public class MDatabaseConnectionFailureException extends MDatabaseException { - - /* */ - - public MDatabaseConnectionFailureException() { - super(); - } - - public MDatabaseConnectionFailureException(String message) { - super(message); - } - - public MDatabaseConnectionFailureException(Throwable error) { - super(error); - } - - public MDatabaseConnectionFailureException(String message, Throwable error) { - super(message, error); - } - -} diff --git a/7.x/src/java/com/marcozanon/macaco/database/MDatabaseConnectionGenerator.java b/7.x/src/java/com/marcozanon/macaco/database/MDatabaseConnectionGenerator.java deleted file mode 100644 index 0dd2a69..0000000 --- a/7.x/src/java/com/marcozanon/macaco/database/MDatabaseConnectionGenerator.java +++ /dev/null @@ -1,121 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * See LICENSE for details. - */ - -package com.marcozanon.macaco.database; - -import com.marcozanon.macaco.MObject; -import com.marcozanon.macaco.logging.MLogListener; -import com.marcozanon.macaco.text.MText; - -public class MDatabaseConnectionGenerator extends MObject { - - protected String driver = null; - protected String url = null; - protected String username = null; - protected String password = null; - protected boolean localTypesMode = false; - protected MLogListener logListener = null; - - /* */ - - public MDatabaseConnectionGenerator(String driver, String url, String username, String password, boolean localTypesMode, MLogListener logListener) { - super(); - // - if (MText.isBlank(driver)) { - throw new IllegalArgumentException("Invalid 'driver': null or empty."); - } - if (MText.isBlank(url)) { - throw new IllegalArgumentException("Invalid 'url': null or empty."); - } - if (null == username) { - throw new IllegalArgumentException("Invalid 'username': null."); - } - if (null == password) { - throw new IllegalArgumentException("Invalid 'password': null."); - } - // - this.driver = driver; - this.url = url; - this.username = username; - this.password = password; - this.localTypesMode = localTypesMode; - this.setLogListener(logListener); - } - - /* Driver. */ - - public String getDriver() { - return this.driver; - } - - /* Url. */ - - public String getUrl() { - return this.url; - } - - /* Username. */ - - public String getUsername() { - return this.username; - } - - /* Password. */ - - public String getPassword() { - return this.password; - } - - /* Local types mode. */ - - public boolean getLocalTypesMode() { - return this.localTypesMode; - } - - /* Log listener. */ - - public MLogListener getLogListener() { - return this.logListener; - } - - public void setLogListener(MLogListener logListener) { - this.logListener = logListener; - } - - /* Generator. */ - - public MDatabaseConnection getNewDatabaseConnection() throws MDatabaseConnectionFailureException { - return new MDatabaseConnection(this.getDriver(), this.getUrl(), this.getUsername(), this.getPassword(), this.getLocalTypesMode(), this.getLogListener()); - } - - public boolean isGeneratorFor(MDatabaseConnection databaseConnection) { - if (null == databaseConnection) { - throw new IllegalArgumentException("Invalid 'databaseConnection': null."); - } - // - if (!databaseConnection.getDriver().equals(this.getDriver())) { - return false; - } - if (!databaseConnection.getUrl().equals(this.getUrl())) { - return false; - } - if (!databaseConnection.getUsername().equals(this.getUsername())) { - return false; - } - if (!databaseConnection.getPassword().equals(this.getPassword())) { - return false; - } - if (databaseConnection.getLocalTypesMode() != this.getLocalTypesMode()) { - return false; - } - if (databaseConnection.getLogListener() != this.getLogListener()) { - return false; - } - // - return true; - } - -} diff --git a/7.x/src/java/com/marcozanon/macaco/database/MDatabaseConnectionPool.java b/7.x/src/java/com/marcozanon/macaco/database/MDatabaseConnectionPool.java deleted file mode 100644 index 40fb91e..0000000 --- a/7.x/src/java/com/marcozanon/macaco/database/MDatabaseConnectionPool.java +++ /dev/null @@ -1,132 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * See LICENSE for details. - */ - -package com.marcozanon.macaco.database; - -import com.marcozanon.macaco.MObject; -import com.marcozanon.macaco.logging.MLogListener; -import java.util.LinkedList; - -public class MDatabaseConnectionPool extends MObject { - - protected MDatabaseConnectionGenerator databaseConnectionGenerator = null; - - protected LinkedList databaseConnections = new LinkedList(); - protected int minimumSize = 0; - protected int maximumSize = 0; - - /* */ - - public MDatabaseConnectionPool(String driver, String url, String username, String password, boolean localTypesMode, int minimumSize, int maximumSize, MLogListener logListener) throws MDatabaseConnectionFailureException { - super(); - // - if (0 > minimumSize) { - throw new IllegalArgumentException(String.format("Invalid 'minimumSize': %s.", minimumSize)); - } - if (1 > maximumSize) { - throw new IllegalArgumentException(String.format("Invalid 'maximumSize': %s.", maximumSize)); - } - else if (minimumSize > maximumSize) { - throw new IllegalArgumentException(String.format("Invalid 'maximumSize': %s < %s ('minimumSize').", maximumSize, minimumSize)); - } - // - this.databaseConnectionGenerator = new MDatabaseConnectionGenerator(driver, url, username, password, localTypesMode, logListener); - this.minimumSize = minimumSize; - this.maximumSize = maximumSize; - // - this.initialize(); - } - - protected void initialize() throws MDatabaseConnectionFailureException { - for (int c = 0; c < this.getMinimumSize(); c++) { - MDatabaseConnection databaseConnection = this.getDatabaseConnectionGenerator().getNewDatabaseConnection(); - this.pushDatabaseConnection(databaseConnection); - } - } - - protected void closeConnections() { - LinkedList databaseConnections = this.getDatabaseConnections(); - while (0 < databaseConnections.size()) { - MDatabaseConnection databaseConnection = databaseConnections.removeLast(); - // - try { - databaseConnection.close(); - } - catch (MDatabaseConnectionFailureException exception) { - } - } - } - - @Override - protected void finalize() { - this.closeConnections(); - } - - /* Database connection generator. */ - - protected MDatabaseConnectionGenerator getDatabaseConnectionGenerator() { - return this.databaseConnectionGenerator; - } - - /* Database connections. */ - - protected LinkedList getDatabaseConnections() { - return this.databaseConnections; - } - - protected int getMinimumSize() { - return this.minimumSize; - } - - protected int getMaximumSize() { - return this.maximumSize; - } - - public synchronized MDatabaseConnection popDatabaseConnection() throws MDatabaseConnectionFailureException { - LinkedList databaseConnections = this.getDatabaseConnections(); - MDatabaseConnection databaseConnection = null; - if (0 == databaseConnections.size()) { - databaseConnection = this.getDatabaseConnectionGenerator().getNewDatabaseConnection(); - } - else { - databaseConnection = databaseConnections.removeLast(); - } - if (this.getMinimumSize() > databaseConnections.size()) { - databaseConnections.add(this.getDatabaseConnectionGenerator().getNewDatabaseConnection()); - } - return databaseConnection; - } - - public synchronized void pushDatabaseConnection(MDatabaseConnection databaseConnection) throws MDatabaseConnectionFailureException { - if (null == databaseConnection) { - throw new IllegalArgumentException("Invalid 'databaseConnection': null."); - } - else if (databaseConnection.isClosed()) { - throw new IllegalArgumentException("Invalid 'databaseConnection': closed."); - } - // - if (!this.getDatabaseConnectionGenerator().isGeneratorFor(databaseConnection)) { - databaseConnection.close(); - } - else { - LinkedList databaseConnections = this.getDatabaseConnections(); - if (this.getMaximumSize() >= databaseConnections.size()) { - databaseConnections.add(databaseConnection); - } - } - } - - /* Logging. */ - - public synchronized void setLogListener(MLogListener logListener) { - this.getDatabaseConnectionGenerator().setLogListener(logListener); - // - for (MDatabaseConnection databaseConnection: this.getDatabaseConnections()) { - databaseConnection.setLogListener(logListener); - } - } - -} diff --git a/7.x/src/java/com/marcozanon/macaco/database/MDatabaseException.java b/7.x/src/java/com/marcozanon/macaco/database/MDatabaseException.java deleted file mode 100644 index 2637efe..0000000 --- a/7.x/src/java/com/marcozanon/macaco/database/MDatabaseException.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * See LICENSE for details. - */ - -package com.marcozanon.macaco.database; - -import com.marcozanon.macaco.MException; - -public abstract class MDatabaseException extends MException { - - /* */ - - public MDatabaseException() { - super(); - } - - public MDatabaseException(String message) { - super(message); - } - - public MDatabaseException(Throwable error) { - super(error); - } - - public MDatabaseException(String message, Throwable error) { - super(message, error); - } - -} diff --git a/7.x/src/java/com/marcozanon/macaco/database/MSqlStatementException.java b/7.x/src/java/com/marcozanon/macaco/database/MSqlStatementException.java deleted file mode 100644 index ff2e4e2..0000000 --- a/7.x/src/java/com/marcozanon/macaco/database/MSqlStatementException.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * See LICENSE for details. - */ - -package com.marcozanon.macaco.database; - -@SuppressWarnings("serial") -public class MSqlStatementException extends MDatabaseException { - - /* */ - - public MSqlStatementException() { - super(); - } - - public MSqlStatementException(String message) { - super(message); - } - - public MSqlStatementException(Throwable error) { - super(error); - } - - public MSqlStatementException(String message, Throwable error) { - super(message, error); - } - -} diff --git a/7.x/src/java/com/marcozanon/macaco/database/MSqlStatementResults.java b/7.x/src/java/com/marcozanon/macaco/database/MSqlStatementResults.java deleted file mode 100644 index 3322c73..0000000 --- a/7.x/src/java/com/marcozanon/macaco/database/MSqlStatementResults.java +++ /dev/null @@ -1,149 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * See LICENSE for details. - */ - -package com.marcozanon.macaco.database; - -import com.marcozanon.macaco.MObject; -import com.marcozanon.macaco.text.MText; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.ResultSetMetaData; -import java.sql.SQLException; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.LinkedList; - -public class MSqlStatementResults extends MObject { - - protected PreparedStatement preparedStatement = null; - protected boolean localTypesMode = false; - - protected ResultSet resultSet = null; - - protected LinkedList> records = new LinkedList>(); - - protected LinkedHashSet generatedKeys = new LinkedHashSet(); - - /* */ - - public MSqlStatementResults(PreparedStatement preparedStatement, boolean localTypesMode) throws MSqlStatementException { - super(); - // - if (null == preparedStatement) { - throw new IllegalArgumentException("Invalid 'preparedStatement': null."); - } - // - this.preparedStatement = preparedStatement; - this.localTypesMode = localTypesMode; - // - try { - this.resultSet = this.getPreparedStatement().getResultSet(); - if (null != this.getResultSet()) { - while (this.getResultSet().next()) { - ResultSetMetaData resultSetMetaData = this.getResultSet().getMetaData(); - LinkedHashMap record = new LinkedHashMap(); - // - for (int f = 0; resultSetMetaData.getColumnCount() > f; f++) { - String field = resultSetMetaData.getColumnLabel(f + 1); - Object value = this.getResultSet().getObject(field); - // - if ((value instanceof java.sql.Date) && (this.getLocalTypesMode())) { - value = ((java.sql.Date)value).toLocalDate(); - } - else if ((value instanceof java.sql.Time) && (this.getLocalTypesMode())) { - value = ((java.sql.Time)value).toLocalTime(); - } - else if ((value instanceof java.sql.Timestamp) && (this.getLocalTypesMode())) { - value = ((java.sql.Timestamp)value).toLocalDateTime(); - } - // - record.put(field, value); - } - // - this.getRecords().add(record); - } - } - } - catch (SQLException exception) { - throw new MSqlStatementException("Could not retrieve statement records.", exception); - } - // - try { - ResultSet generatedKeysResultSet = this.getPreparedStatement().getGeneratedKeys(); - while (generatedKeysResultSet.next()) { - this.getGeneratedKeys().add(generatedKeysResultSet.getObject(1)); - } - generatedKeysResultSet.close(); - } - catch (SQLException exception) { - throw new MSqlStatementException("Could not retrieve generated keys.", exception); - } - } - - public void close() throws MDatabaseConnectionFailureException { - try { - if (null != this.getResultSet()) { - this.getResultSet().close(); - } - this.getPreparedStatement().close(); - } - catch (SQLException exception) { - throw new MDatabaseConnectionFailureException("Could not close statement and/or result set references.", exception); - } - } - - @Override - protected void finalize() { - try { - this.close(); - } - catch (Exception exception) { - } - } - - /* References. */ - - protected PreparedStatement getPreparedStatement() { - return this.preparedStatement; - } - - public ResultSet getResultSet() { - return this.resultSet; - } - - public boolean getLocalTypesMode() { - return this.localTypesMode; - } - - /* Records. */ - - public LinkedList> getRecords() { - return this.records; - } - - 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.getRecords()) { - Object r = record.get(field); - if (null == r) { - throw new IllegalArgumentException(String.format("Invalid 'field': %s.", field)); - } - recordsByField.add(r); - } - return recordsByField; - } - - /* Generated keys. */ - - public LinkedHashSet getGeneratedKeys() { - return this.generatedKeys; - } - -} diff --git a/7.x/src/java/com/marcozanon/macaco/database/MSqlTable.java b/7.x/src/java/com/marcozanon/macaco/database/MSqlTable.java deleted file mode 100644 index cfdf380..0000000 --- a/7.x/src/java/com/marcozanon/macaco/database/MSqlTable.java +++ /dev/null @@ -1,172 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * See LICENSE for details. - */ - -package com.marcozanon.macaco.database; - -import com.marcozanon.macaco.MObject; -import com.marcozanon.macaco.text.MText; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.StringJoiner; - -public class MSqlTable extends MObject { - - protected MDatabaseConnection databaseConnection = null; - protected String table = null; - protected String primaryKey = null; - - /* */ - - public MSqlTable(MDatabaseConnection databaseConnection, String table, String primaryKey) { - super(); - // - if (null == databaseConnection) { - throw new IllegalArgumentException("Invalid 'databaseConnection': null."); - } - if (MText.isBlank(table)) { - throw new IllegalArgumentException("Invalid 'table': null or empty."); - } - if (MText.isBlank(primaryKey)) { - throw new IllegalArgumentException("Invalid 'primaryKey': null or empty."); - } - // - this.databaseConnection = databaseConnection; - this.table = table; - this.primaryKey = primaryKey; - } - - /* Database connection. */ - - protected MDatabaseConnection getDatabaseConnection() { - return this.databaseConnection; - } - - /* Table. */ - - public String getTable() { - return this.table; - } - - /* Primary key. */ - - public String getPrimaryKey() { - return this.primaryKey; - } - - /* Statements. */ - - protected MSqlStatementResults insertRecord(LinkedHashMap map, boolean loggableStatement) throws MDatabaseConnectionFailureException, MSqlStatementException { - 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.getDatabaseConnection().executePreparedStatement(" INSERT INTO \"" + this.getTable() + "\"" - + " (" + fields + ")" - + " VALUES (" + s + ")", - p, true, loggableStatement); - } - - protected MSqlStatementResults updateRecord(LinkedHashMap map, Object id, boolean loggableStatement) throws MDatabaseConnectionFailureException, MSqlStatementException { - 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.getDatabaseConnection().executePreparedStatement(" UPDATE \"" + this.getTable() + "\"" - + " SET " + s - + " WHERE (\"" + this.getTable() + "\".\"" + this.getPrimaryKey() + "\" = ?)", - p, true, loggableStatement); - } - - public MSqlStatementResults setRecord(LinkedHashMap map, Object id) throws MDatabaseConnectionFailureException, MSqlStatementException { - return this.setRecord(map, id, true); - } - - public MSqlStatementResults setRecord(LinkedHashMap map, Object id, boolean loggableStatement) throws MDatabaseConnectionFailureException, MSqlStatementException { - if (null == id) { - return this.insertRecord(map, loggableStatement); - } - else { - return this.updateRecord(map, id, loggableStatement); - } - } - - public MSqlStatementResults getRecord(Object id) throws MDatabaseConnectionFailureException, MSqlStatementException { - if (null == id) { - throw new IllegalArgumentException("Invalid 'id': null."); - } - // - LinkedList p = new LinkedList(); - p.add(id); - // - return this.getDatabaseConnection().executePreparedStatement(" SELECT *" - + " FROM \"" + this.getTable() + "\"" - + " WHERE (\"" + this.getTable() + "\".\"" + this.getPrimaryKey() + "\" = ?)", - p); - } - - public MSqlStatementResults deleteRecord(Object id) throws MDatabaseConnectionFailureException, MSqlStatementException { - if (null == id) { - throw new IllegalArgumentException("Invalid 'id': null."); - } - // - LinkedList p = new LinkedList(); - p.add(id); - // - return this.getDatabaseConnection().executePreparedStatement(" DELETE FROM \"" + this.getTable() + "\"" - + " WHERE (\"" + this.getTable() + "\".\"" + this.getPrimaryKey() + "\" = ?)", - p); - } - - public MSqlStatementResults deleteRecords(LinkedHashSet idSet) throws MDatabaseConnectionFailureException, MSqlStatementException { - if (null == idSet) { - throw new IllegalArgumentException("Invalid 'idSet': null."); - } - // - String whereClause = null; - LinkedList p = null; - if (0 == idSet.size()) { - whereClause = "0"; - } - else { - StringJoiner s = new StringJoiner(", "); - p = new LinkedList(); - for (Object id: idSet) { - s.add("?"); - p.add(id); - } - whereClause = "\"" + this.getTable() + "\".\"" + this.getPrimaryKey() + "\" IN (" + s.toString() + ")"; - } - // - return this.getDatabaseConnection().executePreparedStatement(" DELETE FROM \"" + this.getTable() + "\"" - + " WHERE (" + whereClause + ")", - p); - } - -} diff --git a/7.x/src/java/com/marcozanon/macaco/database/MSqlTransactionException.java b/7.x/src/java/com/marcozanon/macaco/database/MSqlTransactionException.java deleted file mode 100644 index b6554d8..0000000 --- a/7.x/src/java/com/marcozanon/macaco/database/MSqlTransactionException.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * See LICENSE for details. - */ - -package com.marcozanon.macaco.database; - -@SuppressWarnings("serial") -public class MSqlTransactionException extends MDatabaseException { - - /* */ - - public MSqlTransactionException() { - super(); - } - - public MSqlTransactionException(String message) { - super(message); - } - - public MSqlTransactionException(Throwable error) { - super(error); - } - - public MSqlTransactionException(String message, Throwable error) { - super(message, error); - } - -} diff --git a/7.x/src/java/com/marcozanon/macaco/json/MInvalidJsonValueException.java b/7.x/src/java/com/marcozanon/macaco/json/MInvalidJsonValueException.java deleted file mode 100644 index d22771f..0000000 --- a/7.x/src/java/com/marcozanon/macaco/json/MInvalidJsonValueException.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * See LICENSE for details. - */ - -package com.marcozanon.macaco.json; - -@SuppressWarnings("serial") -public class MInvalidJsonValueException extends MJsonException { - - /* */ - - public MInvalidJsonValueException() { - super(); - } - - public MInvalidJsonValueException(String message) { - super(message); - } - - public MInvalidJsonValueException(Throwable error) { - super(error); - } - - public MInvalidJsonValueException(String message, Throwable error) { - super(message, error); - } - -} diff --git a/7.x/src/java/com/marcozanon/macaco/json/MJsonArray.java b/7.x/src/java/com/marcozanon/macaco/json/MJsonArray.java deleted file mode 100644 index ff94a5a..0000000 --- a/7.x/src/java/com/marcozanon/macaco/json/MJsonArray.java +++ /dev/null @@ -1,210 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * 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 MInvalidJsonValueException { - this("[]"); - } - - public MJsonArray(String x) throws MInvalidJsonValueException { - super(); - // - this.parseString(x); - } - - @Override - public MJsonArray clone() { - MJsonArray tmpMJsonArray = null; - try { - tmpMJsonArray = new MJsonArray(this.getJsonValue()); - } - catch (MInvalidJsonValueException exception) { // cannot happen - } - return tmpMJsonArray; - } - - /* Value handlers. */ - - public void addValue(MJsonValue x) { - if (null == x) { - throw new IllegalArgumentException("Invalid 'x': null."); - } - // - this.getValues().add(x); - } - - public void setValue(int index, MJsonValue x) { - if ((0 > index) || ((this.getValueCount() - 1) < index)) { - throw new IllegalArgumentException(String.format("Invalid 'index': %s: out of range.", index)); - } - if (null == x) { - throw new IllegalArgumentException("Invalid 'x': null."); - } - // - this.getValues().set(index, x); - } - - protected LinkedList getValues() { - return this.values; - } - - public MJsonValue getValue(int index) { - if ((0 > index) || ((this.getValueCount() - 1) < index)) { - throw new IllegalArgumentException(String.format("Invalid 'index': %s: out of range.", index)); - } - // - return this.getValues().get(index).clone(); - } - - public int getValueCount() { - return this.getValues().size(); - } - - public void removeValue(int index) { - if ((0 > index) || ((this.getValueCount() - 1) < index)) { - throw new IllegalArgumentException(String.format("Invalid 'index': %s: out of range.", index)); - } - // - this.getValues().remove(index); - } - - public void clearValues() { - this.getValues().clear(); - } - - /* Parsers. */ - - protected static int getTokenLength(String x) { - if (null == x) { - throw new IllegalArgumentException("Invalid 'x': null."); - } - // - if ((2 > x.length()) || ('[' != x.charAt(0))) { - return 0; - } - int position = x.indexOf("]", 1); - while (-1 < position) { - try { - MJsonArray testValue = new MJsonArray(); - testValue.parseString(x.substring(0, position + 1)); - return position + 1; - } - catch (MInvalidJsonValueException exception) { - position = x.indexOf("]", position + 1); - } - } - return 0; - } - - public void parseString(String x) throws MInvalidJsonValueException { - if (MText.isBlank(x)) { - throw new IllegalArgumentException("Invalid 'x': null or empty."); - } - // - if ((2 > x.length()) || ('[' != x.charAt(0)) || (']' != x.charAt(x.length() - 1))) { - throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: must begin and end with square brackets.", x)); - } - String y = x.substring(1, x.length() - 1); - int parsingPosition = 0; - MJsonArray.ParsingMode parsingMode = MJsonArray.ParsingMode.START; - while ((y.length() > parsingPosition) && (MJsonArray.ParsingMode.ERROR != parsingMode)) { - int c = y.codePointAt(parsingPosition); - switch (parsingMode) { - case START: - case POST_VALUE_SEPARATOR: // a value is expected - if (MJsonValue.isWhitespace(c)) { - parsingPosition++; - } - else if ((MJsonValue.isValueSeparator(c)) || (MJsonValue.isNameSeparator(c)) || (MJsonValue.isClosingStructuralCharacter(c))) { - parsingMode = MJsonArray.ParsingMode.ERROR; - } - else { - int offset = 0; - if (0 < (offset = MJsonNull.getTokenLength(y.substring(parsingPosition)))) { - this.addValue(new MJsonNull(y.substring(parsingPosition, parsingPosition + offset))); - } - else if (0 < (offset = MJsonBoolean.getTokenLength(y.substring(parsingPosition)))) { - this.addValue(new MJsonBoolean(y.substring(parsingPosition, parsingPosition + offset))); - } - else if (0 < (offset = MJsonNumber.getTokenLength(y.substring(parsingPosition)))) { - this.addValue(new MJsonNumber(y.substring(parsingPosition, parsingPosition + offset))); - } - else if (0 < (offset = MJsonString.getTokenLength(y.substring(parsingPosition)))) { - this.addValue(new MJsonString(y.substring(parsingPosition, parsingPosition + offset))); - } - else if (0 < (offset = MJsonArray.getTokenLength(y.substring(parsingPosition)))) { - this.addValue(new MJsonArray(y.substring(parsingPosition, parsingPosition + offset))); - } - else if (0 < (offset = MJsonObject.getTokenLength(y.substring(parsingPosition)))) { - this.addValue(new MJsonObject(y.substring(parsingPosition, parsingPosition + offset))); - } - if (0 < offset) { - parsingPosition += offset; - parsingMode = MJsonArray.ParsingMode.POST_VALUE; - } - else { - parsingMode = MJsonArray.ParsingMode.ERROR; - } - } - break; - case POST_VALUE: - if (MJsonValue.isWhitespace(c)) { - parsingPosition++; - } - else if (MJsonValue.isValueSeparator(c)) { - parsingPosition++; - parsingMode = MJsonArray.ParsingMode.POST_VALUE_SEPARATOR; - } - else { - parsingMode = MJsonArray.ParsingMode.ERROR; - } - break; - case ERROR: // malformed string - break; - } - } - if (MJsonArray.ParsingMode.POST_VALUE_SEPARATOR == parsingMode) { - parsingMode = MJsonArray.ParsingMode.ERROR; - } - if (MJsonArray.ParsingMode.ERROR == parsingMode) { - throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json array.", x)); - } - } - - /* Formatter. */ - - @Override - public String getJsonValue() { - StringBuilder s = new StringBuilder(""); - s.append("["); - for (int i = 0; this.getValueCount() > i; i++) { - if (0 < i) { - s.append(", "); - } - s.append(this.getValues().get(i).getJsonValue()); - } - s.append("]"); - return s.toString(); - } - -} diff --git a/7.x/src/java/com/marcozanon/macaco/json/MJsonBoolean.java b/7.x/src/java/com/marcozanon/macaco/json/MJsonBoolean.java deleted file mode 100644 index c1f26ef..0000000 --- a/7.x/src/java/com/marcozanon/macaco/json/MJsonBoolean.java +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * See LICENSE for details. - */ - -package com.marcozanon.macaco.json; - -import com.marcozanon.macaco.text.MText; - -public class MJsonBoolean extends MJsonValue { - - protected Boolean value = null; - - /* */ - - public MJsonBoolean() throws MInvalidJsonValueException { - this("false"); - } - - public MJsonBoolean(String x) throws MInvalidJsonValueException { - super(); - // - this.parseString(x); - } - - @Override - public MJsonBoolean clone() { - MJsonBoolean tmpMJsonBoolean = null; - try { - tmpMJsonBoolean = new MJsonBoolean(this.getJsonValue()); - } - catch (MInvalidJsonValueException exception) { // cannot happen - } - return tmpMJsonBoolean; - } - - /* Value handlers. */ - - public void setValue(Boolean x) { - if (null == x) { - throw new IllegalArgumentException("Invalid 'x': null."); - } - // - this.value = x; - } - - public Boolean getValue() { - return this.value; - } - - /* Parsers. */ - - protected static int getTokenLength(String x) { - if (null == x) { - throw new IllegalArgumentException("Invalid 'x': null."); - } - // - if (x.startsWith("true")) { - return "true".length(); - } - else if (x.startsWith("false")) { - return "false".length(); - } - return 0; - } - - public void parseString(String x) throws MInvalidJsonValueException { - if (MText.isBlank(x)) { - throw new IllegalArgumentException("Invalid 'x': null or empty."); - } - // - if ((!"true".equals(x)) && (!"false".equals(x))) { - throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json boolean.", x)); - } - this.setValue(Boolean.valueOf(x)); - } - - /* Formatter. */ - - @Override - public String getJsonValue() { - return this.getValue().toString(); - } - -} diff --git a/7.x/src/java/com/marcozanon/macaco/json/MJsonException.java b/7.x/src/java/com/marcozanon/macaco/json/MJsonException.java deleted file mode 100644 index 35bae21..0000000 --- a/7.x/src/java/com/marcozanon/macaco/json/MJsonException.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * 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/7.x/src/java/com/marcozanon/macaco/json/MJsonNull.java b/7.x/src/java/com/marcozanon/macaco/json/MJsonNull.java deleted file mode 100644 index a6e4339..0000000 --- a/7.x/src/java/com/marcozanon/macaco/json/MJsonNull.java +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * See LICENSE for details. - */ - -package com.marcozanon.macaco.json; - -import com.marcozanon.macaco.text.MText; - -public class MJsonNull extends MJsonValue { - - /* */ - - public MJsonNull() throws MInvalidJsonValueException { - this("null"); - } - - public MJsonNull(String x) throws MInvalidJsonValueException { - super(); - // - this.parseString(x); - } - - @Override - public MJsonNull clone() { - MJsonNull tmpMJsonNull = null; - try { - tmpMJsonNull = new MJsonNull(this.getJsonValue()); - } - catch (MInvalidJsonValueException exception) { // cannot happen - } - return tmpMJsonNull; - } - - /* Parsers. */ - - protected static int getTokenLength(String x) { - if (null == x) { - throw new IllegalArgumentException("Invalid 'x': null."); - } - // - if (x.startsWith("null")) { - return "null".length(); - } - return 0; - } - - public void parseString(String x) throws MInvalidJsonValueException { - if (MText.isBlank(x)) { - throw new IllegalArgumentException("Invalid 'x': null or empty."); - } - // - if (!"null".equals(x)) { - throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json null.", x)); - } - } - - /* Formatter. */ - - @Override - public String getJsonValue() { - return "null"; - } - -} diff --git a/7.x/src/java/com/marcozanon/macaco/json/MJsonNumber.java b/7.x/src/java/com/marcozanon/macaco/json/MJsonNumber.java deleted file mode 100644 index 8e20402..0000000 --- a/7.x/src/java/com/marcozanon/macaco/json/MJsonNumber.java +++ /dev/null @@ -1,106 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * See LICENSE for details. - */ - -package com.marcozanon.macaco.json; - -import com.marcozanon.macaco.text.MText; -import java.math.BigDecimal; - -public class MJsonNumber extends MJsonValue { - - protected BigDecimal value = null; - - /* */ - - public MJsonNumber() throws MInvalidJsonValueException { - this("0"); - } - - public MJsonNumber(String x) throws MInvalidJsonValueException { - super(); - // - this.parseString(x); - } - - @Override - public MJsonNumber clone() { - MJsonNumber tmpMJsonNumber = null; - try { - tmpMJsonNumber = new MJsonNumber(this.getJsonValue()); - } - catch (MInvalidJsonValueException exception) { // cannot happen - } - return tmpMJsonNumber; - } - - /* Value handlers. */ - - public void setValue(Number x) { - if (null == x) { - throw new IllegalArgumentException("Invalid 'x': null."); - } - // - this.value = new BigDecimal(x.toString()); - } - - public BigDecimal getValue() { - return this.value; - } - - /* Parsers. */ - - protected static int getTokenLength(String x) { - if (null == x) { - throw new IllegalArgumentException("Invalid 'x': null."); - } - // - if (' ' == x.charAt(0)) { - return 0; - } - int position = 0; - int validPosition = 0; - BigDecimal y = null; - while (x.length() > position) { - if (' ' == x.charAt(position)) { - break; - } - else { - try { - y = new BigDecimal(x.substring(0, position + 1)); - position++; - validPosition = position; - } - catch (NumberFormatException exception) { - position++; - } - } - } - return validPosition; - } - - public void parseString(String x) throws MInvalidJsonValueException { - if (MText.isBlank(x)) { - throw new IllegalArgumentException("Invalid 'x': null or empty."); - } - // - BigDecimal y = null; - try { - y = new BigDecimal(x); - } - catch (NumberFormatException exception) { - throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json number.", x)); // no need to propagate exception - } - this.setValue(y); - } - - /* Formatter. */ - - @Override - public String getJsonValue() { - return this.getValue().toString(); - } - -} diff --git a/7.x/src/java/com/marcozanon/macaco/json/MJsonObject.java b/7.x/src/java/com/marcozanon/macaco/json/MJsonObject.java deleted file mode 100644 index 76101f0..0000000 --- a/7.x/src/java/com/marcozanon/macaco/json/MJsonObject.java +++ /dev/null @@ -1,264 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * 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 MInvalidJsonValueException { - this("{}"); - } - - public MJsonObject(String x) throws MInvalidJsonValueException { - super(); - // - this.parseString(x); - } - - @Override - public MJsonObject clone() { - MJsonObject tmpMJsonObject = null; - try { - tmpMJsonObject = new MJsonObject(this.getJsonValue()); - } - catch (MInvalidJsonValueException exception) { // cannot happen - } - return tmpMJsonObject; - } - - /* Value handlers. */ - - public void setValue(String key, MJsonValue x) { - if (MText.isBlank(key)) { - throw new IllegalArgumentException("Invalid 'key': null or empty."); - } - if (null == x) { - throw new IllegalArgumentException("Invalid 'x': null."); - } - // - this.getValues().put(key, x); - } - - protected LinkedHashMap getValues() { - return this.values; - } - - public MJsonValue getValue(String key) { - if (MText.isBlank(key)) { - throw new IllegalArgumentException("Invalid 'key': null or empty."); - } - // - if (!this.containsKey(key)) { - throw new IllegalArgumentException(String.format("Invalid 'key': %s: not available.", key)); - } - return this.getValues().get(key).clone(); - } - - public int getValueCount() { - return this.getValues().size(); - } - - public void removeValue(String key) { - if (MText.isBlank(key)) { - throw new IllegalArgumentException("Invalid 'key': null or empty."); - } - // - if (!this.containsKey(key)) { - throw new IllegalArgumentException(String.format("Invalid 'key': %s: not available.", key)); - } - this.getValues().remove(key); - } - - public void clearValues() { - this.getValues().clear(); - } - - public boolean containsKey(String key) { - if (MText.isBlank(key)) { - throw new IllegalArgumentException("Invalid 'key': null or empty."); - } - // - return this.getValues().containsKey(key); - } - - public LinkedHashSet getKeys() { - LinkedHashSet keys = new LinkedHashSet(); - for (String key: this.getValues().keySet()) { - keys.add(key); - } - return keys; - } - - /* Parsers. */ - - protected static int getTokenLength(String x) { - if (null == x) { - throw new IllegalArgumentException("Invalid 'x': null."); - } - // - if ((2 > x.length()) || ('{' != x.charAt(0))) { - return 0; - } - int position = x.indexOf("}", 1); - while (-1 < position) { - try { - MJsonObject testValue = new MJsonObject(); - testValue.parseString(x.substring(0, position + 1)); - return position + 1; - } - catch (MInvalidJsonValueException exception) { - position = x.indexOf("}", position + 1); - } - } - return 0; - } - - public void parseString(String x) throws MInvalidJsonValueException { - if (MText.isBlank(x)) { - throw new IllegalArgumentException("Invalid 'x': null or empty."); - } - // - if ((2 > x.length()) || ('{' != x.charAt(0)) || ('}' != x.charAt(x.length() - 1))) { - throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: must begin and end with curly brackets.", x)); - } - String y = x.substring(1, x.length() - 1); - int parsingPosition = 0; - MJsonObject.ParsingMode parsingMode = MJsonObject.ParsingMode.START; - String lastKey = ""; - while ((y.length() > parsingPosition) && (MJsonObject.ParsingMode.ERROR != parsingMode)) { - int c = y.codePointAt(parsingPosition); - switch (parsingMode) { - case START: - case POST_VALUE_SEPARATOR: // a name is expected - if (MJsonValue.isWhitespace(c)) { - parsingPosition++; - } - else if ((MJsonValue.isValueSeparator(c)) || (MJsonValue.isNameSeparator(c)) || (MJsonValue.isOpeningStructuralCharacter(c)) || (MJsonValue.isClosingStructuralCharacter(c))) { - parsingMode = MJsonObject.ParsingMode.ERROR; - } - else { - int offset = MJsonString.getTokenLength(y.substring(parsingPosition)); - if (0 < offset) { - lastKey = (new MJsonString(y.substring(parsingPosition, parsingPosition + offset))).getValue(); - parsingPosition += offset; - parsingMode = MJsonObject.ParsingMode.POST_NAME; - } - else { - parsingMode = MJsonObject.ParsingMode.ERROR; - } - } - break; - case POST_NAME: - if (MJsonValue.isWhitespace(c)) { - parsingPosition++; - } - else if (MJsonValue.isNameSeparator(c)) { - parsingPosition++; - parsingMode = MJsonObject.ParsingMode.POST_NAME_SEPARATOR; - } - else { - parsingMode = MJsonObject.ParsingMode.ERROR; - } - break; - case POST_NAME_SEPARATOR: // a value is expected - if (MJsonValue.isWhitespace(c)) { - parsingPosition++; - } - else if ((MJsonValue.isValueSeparator(c)) || (MJsonValue.isNameSeparator(c)) || (MJsonValue.isClosingStructuralCharacter(c))) { - parsingMode = MJsonObject.ParsingMode.ERROR; - } - else { - int offset = 0; - if (0 < (offset = MJsonNull.getTokenLength(y.substring(parsingPosition)))) { - this.setValue(lastKey, new MJsonNull(y.substring(parsingPosition, parsingPosition + offset))); - } - else if (0 < (offset = MJsonBoolean.getTokenLength(y.substring(parsingPosition)))) { - this.setValue(lastKey, new MJsonBoolean(y.substring(parsingPosition, parsingPosition + offset))); - } - else if (0 < (offset = MJsonNumber.getTokenLength(y.substring(parsingPosition)))) { - this.setValue(lastKey, new MJsonNumber(y.substring(parsingPosition, parsingPosition + offset))); - } - else if (0 < (offset = MJsonString.getTokenLength(y.substring(parsingPosition)))) { - this.setValue(lastKey, new MJsonString(y.substring(parsingPosition, parsingPosition + offset))); - } - else if (0 < (offset = MJsonArray.getTokenLength(y.substring(parsingPosition)))) { - this.setValue(lastKey, new MJsonArray(y.substring(parsingPosition, parsingPosition + offset))); - } - else if (0 < (offset = MJsonObject.getTokenLength(y.substring(parsingPosition)))) { - this.setValue(lastKey, new MJsonObject(y.substring(parsingPosition, parsingPosition + offset))); - } - if (0 < offset) { - lastKey = ""; - parsingPosition += offset; - parsingMode = MJsonObject.ParsingMode.POST_VALUE; - } - else { - parsingMode = MJsonObject.ParsingMode.ERROR; - } - } - break; - case POST_VALUE: - if (MJsonValue.isWhitespace(c)) { - parsingPosition++; - } - else if (MJsonValue.isValueSeparator(c)) { - parsingPosition++; - parsingMode = MJsonObject.ParsingMode.POST_VALUE_SEPARATOR; - } - else { - parsingMode = MJsonObject.ParsingMode.ERROR; - } - break; - case ERROR: // malformed string - break; - } - } - if ((MJsonObject.ParsingMode.POST_NAME == parsingMode) || (MJsonObject.ParsingMode.POST_NAME_SEPARATOR == parsingMode) || (MJsonObject.ParsingMode.POST_VALUE_SEPARATOR == parsingMode)) { - parsingMode = MJsonObject.ParsingMode.ERROR; - } - if (MJsonObject.ParsingMode.ERROR == parsingMode) { - throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json object.", x)); - } - } - - /* Formatter. */ - - @Override - public String getJsonValue() { - StringBuilder s = new StringBuilder(""); - s.append("{"); - int k = 0; - for (String key: this.getValues().keySet()) { - if (0 < k) { - s.append(", "); - } - s.append("\"" + MJsonString.getEscapedString(key, false) + "\""); - s.append(": "); - s.append(this.getValues().get(key).getJsonValue()); - k++; - } - s.append("}"); - return s.toString(); - } - -} diff --git a/7.x/src/java/com/marcozanon/macaco/json/MJsonString.java b/7.x/src/java/com/marcozanon/macaco/json/MJsonString.java deleted file mode 100644 index 2e22d12..0000000 --- a/7.x/src/java/com/marcozanon/macaco/json/MJsonString.java +++ /dev/null @@ -1,294 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * See LICENSE for details. - */ - -package com.marcozanon.macaco.json; - -import com.marcozanon.macaco.text.MText; - -public class MJsonString extends MJsonValue { - - protected boolean extendedEscapeMode = false; - - protected String value = null; - - /* */ - - public MJsonString() throws MInvalidJsonValueException { - this("\"\""); - } - - public MJsonString(String x) throws MInvalidJsonValueException { - this(x, false); - } - - public MJsonString(String x, boolean extendedEscapeMode) throws MInvalidJsonValueException { - super(); - // - this.setExtendedEscapeMode(extendedEscapeMode); - this.parseString(x); - } - - @Override - public MJsonString clone() { - MJsonString tmpMJsonString = null; - try { - tmpMJsonString = new MJsonString(this.getJsonValue()); - } - catch (MInvalidJsonValueException exception) { // cannot happen - } - return tmpMJsonString; - } - - /* Extended escape mode. */ - - public void setExtendedEscapeMode(boolean extendedEscapeMode) { - this.extendedEscapeMode = extendedEscapeMode; - } - - public boolean getExtendedEscapeMode() { - return this.extendedEscapeMode; - } - - /* Value handlers. */ - - public void setValue(String x) { - if (null == x) { - throw new IllegalArgumentException("Invalid 'x': null."); - } - // - this.value = x; - } - - public String getValue() { - return this.value; - } - - /* Helpers. */ - - protected static String getEscapedString(String x, boolean extendedEscapeMode) { - if (null == x) { - throw new IllegalArgumentException("Invalid 'x': null."); - } - // - StringBuilder y = new StringBuilder(); - for (int i = 0; x.length() > i; i++) { - int c = x.codePointAt(i); - if (extendedEscapeMode) { - if (0x00001F >= c) { // control Ascii character (0x000000-0x00001F) - y.append("\\u" + String.format("%4H", c).replace(" ", "0")); - } - else if (0x00007F >= c) { // non-control Ascii character (0x000020-0x00007F) - if ('"' == c) { - y.append("\\\""); - } - else if ('\\' == c) { - y.append("\\\\"); - } - else if ('/' == c) { - y.append("\\/"); - } - else { - y.appendCodePoint(c); - } - } - else if (0x00FFFF >= c) { // non-Ascii Bmp character (0x000080-0x00FFFF) - y.append("\\u" + String.format("%4H", c).replace(" ", "0")); - } - else { // non-Bmp character (0x010000-0x10FFFF) - int cc = c - 0x010000; - int ch = 0xD800 + (cc >> 10); - int cl = 0xDC00 + (cc & 0x03FF); - y.append("\\u" + String.format("%4H", ch).replace(" ", "0") + "\\u" + String.format("%4H", cl).replace(" ", "0")); - i++; - } - } - else { - if ('"' == c) { - y.append("\\\""); - } - else if ('\\' == c) { - y.append("\\\\"); - } - else if ('/' == c) { - y.append("\\/"); - } - else if ('\b' == c) { - y.append("\\b"); - } - else if ('\f' == c) { - y.append("\\f"); - } - else if ('\n' == c) { - y.append("\\n"); - } - else if ('\r' == c) { - y.append("\\r"); - } - else if ('\t' == c) { - y.append("\\t"); - } - else if (0x00001F >= c) { // non-special control Ascii character (0x000000-0x00001F) - y.append("\\u" + String.format("%4H", c).replace(" ", "0")); - } - else { - y.appendCodePoint(c); - if (0x010000 <= c) { - i++; - } - } - } - } - return y.toString(); - } - - protected static String getUnescapedString(String x) throws MInvalidJsonValueException { - if (null == x) { - throw new IllegalArgumentException("Invalid 'x': null."); - } - // - StringBuilder y = new StringBuilder(); - for (int i = 0; x.length() > i; i++) { - if (x.indexOf("\\\"", i) == i) { - y.append("\""); - i++; - } - else if (x.indexOf("\\\\", i) == i) { - y.append("\\"); - i++; - } - else if (x.indexOf("\\/", i) == i) { - y.append("/"); - i++; - } - else if (x.indexOf("\\b", i) == i) { - y.append("\b"); - i++; - } - else if (x.indexOf("\\f", i) == i) { - y.append("\f"); - i++; - } - else if (x.indexOf("\\n", i) == i) { - y.append("\n"); - i++; - } - else if (x.indexOf("\\r", i) == i) { - y.append("\r"); - i++; - } - else if (x.indexOf("\\t", i) == i) { - y.append("\t"); - i++; - } - else if (x.indexOf("\\u", i) == i) { - if ((i + 2 + 4) > x.length()) { - throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json string.", x)); - } - else { - int ch = -1; - try { - ch = Integer.parseInt(x.substring(i + 2, i + 2 + 4), 16); - i += 5; - if ((0xD800 <= ch) && (0xDBFF >= ch)) { - i++; - if (x.indexOf("\\u", i) != i) { - throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json string.", x)); - } - else if ((i + 2 + 4) > x.length()) { - throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json string.", x)); - } - else { - int cl = -1; - try { - cl = Integer.parseInt(x.substring(i + 2, i + 2 + 4), 16); - i += 5; - if ((0xDC00 >= cl) || (0xDFFF <= cl)) { - throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json string.", x)); - } - int c = ((ch - 0xD800) << 10) + (cl - 0xDC00) + 0x010000; - y.appendCodePoint(c); - } - catch (NumberFormatException exception) { - throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json string.", x)); // no need to propagate exception - } - } - } - else { - y.appendCodePoint(ch); - } - } - catch (NumberFormatException exception) { - throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json string.", x)); // no need to propagate exception - } - } - } - else { - int c = x.codePointAt(i); - if (0x010000 <= c) { - i++; - } - y.appendCodePoint(c); - } - } - return y.toString(); - } - - /* Parsers. */ - - protected static int getTokenLength(String x) { - if (null == x) { - throw new IllegalArgumentException("Invalid 'x': null."); - } - // - if ((2 > x.length()) || ('"' != x.charAt(0))) { - return 0; - } - if ('"' == x.charAt(1)) { - return 1 + 1; - } - String y = x.substring(1); - int quotationMarkPosition = 0; - while (-1 < (quotationMarkPosition = y.indexOf("\"", quotationMarkPosition + 1))) { - int c = 0; - StringBuilder s = new StringBuilder(""); - while ((c <= quotationMarkPosition) && (y.substring(quotationMarkPosition - c, quotationMarkPosition).equals(s.toString()))) { - c++; - s.append("\\"); - } - c--; - if (0 == (c % 2)) { - break; - } - } - if (-1 == quotationMarkPosition) { - return 0; - } - return (quotationMarkPosition + 1) + 1; - } - - public void parseString(String x) throws MInvalidJsonValueException { - if (MText.isBlank(x)) { - throw new IllegalArgumentException("Invalid 'x': null or empty."); - } - // - if ((2 > x.length()) || ('"' != x.charAt(0)) || ('"' != x.charAt(x.length() - 1))) { - throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json string.", x)); - } - String y = x.substring(1, x.length() - 1); - if (-1 < y.replaceAll("\\\\\"", "__").indexOf("\"")) { - throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json string.", x)); - } - this.setValue(MJsonString.getUnescapedString(y)); - } - - /* Formatter. */ - - @Override - public String getJsonValue() { - return "\"" + MJsonString.getEscapedString(this.getValue(), this.getExtendedEscapeMode()) + "\""; - } - -} diff --git a/7.x/src/java/com/marcozanon/macaco/json/MJsonValue.java b/7.x/src/java/com/marcozanon/macaco/json/MJsonValue.java deleted file mode 100644 index 85fbe8a..0000000 --- a/7.x/src/java/com/marcozanon/macaco/json/MJsonValue.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * See LICENSE for details. - */ - -package com.marcozanon.macaco.json; - -import com.marcozanon.macaco.MObject; - -public abstract class MJsonValue extends MObject { - - /* */ - - @Override - 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/7.x/src/java/com/marcozanon/macaco/logging/MLogDatabaseTable.java b/7.x/src/java/com/marcozanon/macaco/logging/MLogDatabaseTable.java deleted file mode 100644 index 07d213f..0000000 --- a/7.x/src/java/com/marcozanon/macaco/logging/MLogDatabaseTable.java +++ /dev/null @@ -1,117 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * See LICENSE for details. - */ - -package com.marcozanon.macaco.logging; - -import com.marcozanon.macaco.database.MDatabaseConnection; -import com.marcozanon.macaco.database.MDatabaseConnectionFailureException; -import com.marcozanon.macaco.database.MDatabaseConnectionPool; -import com.marcozanon.macaco.database.MSqlStatementException; -import com.marcozanon.macaco.database.MSqlTable; -import com.marcozanon.macaco.text.MText; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.LinkedHashMap; - -public class MLogDatabaseTable extends MLogTarget { - - protected MDatabaseConnectionPool databaseConnectionPool = null; - - protected String logDatabaseTable = null; - protected String logDatabaseTablePrimaryKey = null; - protected String logDatabaseField = null; - - protected MDatabaseConnection databaseConnection = null; - - /* */ - - public MLogDatabaseTable(MDatabaseConnectionPool databaseConnectionPool, String logDatabaseTable, String logDatabaseTablePrimaryKey, String logDatabaseField) throws MLoggingException { - super(); - // - if (null == databaseConnectionPool) { - throw new IllegalArgumentException("Invalid 'databaseConnectionPool': null."); - } - if (MText.isEmpty(logDatabaseTable)) { - throw new IllegalArgumentException("Invalid 'logDatabaseTable': null or empty."); - } - if (MText.isEmpty(logDatabaseTablePrimaryKey)) { - throw new IllegalArgumentException("Invalid 'logDatabaseTablePrimaryKey': null or empty."); - } - if (MText.isEmpty(logDatabaseField)) { - throw new IllegalArgumentException("Invalid 'logDatabaseField': null or empty."); - } - // - this.databaseConnectionPool = databaseConnectionPool; - this.logDatabaseTable = logDatabaseTable; - this.logDatabaseTablePrimaryKey = logDatabaseTablePrimaryKey; - this.logDatabaseField = logDatabaseField; - } - - @Override - public void close() throws MLoggingException { - } - - /* Database connection pool. */ - - protected MDatabaseConnectionPool getDatabaseConnectionPool() { - return this.databaseConnectionPool; - } - - /* Database logging parameters. */ - - protected String getLogDatabaseTable() { - return this.logDatabaseTable; - } - - protected String getLogDatabaseTablePrimaryKey() { - return this.logDatabaseTablePrimaryKey; - } - - protected String getLogDatabaseField() { - return this.logDatabaseField; - } - - /* Output. */ - - @Override - public void appendMessage(String message) throws MLoggingException { - this.appendMessage(message, 0); - } - - @Override - public void appendMessage(String message, int indentation) throws MLoggingException { - if (null == message) { - throw new IllegalArgumentException("Invalid 'message': null."); - } - if (0 > indentation) { - throw new IllegalArgumentException(String.format("Invalid 'indentation': %s: cannot be negative.", indentation)); - } - // - try { - String timestamp = (new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")).format(new Date()); - // - StringBuilder separator = new StringBuilder(" "); - for (int i = 0; i < indentation; i++) { - separator.append(" "); - } - // - MDatabaseConnection databaseConnection = this.getDatabaseConnectionPool().popDatabaseConnection(); - // - LinkedHashMap p = new LinkedHashMap(); - p.put(this.getLogDatabaseField(), timestamp + separator + message); - (new MSqlTable(databaseConnection, this.getLogDatabaseTable(), this.getLogDatabaseTablePrimaryKey())).setRecord(p, null, false); - // - this.getDatabaseConnectionPool().pushDatabaseConnection(databaseConnection); - } - catch (MDatabaseConnectionFailureException exception) { - throw new MLoggingException("Could not write to database table.", exception); - } - catch (MSqlStatementException exception) { - throw new MLoggingException("Could not write to database table.", exception); - } - } - -} diff --git a/7.x/src/java/com/marcozanon/macaco/logging/MLogFilter.java b/7.x/src/java/com/marcozanon/macaco/logging/MLogFilter.java deleted file mode 100644 index c5bfcca..0000000 --- a/7.x/src/java/com/marcozanon/macaco/logging/MLogFilter.java +++ /dev/null @@ -1,125 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * See LICENSE for details. - */ - -package com.marcozanon.macaco.logging; - -import com.marcozanon.macaco.MObject; -import java.util.LinkedList; - -public class MLogFilter extends MObject { - - public static enum Threshold { - STANDARD, - DEBUG - }; - - protected MLogFilter.Threshold threshold = null; - - protected LinkedList 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.getLogTargets()) { - t.close(); - } - } - - @Override - 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 getLogTargets() { - return this.logTargets; - } - - /* Output. */ - - public void setPausedState(boolean pausedState) throws MLoggingException { - this.pausedState = pausedState; - // - if (!this.getPausedState()) { - this.flushMessages(); - } - } - - public boolean getPausedState() { - return this.pausedState; - } - - public void appendMessage(MLogFilter.Threshold level, String message) throws MLoggingException { - this.appendMessage(level, message, 0); - } - - public void appendMessage(MLogFilter.Threshold level, String message, int indentation) throws MLoggingException { - if (null == level) { - throw new IllegalArgumentException("Invalid 'level': null."); - } - // - if (level.ordinal() > this.getThreshold().ordinal()) { - return; - } - // - this.logMessageQueue.add(new MLogMessage(message, indentation)); - // - if (!this.getPausedState()) { - this.flushMessages(); - } - } - - protected void flushMessages() throws MLoggingException { - while (0 < this.logMessageQueue.size()) { - MLogMessage logMessage = this.logMessageQueue.remove(); - String message = logMessage.getMessage(); - int indentation = logMessage.getIndentation(); - // - for (MLogTarget logTarget: this.getLogTargets()) { - logTarget.appendMessage(message, indentation); - } - } - } - -} diff --git a/7.x/src/java/com/marcozanon/macaco/logging/MLogListener.java b/7.x/src/java/com/marcozanon/macaco/logging/MLogListener.java deleted file mode 100644 index 53956e6..0000000 --- a/7.x/src/java/com/marcozanon/macaco/logging/MLogListener.java +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * See LICENSE for details. - */ - -package com.marcozanon.macaco.logging; - -public interface MLogListener { - - /* Logging. */ - - public void onMessageLogging(String message); - -} diff --git a/7.x/src/java/com/marcozanon/macaco/logging/MLogMessage.java b/7.x/src/java/com/marcozanon/macaco/logging/MLogMessage.java deleted file mode 100644 index 13b6f1b..0000000 --- a/7.x/src/java/com/marcozanon/macaco/logging/MLogMessage.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * 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/7.x/src/java/com/marcozanon/macaco/logging/MLogPlainTextFile.java b/7.x/src/java/com/marcozanon/macaco/logging/MLogPlainTextFile.java deleted file mode 100644 index 3a13d75..0000000 --- a/7.x/src/java/com/marcozanon/macaco/logging/MLogPlainTextFile.java +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * See LICENSE for details. - */ - -package com.marcozanon.macaco.logging; - -import com.marcozanon.macaco.MConstants; -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), MConstants.TEXT_ENCODING)); - } - catch (FileNotFoundException exception) { - throw new MLoggingException("Could not open file.", exception); - } - catch (UnsupportedEncodingException exception) { // cannot happen - } - } - - @Override - public void close() throws MLoggingException { - try { - this.getBuffer().close(); - } - catch (IOException exception) { - throw new MLoggingException("Could not close file.", exception); - } - } - - /* File. */ - - protected String getFile() { - return this.file; - } - - /* Buffer. */ - - protected BufferedWriter getBuffer() { - return this.buffer; - } - - /* Output. */ - - @Override - public void appendMessage(String message) throws MLoggingException { - this.appendMessage(message, 0); - } - - @Override - public void appendMessage(String message, int indentation) throws MLoggingException { - if (null == message) { - throw new IllegalArgumentException("Invalid 'message': null."); - } - if (0 > indentation) { - throw new IllegalArgumentException(String.format("Invalid 'indentation': %s: cannot be negative.", indentation)); - } - // - try { - String timestamp = (new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")).format(new Date()); - // - StringBuilder separator = new StringBuilder(" "); - for (int i = 0; i < indentation; i++) { - separator.append(" "); - } - // - synchronized (this.getBuffer()) { - this.getBuffer().write(timestamp + separator + message); - this.getBuffer().newLine(); - this.getBuffer().flush(); - } - } - catch (IOException exception) { - throw new MLoggingException("Could not write to file.", exception); - } - } - -} diff --git a/7.x/src/java/com/marcozanon/macaco/logging/MLogTarget.java b/7.x/src/java/com/marcozanon/macaco/logging/MLogTarget.java deleted file mode 100644 index c9bb083..0000000 --- a/7.x/src/java/com/marcozanon/macaco/logging/MLogTarget.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * 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; - - @Override - 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/7.x/src/java/com/marcozanon/macaco/logging/MLoggingException.java b/7.x/src/java/com/marcozanon/macaco/logging/MLoggingException.java deleted file mode 100644 index f9abdf6..0000000 --- a/7.x/src/java/com/marcozanon/macaco/logging/MLoggingException.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * 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/7.x/src/java/com/marcozanon/macaco/text/MText.java b/7.x/src/java/com/marcozanon/macaco/text/MText.java deleted file mode 100644 index 587aaf5..0000000 --- a/7.x/src/java/com/marcozanon/macaco/text/MText.java +++ /dev/null @@ -1,482 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon . - * See LICENSE for details. - */ - -package com.marcozanon.macaco.text; - -import com.marcozanon.macaco.MConstants; -import com.marcozanon.macaco.MObject; -import java.io.BufferedInputStream; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.util.StringJoiner; -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(); - } - - public static String removeLine(String x, int lineNumber) { - if (null == x) { - throw new IllegalArgumentException("Invalid 'x': null."); - } - // - String[] xLines = x.split(System.getProperty("line.separator")); - // - if ((0 > lineNumber) || (xLines.length - 1 < lineNumber)) { - throw new IllegalArgumentException(String.format("Invalid 'lineNumber': %s.", lineNumber)); - } - // - StringJoiner stringJoiner = new StringJoiner(System.getProperty("line.separator")); - for (int l = 0; xLines.length > l; l++) { - if (l == lineNumber) { - continue; - } - // - stringJoiner.add(xLines[l]); - } - // - return stringJoiner.toString(); - } - - public static String removeFirstLine(String x) { - return MText.removeLine(x, 0); - } - - public static String removeLastLine(String x) { - if (null == x) { - throw new IllegalArgumentException("Invalid 'x': null."); - } - // - String[] xLines = x.split(System.getProperty("line.separator")); - // - return MText.removeLine(x, xLines.length - 1); - } - - /* 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 MXhtmlUnsafeStringException { - if (null == x) { - return null; - } - // - String text = x; - // - StringBuilder fakeXhtmlPageContent = new StringBuilder(""); - fakeXhtmlPageContent.append(String.format("", MConstants.TEXT_ENCODING)); - fakeXhtmlPageContent.append(""); - fakeXhtmlPageContent.append(""); - fakeXhtmlPageContent.append("</head>"); - fakeXhtmlPageContent.append(String.format("<body>%s</body>", text)); - fakeXhtmlPageContent.append("</html>"); - // Validate Xhtml (with no dangerous tags and event attributes). - try { - SAXParserFactory factory = SAXParserFactory.newInstance(); - factory.setValidating(true); - factory.setNamespaceAware(true); - SAXParser parser = factory.newSAXParser(); - XMLReader reader = parser.getXMLReader(); - reader.setEntityResolver(new EntityResolver() { - - @Override - 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() { - - @Override - 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() { - - @Override - public void error(SAXParseException exception) throws SAXException { - throw new SAXException(exception); - } - - @Override - public void fatalError(SAXParseException exception) throws SAXException { - throw new SAXException(exception); - } - - @Override - public void warning(SAXParseException exception) throws SAXException { - throw new SAXException(exception); - } - - }); - // - reader.parse(new InputSource(new ByteArrayInputStream(fakeXhtmlPageContent.toString().getBytes(MConstants.TEXT_ENCODING)))); - } - catch (ParserConfigurationException exception) { // cannot happen - } - catch (SAXException exception) { - throw new MXhtmlUnsafeStringException("Invalid 'x': unsafe tags or attributes inside.", exception); - } - catch (UnsupportedEncodingException exception) { // cannot happen - } - catch (IOException exception) { // cannot happen; put here not to bypass UnsupportedEncodingException - } - // Also convert named entities to numeric entities. - return MText.getXhtmlNumericEntitiesString(text); - } - -} diff --git a/7.x/src/java/com/marcozanon/macaco/text/MTextException.java b/7.x/src/java/com/marcozanon/macaco/text/MTextException.java deleted file mode 100644 index b451608..0000000 --- a/7.x/src/java/com/marcozanon/macaco/text/MTextException.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. - * 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/7.x/src/java/com/marcozanon/macaco/text/MTranslationException.java b/7.x/src/java/com/marcozanon/macaco/text/MTranslationException.java deleted file mode 100644 index 2b43602..0000000 --- a/7.x/src/java/com/marcozanon/macaco/text/MTranslationException.java +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. - * 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/7.x/src/java/com/marcozanon/macaco/text/MTranslationFileParsingException.java b/7.x/src/java/com/marcozanon/macaco/text/MTranslationFileParsingException.java deleted file mode 100644 index b33b9d3..0000000 --- a/7.x/src/java/com/marcozanon/macaco/text/MTranslationFileParsingException.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. - * See LICENSE for details. - */ - -package com.marcozanon.macaco.text; - -@SuppressWarnings("serial") -public class MTranslationFileParsingException extends MTranslationException { - - /* */ - - public MTranslationFileParsingException() { - super(); - } - - public MTranslationFileParsingException(String message) { - super(message); - } - - public MTranslationFileParsingException(Throwable error) { - super(error); - } - - public MTranslationFileParsingException(String message, Throwable error) { - super(message, error); - } - -} diff --git a/7.x/src/java/com/marcozanon/macaco/text/MTranslationValueNotFoundException.java b/7.x/src/java/com/marcozanon/macaco/text/MTranslationValueNotFoundException.java deleted file mode 100644 index 4610c66..0000000 --- a/7.x/src/java/com/marcozanon/macaco/text/MTranslationValueNotFoundException.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. - * See LICENSE for details. - */ - -package com.marcozanon.macaco.text; - -@SuppressWarnings("serial") -public class MTranslationValueNotFoundException extends MTranslationException { - - /* */ - - public MTranslationValueNotFoundException() { - super(); - } - - public MTranslationValueNotFoundException(String message) { - super(message); - } - - public MTranslationValueNotFoundException(Throwable error) { - super(error); - } - - public MTranslationValueNotFoundException(String message, Throwable error) { - super(message, error); - } - -} diff --git a/7.x/src/java/com/marcozanon/macaco/text/MTranslator.java b/7.x/src/java/com/marcozanon/macaco/text/MTranslator.java deleted file mode 100644 index 4760d27..0000000 --- a/7.x/src/java/com/marcozanon/macaco/text/MTranslator.java +++ /dev/null @@ -1,184 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. - * See LICENSE for details. - */ - -package com.marcozanon.macaco.text; - -import com.marcozanon.macaco.MConstants; -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 Locale basicLocale = null; - - protected LinkedHashMap<String, LinkedHashMap<String, String>> messages = new LinkedHashMap<String, LinkedHashMap<String, String>>(); - - /* */ - - public MTranslator(Locale basicLocale) { - super(); - // - if (null == basicLocale) { - throw new IllegalArgumentException("Invalid 'basicLocale': null."); - } - // - this.basicLocale = basicLocale; - } - - public MTranslator(Locale basicLocale, LinkedHashMap<String, LinkedHashMap<String, String>> messages) { - this(basicLocale); - // - this.messages = messages; - } - - public MTranslator(Locale basicLocale, String file) throws MTranslationFileParsingException { - this(basicLocale); - // - this.parseFile(file); - } - - @Override - public MTranslator clone() { - return new MTranslator(this.getBasicLocale(), this.getMessages()); - } - - /* Locale. */ - - protected Locale getBasicLocale() { - return this.basicLocale; - } - - /* String management. */ - - protected LinkedHashMap<String, LinkedHashMap<String, String>> getMessages() { - return this.messages; - } - - public void parseFile(String file) throws MTranslationFileParsingException { - if (MText.isBlank(file)) { - throw new IllegalArgumentException("Invalid 'file': null or empty."); - } - // - try (LineNumberReader buffer = new LineNumberReader(new InputStreamReader(new FileInputStream(file), MConstants.TEXT_ENCODING))) { - String message = null; - String line = null; - synchronized (this.getMessages()) { - while (true) { - try { - line = buffer.readLine(); - } - catch (IOException exception) { - throw new MTranslationFileParsingException("Could not read file.", exception); - } - if (null == line) { - break; - } - line = line.trim(); - if (MText.isEmpty(line)) { - message = null; - continue; - } - else if ((line.startsWith("#")) || (line.startsWith(";"))) { - continue; - } - else if (null == message) { - message = line; - if (this.getMessages().containsKey(message)) { - throw new MTranslationFileParsingException(String.format("Invalid line: %s: duplicated message: %s.", buffer.getLineNumber(), message)); - } - this.getMessages().put(message, new LinkedHashMap<String, String>()); - } - else { - int a = line.indexOf("="); - if (-1 == a) { - throw new MTranslationFileParsingException(String.format("Invalid line: %s: string malformed: %s.", buffer.getLineNumber(), line)); - } - String localeRepresentation = line.substring(0, a).trim(); - String translation = line.substring(a + 1).trim(); - if (this.getMessages().get(message).containsKey(localeRepresentation)) { - throw new MTranslationFileParsingException(String.format("Invalid line: %s: duplicated translation for locale: %s.", buffer.getLineNumber(), message)); - } - this.getMessages().get(message).put(localeRepresentation, translation); - } - } - } - } - catch (FileNotFoundException exception) { - throw new MTranslationFileParsingException("Could not open file.", exception); - } - catch (UnsupportedEncodingException exception) { // cannot happen - } - catch (IOException exception) { - throw new MTranslationFileParsingException("Could not open / close file.", exception); - } - } - - public void clear() { - synchronized (this.getMessages()) { - this.getMessages().clear(); - } - } - - public String t(String message, Locale locale) { - return this.getLenientTranslation(message, locale); - } - - public String getLenientTranslation(String message, Locale locale) { - String translation = null; - try { - translation = this.getTranslation(message, locale, false); - } - catch (MTranslationValueNotFoundException exception) { // cannot happen - } - return translation; - } - - public String getStrictTranslation(String message, Locale locale) throws MTranslationValueNotFoundException { - return this.getTranslation(message, locale, true); - } - - protected String getTranslation(String message, Locale locale, boolean strictMode) throws MTranslationValueNotFoundException { - if (MText.isBlank(message)) { - throw new IllegalArgumentException("Invalid 'message': null or empty."); - } - if (null == locale) { - throw new IllegalArgumentException("Invalid 'locale': null."); - } - // - if (!this.getMessages().containsKey(message)) { - if (strictMode) { - throw new MTranslationValueNotFoundException(String.format("Invalid 'message': %s: not available.", message)); - } - return message; - } -/* - if (this.getBasicLocale().equals(locale)) { - return message; - } -*/ - LinkedHashMap<String, String> messageTranslations = this.getMessages().get(message); - String localeRepresentation = locale.toString(); - if (!messageTranslations.containsKey(localeRepresentation)) { - if (strictMode) { - throw new MTranslationValueNotFoundException(String.format("Invalid 'locale': %s: translation not available for message: %s.", localeRepresentation, message)); - } - String localeFallbackRepresentation = locale.getLanguage(); - if (!messageTranslations.containsKey(localeFallbackRepresentation)) { - return message; - } - return messageTranslations.get(localeFallbackRepresentation); - } - return messageTranslations.get(localeRepresentation); - } - -} diff --git a/7.x/src/java/com/marcozanon/macaco/text/MXhtmlUnsafeStringException.java b/7.x/src/java/com/marcozanon/macaco/text/MXhtmlUnsafeStringException.java deleted file mode 100644 index abdc9f1..0000000 --- a/7.x/src/java/com/marcozanon/macaco/text/MXhtmlUnsafeStringException.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Macaco - * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. - * See LICENSE for details. - */ - -package com.marcozanon.macaco.text; - -@SuppressWarnings("serial") -public class MXhtmlUnsafeStringException extends MTextException { - - /* */ - - public MXhtmlUnsafeStringException() { - super(); - } - - public MXhtmlUnsafeStringException(String message) { - super(message); - } - - public MXhtmlUnsafeStringException(Throwable error) { - super(error); - } - - public MXhtmlUnsafeStringException(String message, Throwable error) { - super(message, error); - } - -} diff --git a/7.x/src/main/java/com/marcozanon/macaco/MConstants.java b/7.x/src/main/java/com/marcozanon/macaco/MConstants.java new file mode 100644 index 0000000..00a0c8a --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/MConstants.java @@ -0,0 +1,16 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * See LICENSE for details. + */ + +package com.marcozanon.macaco; + + +public class MConstants extends MObject { + + public static final String MACACO_VERSION = "7.x"; + + public static final String TEXT_ENCODING = "UTF-8"; + +} diff --git a/7.x/src/main/java/com/marcozanon/macaco/MException.java b/7.x/src/main/java/com/marcozanon/macaco/MException.java new file mode 100644 index 0000000..1de6b77 --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/MException.java @@ -0,0 +1,29 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * 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/7.x/src/main/java/com/marcozanon/macaco/MInformation.java b/7.x/src/main/java/com/marcozanon/macaco/MInformation.java new file mode 100644 index 0000000..b50ebd7 --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/MInformation.java @@ -0,0 +1,49 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * See LICENSE for details. + */ + +package com.marcozanon.macaco; + +import java.io.PrintWriter; +import java.io.StringWriter; + +public class MInformation extends MObject { + + /* Generic information. */ + + public static String getMacacoVersion() { + return MConstants.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-2022 by Marco Zanon." + System.getProperty("line.separator")); + s.append("Released under MIT license (see LICENSE.MIT.sample for details)." + System.getProperty("line.separator")); + s.append("Small portions inspired by other projects or web pages. See source code for additional information."); + return s.toString(); + } + + /* Throwables. */ + + public static String getThrowableAsString(Throwable throwable) { + if (null == throwable) { + throw new IllegalArgumentException("Invalid 'throwable': null."); + } + // + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + throwable.printStackTrace(pw); + pw.flush(); + sw.flush(); + // + return sw.toString(); + } + +} diff --git a/7.x/src/main/java/com/marcozanon/macaco/MObject.java b/7.x/src/main/java/com/marcozanon/macaco/MObject.java new file mode 100644 index 0000000..a1d6233 --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/MObject.java @@ -0,0 +1,23 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * See LICENSE for details. + */ + +package com.marcozanon.macaco; + +public abstract class MObject { + + /* */ + + @Override + protected MObject clone() { + throw new UnsupportedOperationException("Please provide manually by yourself."); + } + + @Override + public String toString() { + throw new UnsupportedOperationException("Please use appropriate methods (if any)."); + } + +} diff --git a/7.x/src/main/java/com/marcozanon/macaco/conversion/MConversionException.java b/7.x/src/main/java/com/marcozanon/macaco/conversion/MConversionException.java new file mode 100644 index 0000000..36efa74 --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/conversion/MConversionException.java @@ -0,0 +1,31 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * 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/7.x/src/main/java/com/marcozanon/macaco/conversion/MDateConverter.java b/7.x/src/main/java/com/marcozanon/macaco/conversion/MDateConverter.java new file mode 100644 index 0000000..43dec3b --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/conversion/MDateConverter.java @@ -0,0 +1,238 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * 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.LinkedHashSet; +import java.util.Locale; +import java.util.TimeZone; + +public class MDateConverter extends MObject { + + protected LinkedHashSet<String> dateFormats = new LinkedHashSet<String>(); + protected Locale locale = null; + protected TimeZone timeZone = null; + + /* */ + + public MDateConverter(String defaultDateFormat, Locale locale) { + this(defaultDateFormat, locale, TimeZone.getDefault()); + } + + public MDateConverter(String defaultDateFormat, Locale locale, TimeZone timeZone) { + super(); + // + this.addDateFormat(defaultDateFormat); + this.setLocale(locale); + this.setTimeZone(timeZone); + } + + public MDateConverter(LinkedHashSet<String> dateFormats, Locale locale, TimeZone timeZone) { + super(); + // + this.setDateFormats(dateFormats); + this.setLocale(locale); + this.setTimeZone(timeZone); + } + + @Override + public MDateConverter clone() { + return new MDateConverter(this.getDateFormats(), this.getLocale(), this.getTimeZone()); + } + + /* Date formats. */ + + public void setDateFormats(LinkedHashSet<String> dateFormats) { + if (null == dateFormats) { + throw new IllegalArgumentException("Invalid 'dateFormats': null."); + } + else if (0 == dateFormats.size()) { + throw new IllegalArgumentException("Invalid 'dateFormats': empty."); + } + else { + for (String dateFormat: dateFormats) { + MDateConverter.checkDateFormat(dateFormat); + } + } + // + synchronized (this.dateFormats) { + this.dateFormats = dateFormats; + } + } + + public void addDateFormat(String dateFormat) { + MDateConverter.checkDateFormat(dateFormat); + // + synchronized (this.dateFormats) { + this.dateFormats.add(dateFormat); + } + } + + public LinkedHashSet<String> getDateFormats() { + return this.dateFormats; + } + + public String getDefaultDateFormat() { + return this.getDateFormats().iterator().next(); + } + + /* Locale. */ + + protected void setLocale(Locale locale) { + if (null == locale) { + throw new IllegalArgumentException("Invalid 'locale': null."); + } + // + this.locale = locale; + } + + public Locale getLocale() { + return this.locale; + } + + /* Time zone. */ + + protected void setTimeZone(TimeZone timeZone) { + if (null == timeZone) { + throw new IllegalArgumentException("Invalid 'timeZone': null."); + } + // + this.timeZone = timeZone; + } + + public TimeZone getTimeZone() { + return this.timeZone; + } + + /* Conversions. */ + + protected static void checkDateFormat(String dateFormat) { + if (MText.isBlank(dateFormat)) { + throw new IllegalArgumentException("Invalid 'dateFormat': null or empty."); + } + // + try { + SimpleDateFormat testDateFormat = new SimpleDateFormat(dateFormat); + } + catch (IllegalArgumentException exception) { + throw new IllegalArgumentException(String.format("Invalid 'dateFormat': %s.", dateFormat)); // no need to propagate exception + } + } + + protected static Date getDateFromStringByParameters(String x, String inputDateFormat, Locale inputLocale, TimeZone inputTimeZone) throws MInvalidConversionFormatException { + if (MText.isBlank(x)) { + throw new IllegalArgumentException("Invalid 'x': null or empty."); + } + MDateConverter.checkDateFormat(inputDateFormat); + if (null == inputLocale) { + throw new IllegalArgumentException("Invalid 'inputLocale': null."); + } + if (null == inputTimeZone) { + throw new IllegalArgumentException("Invalid 'inputTimeZone': null."); + } + // + Calendar c1 = Calendar.getInstance(inputTimeZone, inputLocale); + c1.clear(); + c1.setLenient(false); + SimpleDateFormat sdf = new SimpleDateFormat(inputDateFormat, inputLocale); + sdf.setCalendar(c1); + Date d1 = null; + try { + d1 = sdf.parse(x); + } + catch (ParseException exception) { + throw new MInvalidConversionFormatException(String.format("Invalid 'x' or parsing: %s (input format: %s).", x, inputDateFormat)); // no need to propagate exception + } + Calendar c2 = Calendar.getInstance(inputTimeZone, inputLocale); + c2.clear(); + c2.set(Calendar.YEAR, c1.get(Calendar.YEAR)); + c2.set(Calendar.MONTH, c1.get(Calendar.MONTH)); + c2.set(Calendar.DAY_OF_MONTH, c1.get(Calendar.DAY_OF_MONTH)); + c2.set(Calendar.HOUR_OF_DAY, c1.get(Calendar.HOUR_OF_DAY)); + c2.set(Calendar.MINUTE, c1.get(Calendar.MINUTE)); + c2.set(Calendar.SECOND, c1.get(Calendar.SECOND)); + Date d2 = c2.getTime(); + if (!x.equals(sdf.format(d2))) { + throw new MInvalidConversionFormatException(String.format("Invalid 'x' or parsing: %s (input format: %s).", x, inputDateFormat)); + } + return d2; + } + + protected static String getStringFromDateByParameters(Date date, String outputDateFormat, Locale outputLocale, TimeZone outputTimeZone) { + if (null == date) { + throw new IllegalArgumentException("Invalid 'date': null."); + } + MDateConverter.checkDateFormat(outputDateFormat); + if (null == outputLocale) { + throw new IllegalArgumentException("Invalid 'outputLocale': null."); + } + if (null == outputTimeZone) { + throw new IllegalArgumentException("Invalid 'outputTimeZone': null."); + } + // + SimpleDateFormat sdf = new SimpleDateFormat(outputDateFormat, outputLocale); + sdf.setCalendar(Calendar.getInstance(outputTimeZone, outputLocale)); + return sdf.format(date); + } + + public Date getDateFromString(String x) throws MInvalidConversionFormatException { + Date y = null; + for (String dateFormat: this.getDateFormats()) { + try { + y = MDateConverter.getDateFromStringByParameters(x, dateFormat, this.getLocale(), this.getTimeZone()); + return y; + } + catch (MInvalidConversionFormatException exception) { + } + } + if (null == y) { + throw new MInvalidConversionFormatException(String.format("Invalid 'x': %s.", x)); + } + return y; // necessary to avoid Java compilation errors + } + + public String getStringFromDate(Date x) { + return MDateConverter.getStringFromDateByParameters(x, this.getDefaultDateFormat(), this.getLocale(), this.getTimeZone()); + } + + /* Helpers. */ + + public static Date getFlatDate(Date x) { + if (null == x) { + return x; + } + // + Calendar calendar = new GregorianCalendar(); + calendar.setTime(x); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + return calendar.getTime(); + } + + public static Date getCeilDate(Date x) { + if (null == x) { + return x; + } + // + Calendar calendar = new GregorianCalendar(); + calendar.setTime(x); + calendar.set(Calendar.HOUR_OF_DAY, 23); + calendar.set(Calendar.MINUTE, 59); + calendar.set(Calendar.SECOND, 59); + calendar.set(Calendar.MILLISECOND, 999); + return calendar.getTime(); + } + +} diff --git a/7.x/src/main/java/com/marcozanon/macaco/conversion/MInvalidConversionFormatException.java b/7.x/src/main/java/com/marcozanon/macaco/conversion/MInvalidConversionFormatException.java new file mode 100644 index 0000000..95179d3 --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/conversion/MInvalidConversionFormatException.java @@ -0,0 +1,30 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * See LICENSE for details. + */ + +package com.marcozanon.macaco.conversion; + +@SuppressWarnings("serial") +public class MInvalidConversionFormatException extends MConversionException { + + /* */ + + public MInvalidConversionFormatException() { + super(); + } + + public MInvalidConversionFormatException(String message) { + super(message); + } + + public MInvalidConversionFormatException(Throwable error) { + super(error); + } + + public MInvalidConversionFormatException(String message, Throwable error) { + super(message, error); + } + +} diff --git a/7.x/src/main/java/com/marcozanon/macaco/conversion/MLocalDateConverter.java b/7.x/src/main/java/com/marcozanon/macaco/conversion/MLocalDateConverter.java new file mode 100644 index 0000000..fc52143 --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/conversion/MLocalDateConverter.java @@ -0,0 +1,161 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * See LICENSE for details. + */ + +package com.marcozanon.macaco.conversion; + +import com.marcozanon.macaco.MObject; +import com.marcozanon.macaco.text.MText; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.format.ResolverStyle; +import java.util.LinkedHashSet; +import java.util.Locale; + +public class MLocalDateConverter extends MObject { + + protected LinkedHashSet<String> dateFormats = new LinkedHashSet<String>(); + protected Locale locale = null; + + /* */ + + public MLocalDateConverter(String defaultDateFormat, Locale locale) { + super(); + // + this.addDateFormat(defaultDateFormat); + this.setLocale(locale); + } + + public MLocalDateConverter(LinkedHashSet<String> dateFormats, Locale locale) { + super(); + // + this.setDateFormats(dateFormats); + this.setLocale(locale); + } + + @Override + public MLocalDateConverter clone() { + return new MLocalDateConverter(this.getDateFormats(), this.getLocale()); + } + + /* Date formats. */ + + public void setDateFormats(LinkedHashSet<String> dateFormats) { + if (null == dateFormats) { + throw new IllegalArgumentException("Invalid 'dateFormats': null."); + } + else if (0 == dateFormats.size()) { + throw new IllegalArgumentException("Invalid 'dateFormats': empty."); + } + else { + for (String dateFormat: dateFormats) { + MLocalDateConverter.checkDateFormat(dateFormat); + } + } + // + synchronized (this.dateFormats) { + this.dateFormats = dateFormats; + } + } + + public void addDateFormat(String dateFormat) { + MLocalDateConverter.checkDateFormat(dateFormat); + // + synchronized (this.dateFormats) { + this.dateFormats.add(dateFormat); + } + } + + public LinkedHashSet<String> getDateFormats() { + return this.dateFormats; + } + + public String getDefaultDateFormat() { + return this.getDateFormats().iterator().next(); + } + + /* Locale. */ + + protected void setLocale(Locale locale) { + if (null == locale) { + throw new IllegalArgumentException("Invalid 'locale': null."); + } + // + this.locale = locale; + } + + public Locale getLocale() { + return this.locale; + } + + /* Conversions. */ + + protected static void checkDateFormat(String dateFormat) { + if (MText.isBlank(dateFormat)) { + throw new IllegalArgumentException("Invalid 'dateFormat': null or empty."); + } + // + try { + DateTimeFormatter.ofPattern(dateFormat).withResolverStyle(ResolverStyle.STRICT); + } + catch (IllegalArgumentException exception) { + throw new IllegalArgumentException(String.format("Invalid 'dateFormat': %s.", dateFormat)); // no need to propagate exception + } + } + + protected static LocalDate getDateFromStringByParameters(String x, String inputDateFormat, Locale inputLocale) throws MInvalidConversionFormatException { + if (MText.isBlank(x)) { + throw new IllegalArgumentException("Invalid 'x': null or empty."); + } + MLocalDateConverter.checkDateFormat(inputDateFormat); + if (null == inputLocale) { + throw new IllegalArgumentException("Invalid 'inputLocale': null."); + } + // + LocalDate d = null; + try { + d = LocalDate.parse(x, DateTimeFormatter.ofPattern(inputDateFormat, inputLocale).withResolverStyle(ResolverStyle.STRICT)); + } + catch (DateTimeParseException exception) { + throw new MInvalidConversionFormatException(String.format("Invalid 'x' or parsing: %s (input format: %s).", x, inputDateFormat)); // no need to propagate exception + } + // + return d; + } + + protected static String getStringFromDateByParameters(LocalDate date, String outputDateFormat, Locale outputLocale) { + if (null == date) { + throw new IllegalArgumentException("Invalid 'date': null."); + } + MLocalDateConverter.checkDateFormat(outputDateFormat); + if (null == outputLocale) { + throw new IllegalArgumentException("Invalid 'outputLocale': null."); + } + // + return date.format(DateTimeFormatter.ofPattern(outputDateFormat, outputLocale).withResolverStyle(ResolverStyle.STRICT)); + } + + public LocalDate getDateFromString(String x) throws MInvalidConversionFormatException { + LocalDate y = null; + for (String dateFormat: this.getDateFormats()) { + try { + y = MLocalDateConverter.getDateFromStringByParameters(x, dateFormat, this.getLocale()); + return y; + } + catch (MInvalidConversionFormatException exception) { + } + } + if (null == y) { + throw new MInvalidConversionFormatException(String.format("Invalid 'x': %s.", x)); + } + return y; // necessary to avoid Java compilation errors + } + + public String getStringFromDate(LocalDate x) { + return MLocalDateConverter.getStringFromDateByParameters(x, this.getDefaultDateFormat(), this.getLocale()); + } + +} diff --git a/7.x/src/main/java/com/marcozanon/macaco/conversion/MLocalDateTimeConverter.java b/7.x/src/main/java/com/marcozanon/macaco/conversion/MLocalDateTimeConverter.java new file mode 100644 index 0000000..fdfa86c --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/conversion/MLocalDateTimeConverter.java @@ -0,0 +1,161 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * See LICENSE for details. + */ + +package com.marcozanon.macaco.conversion; + +import com.marcozanon.macaco.MObject; +import com.marcozanon.macaco.text.MText; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.format.ResolverStyle; +import java.util.LinkedHashSet; +import java.util.Locale; + +public class MLocalDateTimeConverter extends MObject { + + protected LinkedHashSet<String> datetimeFormats = new LinkedHashSet<String>(); + protected Locale locale = null; + + /* */ + + public MLocalDateTimeConverter(String defaultDatetimeFormat, Locale locale) { + super(); + // + this.addDatetimeFormat(defaultDatetimeFormat); + this.setLocale(locale); + } + + public MLocalDateTimeConverter(LinkedHashSet<String> datetimeFormats, Locale locale) { + super(); + // + this.setDatetimeFormats(datetimeFormats); + this.setLocale(locale); + } + + @Override + public MLocalDateTimeConverter clone() { + return new MLocalDateTimeConverter(this.getDatetimeFormats(), this.getLocale()); + } + + /* Datetime formats. */ + + public void setDatetimeFormats(LinkedHashSet<String> datetimeFormats) { + if (null == datetimeFormats) { + throw new IllegalArgumentException("Invalid 'datetimeFormats': null."); + } + else if (0 == datetimeFormats.size()) { + throw new IllegalArgumentException("Invalid 'datetimeFormats': empty."); + } + else { + for (String datetimeFormat: datetimeFormats) { + MLocalDateTimeConverter.checkDatetimeFormat(datetimeFormat); + } + } + // + synchronized (this.datetimeFormats) { + this.datetimeFormats = datetimeFormats; + } + } + + public void addDatetimeFormat(String datetimeFormat) { + MLocalDateTimeConverter.checkDatetimeFormat(datetimeFormat); + // + synchronized (this.datetimeFormats) { + this.datetimeFormats.add(datetimeFormat); + } + } + + public LinkedHashSet<String> getDatetimeFormats() { + return this.datetimeFormats; + } + + public String getDefaultDatetimeFormat() { + return this.getDatetimeFormats().iterator().next(); + } + + /* Locale. */ + + protected void setLocale(Locale locale) { + if (null == locale) { + throw new IllegalArgumentException("Invalid 'locale': null."); + } + // + this.locale = locale; + } + + public Locale getLocale() { + return this.locale; + } + + /* Conversions. */ + + protected static void checkDatetimeFormat(String datetimeFormat) { + if (MText.isBlank(datetimeFormat)) { + throw new IllegalArgumentException("Invalid 'datetimeFormat': null or empty."); + } + // + try { + DateTimeFormatter.ofPattern(datetimeFormat).withResolverStyle(ResolverStyle.STRICT); + } + catch (IllegalArgumentException exception) { + throw new IllegalArgumentException(String.format("Invalid 'datetimeFormat': %s.", datetimeFormat)); // no need to propagate exception + } + } + + protected static LocalDateTime getDatetimeFromStringByParameters(String x, String inputDatetimeFormat, Locale inputLocale) throws MInvalidConversionFormatException { + if (MText.isBlank(x)) { + throw new IllegalArgumentException("Invalid 'x': null or empty."); + } + MLocalDateTimeConverter.checkDatetimeFormat(inputDatetimeFormat); + if (null == inputLocale) { + throw new IllegalArgumentException("Invalid 'inputLocale': null."); + } + // + LocalDateTime dt = null; + try { + dt = LocalDateTime.parse(x, DateTimeFormatter.ofPattern(inputDatetimeFormat, inputLocale).withResolverStyle(ResolverStyle.STRICT)); + } + catch (DateTimeParseException exception) { + throw new MInvalidConversionFormatException(String.format("Invalid 'x' or parsing: %s (input format: %s).", x, inputDatetimeFormat)); // no need to propagate exception + } + // + return dt; + } + + protected static String getStringFromDatetimeByParameters(LocalDateTime datetime, String outputDatetimeFormat, Locale outputLocale) { + if (null == datetime) { + throw new IllegalArgumentException("Invalid 'datetime': null."); + } + MLocalDateTimeConverter.checkDatetimeFormat(outputDatetimeFormat); + if (null == outputLocale) { + throw new IllegalArgumentException("Invalid 'outputLocale': null."); + } + // + return datetime.format(DateTimeFormatter.ofPattern(outputDatetimeFormat, outputLocale).withResolverStyle(ResolverStyle.STRICT)); + } + + public LocalDateTime getDatetimeFromString(String x) throws MInvalidConversionFormatException { + LocalDateTime y = null; + for (String datetimeFormat: this.getDatetimeFormats()) { + try { + y = MLocalDateTimeConverter.getDatetimeFromStringByParameters(x, datetimeFormat, this.getLocale()); + return y; + } + catch (MInvalidConversionFormatException exception) { + } + } + if (null == y) { + throw new MInvalidConversionFormatException(String.format("Invalid 'x': %s.", x)); + } + return y; // necessary to avoid Java compilation errors + } + + public String getStringFromDatetime(LocalDateTime x) { + return MLocalDateTimeConverter.getStringFromDatetimeByParameters(x, this.getDefaultDatetimeFormat(), this.getLocale()); + } + +} diff --git a/7.x/src/main/java/com/marcozanon/macaco/conversion/MNumberConverter.java b/7.x/src/main/java/com/marcozanon/macaco/conversion/MNumberConverter.java new file mode 100644 index 0000000..5543c6c --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/conversion/MNumberConverter.java @@ -0,0 +1,165 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * See LICENSE for details. + */ + +package com.marcozanon.macaco.conversion; + +import com.marcozanon.macaco.MObject; +import com.marcozanon.macaco.text.MText; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.ParsePosition; +import java.util.LinkedHashSet; +import java.util.Locale; + +public class MNumberConverter extends MObject { + + protected LinkedHashSet<String> numberFormats = new LinkedHashSet<String>(); + protected Locale locale = null; + + /* */ + + public MNumberConverter(String defaultNumberFormat, Locale locale) { + super(); + // + this.addNumberFormat(defaultNumberFormat); + this.setLocale(locale); + } + + public MNumberConverter(LinkedHashSet<String> numberFormats, Locale locale) { + super(); + // + this.setNumberFormats(numberFormats); + this.setLocale(locale); + } + + @Override + public MNumberConverter clone() { + return new MNumberConverter(this.getNumberFormats(), this.getLocale()); + } + + /* Number formats. */ + + public void setNumberFormats(LinkedHashSet<String> numberFormats) { + if (null == numberFormats) { + throw new IllegalArgumentException("Invalid 'numberFormats': null."); + } + else if (0 == numberFormats.size()) { + throw new IllegalArgumentException("Invalid 'numberFormats': empty."); + } + else { + for (String numberFormat: numberFormats) { + MNumberConverter.checkNumberFormat(numberFormat); + } + } + // + synchronized (this.numberFormats) { + this.numberFormats = numberFormats; + } + } + + public void addNumberFormat(String numberFormat) { + MNumberConverter.checkNumberFormat(numberFormat); + // + synchronized (this.numberFormats) { + this.numberFormats.add(numberFormat); + } + } + + public LinkedHashSet<String> getNumberFormats() { + return this.numberFormats; + } + + public String getDefaultNumberFormat() { + return this.getNumberFormats().iterator().next(); + } + + /* Locale. */ + + protected void setLocale(Locale locale) { + if (null == locale) { + throw new IllegalArgumentException("Invalid 'locale': null."); + } + // + this.locale = locale; + } + + public Locale getLocale() { + return this.locale; + } + + /* Conversions. */ + + protected static void checkNumberFormat(String numberFormat) { + if (MText.isBlank(numberFormat)) { + throw new IllegalArgumentException("Invalid 'numberFormat': null or empty."); + } + // + try { + DecimalFormat testNumberFormat = new DecimalFormat(numberFormat); + } + catch (IllegalArgumentException exception) { + throw new IllegalArgumentException(String.format("Invalid 'numberFormat': %s.", numberFormat)); // no need to propagate exception + } + } + + protected static BigDecimal getNumberFromStringByParameters(String x, String inputNumberFormat, Locale inputLocale) throws MInvalidConversionFormatException { + if (MText.isBlank(x)) { + throw new IllegalArgumentException("Invalid 'x': null or empty."); + } + MNumberConverter.checkNumberFormat(inputNumberFormat); + if (null == inputLocale) { + throw new IllegalArgumentException("Invalid 'inputLocale': null."); + } + // + DecimalFormatSymbols dfs = new DecimalFormatSymbols(inputLocale); + DecimalFormat df = new DecimalFormat(inputNumberFormat, dfs); + df.setParseBigDecimal(true); + ParsePosition validPosition = new ParsePosition(0); + BigDecimal bd = (BigDecimal)df.parse(x, validPosition); + if (validPosition.getIndex() < x.length()) { + throw new MInvalidConversionFormatException(String.format("Invalid 'x' or parsing: %s (input format: %s).", x, inputNumberFormat)); + } + return bd; + } + + protected static String getStringFromNumberByParameters(BigDecimal number, String outputNumberFormat, Locale outputLocale) { + if (null == number) { + throw new IllegalArgumentException("Invalid 'number': null."); + } + MNumberConverter.checkNumberFormat(outputNumberFormat); + if (null == outputLocale) { + throw new IllegalArgumentException("Invalid 'outputLocale': null."); + } + // + DecimalFormatSymbols dfs = new DecimalFormatSymbols(outputLocale); + DecimalFormat df = new DecimalFormat(outputNumberFormat, dfs); + df.setRoundingMode(RoundingMode.HALF_UP); + return df.format(number); + } + + public BigDecimal getNumberFromString(String x) throws MInvalidConversionFormatException { + BigDecimal y = null; + for (String numberFormat: this.getNumberFormats()) { + try { + y = MNumberConverter.getNumberFromStringByParameters(x, numberFormat, this.getLocale()); + return y; + } + catch (MInvalidConversionFormatException exception) { + } + } + if (null == y) { + throw new MInvalidConversionFormatException(String.format("Invalid 'x': %s.", x)); + } + return y; // necessary to avoid Java compilation errors + } + + public String getStringFromNumber(BigDecimal x) { + return MNumberConverter.getStringFromNumberByParameters(x, this.getDefaultNumberFormat(), this.getLocale()); + } + +} diff --git a/7.x/src/main/java/com/marcozanon/macaco/database/MDatabaseConnection.java b/7.x/src/main/java/com/marcozanon/macaco/database/MDatabaseConnection.java new file mode 100644 index 0000000..9aa684d --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/database/MDatabaseConnection.java @@ -0,0 +1,342 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * See LICENSE for details. + */ + +package com.marcozanon.macaco.database; + +import com.marcozanon.macaco.MObject; +import com.marcozanon.macaco.logging.MLogListener; +import com.marcozanon.macaco.text.MText; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.LinkedHashMap; +import java.util.LinkedList; + +public class MDatabaseConnection extends MObject { + + public static enum TransactionStatus { + CLOSED, + SUCCESSFUL, + FAILED + }; + + protected String driver = null; + protected String url = null; + protected String username = null; + protected String password = null; + protected boolean localTypesMode = false; + protected MLogListener logListener = null; + + protected Connection connection = null; + + protected MDatabaseConnection.TransactionStatus transactionStatus = MDatabaseConnection.TransactionStatus.CLOSED; + + /* */ + + public MDatabaseConnection(String driver, String url, String username, String password, boolean localTypesMode, MLogListener logListener) throws MDatabaseConnectionFailureException { + super(); + // + if (MText.isBlank(driver)) { + throw new IllegalArgumentException("Invalid 'driver': null or empty."); + } + if (MText.isBlank(url)) { + throw new IllegalArgumentException("Invalid 'url': null or empty."); + } + if (null == username) { + throw new IllegalArgumentException("Invalid 'username': null."); + } + if (null == password) { + throw new IllegalArgumentException("Invalid 'password': null."); + } + // + this.driver = driver; + this.url = url; + this.username = username; + this.password = password; + this.localTypesMode = localTypesMode; + this.setLogListener(logListener); + // Load driver. + try { + Class.forName(this.getDriver()).newInstance(); + } + catch (ClassNotFoundException exception) { + throw new MDatabaseConnectionFailureException("Could not load driver.", exception); + } + catch (IllegalAccessException exception) { + throw new MDatabaseConnectionFailureException("Could not load driver.", exception); + } + catch (InstantiationException exception) { + throw new MDatabaseConnectionFailureException("Could not load driver.", exception); + } + // Initialize the connection. + this.initialize(); + } + + public void initialize() throws MDatabaseConnectionFailureException { + // Establish a connection. + try { + this.connection = DriverManager.getConnection(this.getUrl(), this.getUsername(), this.getPassword()); + } + catch (SQLException exception) { + throw new MDatabaseConnectionFailureException("Could not get database connection.", exception); + } + // Set SQL mode to standard ANSI and TRADITIONAL. + try { + this.getConnection().createStatement().executeUpdate("SET SQL_MODE = 'ANSI,TRADITIONAL'"); +/* Disabled to prevent an infinite loop. + this.logStatement("### SET SQL_MODE = 'ANSI,TRADITIONAL' ###"); +*/ + } + catch (SQLException exception) { + throw new MDatabaseConnectionFailureException("Could not set SQL mode to ANSI and TRADITIONAL.", exception); + } + } + + protected void check() throws MDatabaseConnectionFailureException { + try { + this.getConnection().createStatement().executeQuery("/* ping */ SELECT 1"); + } + catch (SQLException exception) { + this.initialize(); + } + } + + public void close() throws MDatabaseConnectionFailureException { + try { + this.getConnection().close(); + } + catch (SQLException exception) { + throw new MDatabaseConnectionFailureException("Could not close database connection.", exception); + } + } + + public boolean isClosed() throws MDatabaseConnectionFailureException { + try { + return this.getConnection().isClosed(); + } + catch (SQLException exception) { + throw new MDatabaseConnectionFailureException("Could not determine the state.", exception); + } + } + + @Override + 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; + } + + /* Local types mode. */ + + public boolean getLocalTypesMode() { + return this.localTypesMode; + } + + /* Connection. */ + + protected Connection getConnection() { + return this.connection; + } + + /* Transactions. */ + + protected void setTransactionStatus(MDatabaseConnection.TransactionStatus transactionStatus) { + if (null == transactionStatus) { + throw new IllegalArgumentException("Invalid 'transactionStatus': null."); + } + // + this.transactionStatus = transactionStatus; + } + + public MDatabaseConnection.TransactionStatus getTransactionStatus() { + return this.transactionStatus; + } + + public void startTransaction() throws MSqlTransactionException { + if (MDatabaseConnection.TransactionStatus.CLOSED != this.getTransactionStatus()) { + throw new MSqlTransactionException("Nested transactions not allowed."); + } + // + try { + // Check the connection. + this.check(); + // Start the transaction. + this.getConnection().setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); + this.getConnection().setAutoCommit(false); + this.setTransactionStatus(MDatabaseConnection.TransactionStatus.SUCCESSFUL); + this.logStatement("### BEGIN TRANSACTION ###"); + } + catch (MDatabaseConnectionFailureException exception) { + throw new MSqlTransactionException("Could not start transaction.", exception); + } + catch (SQLException exception) { + throw new MSqlTransactionException("Could not start transaction.", exception); + } + } + + public void rollBackTransaction() throws MSqlTransactionException { + if (MDatabaseConnection.TransactionStatus.CLOSED == this.getTransactionStatus()) { + throw new MSqlTransactionException("Transaction not started."); + } + // + try { +/* + // Check the connection. + this.check(); +*/ + // Cancel the transaction. + this.getConnection().rollback(); + this.getConnection().setAutoCommit(true); + this.setTransactionStatus(MDatabaseConnection.TransactionStatus.CLOSED); + this.logStatement("### ROLLBACK ###"); + } + catch (SQLException exception) { + this.setTransactionStatus(MDatabaseConnection.TransactionStatus.FAILED); + throw new MSqlTransactionException("Could not roll back transaction.", exception); + } + } + + public MDatabaseConnection.TransactionStatus commitTransaction() throws MSqlTransactionException { + switch (this.getTransactionStatus()) { + case SUCCESSFUL: + try { +/* + // Check the connection. + this.check(); +*/ + // Commit the transaction. + this.getConnection().commit(); + this.getConnection().setAutoCommit(true); + this.setTransactionStatus(MDatabaseConnection.TransactionStatus.CLOSED); + this.logStatement("### COMMIT ###"); + return MDatabaseConnection.TransactionStatus.SUCCESSFUL; + } + catch (SQLException exception) { + this.rollBackTransaction(); + return MDatabaseConnection.TransactionStatus.FAILED; + } + case FAILED: + this.rollBackTransaction(); + return MDatabaseConnection.TransactionStatus.FAILED; + default: // instead of "case CLOSED:" (necessary to avoid Java compilation errors) + throw new MSqlTransactionException("Transaction not started."); + } + } + + /* Statements. */ + + public MSqlStatementResults executePreparedStatement(String statement) throws MDatabaseConnectionFailureException, MSqlStatementException { + return this.executePreparedStatement(statement, new LinkedList<Object>()); + } + + public MSqlStatementResults executePreparedStatement(String statement, LinkedList<Object> parameters) throws MDatabaseConnectionFailureException, MSqlStatementException { + return this.executePreparedStatement(statement, parameters, this.getLocalTypesMode()); + } + + public MSqlStatementResults executePreparedStatement(String statement, LinkedList<Object> parameters, boolean localTypesMode) throws MDatabaseConnectionFailureException, MSqlStatementException { + return this.executePreparedStatement(statement, parameters, this.getLocalTypesMode(), true); + }; + + public MSqlStatementResults executePreparedStatement(String statement, LinkedList<Object> parameters, boolean localTypesMode, boolean loggableStatement) throws MDatabaseConnectionFailureException, MSqlStatementException { + if (MText.isBlank(statement)) { + throw new IllegalArgumentException("Invalid 'statement': null or empty."); + } + if (null == parameters) { + throw new IllegalArgumentException("Invalid 'parameters': null."); + } + // + MSqlStatementResults results = null; + PreparedStatement preparedStatement = null; + try { + // Check the connection. + this.check(); + // Prepare the statement. + preparedStatement = this.getConnection().prepareStatement(statement, PreparedStatement.RETURN_GENERATED_KEYS); + for (int p = 0; parameters.size() > p; p++) { + preparedStatement.setObject(p + 1, parameters.get(p)); + } + // Execute the statement and parse the results. + preparedStatement.execute(); + results = new MSqlStatementResults(preparedStatement, localTypesMode); + if (loggableStatement) { + this.logStatement(preparedStatement.toString()); + } + } + catch (MDatabaseConnectionFailureException exception) { + if (MDatabaseConnection.TransactionStatus.SUCCESSFUL == this.getTransactionStatus()) { + this.setTransactionStatus(MDatabaseConnection.TransactionStatus.FAILED); + } + throw new MSqlStatementException(String.format("Could not execute prepared statement: %s.", preparedStatement), exception); + } + catch (SQLException exception) { + if (MDatabaseConnection.TransactionStatus.SUCCESSFUL == this.getTransactionStatus()) { + this.setTransactionStatus(MDatabaseConnection.TransactionStatus.FAILED); + } + throw new MSqlStatementException(String.format("Could not execute prepared statement: %s.", preparedStatement), exception); + } + return results; + } + + /* Tables. */ + + public MSqlTable getTable(String table, String primaryKey) { + return new MSqlTable(this, table, primaryKey); + } + + /* Logging. */ + + public void setLogListener(MLogListener logListener) { + this.logListener = logListener; + } + + protected MLogListener getLogListener() { + return this.logListener; + } + + protected void logStatement(String statement) { + MLogListener logListener = this.getLogListener(); + if (null != logListener) { + logListener.onMessageLogging(statement); + } + } + + /* Engine version. */ + + public String getEngineVersion() throws MDatabaseConnectionFailureException, MSqlStatementException { + MSqlStatementResults results = this.executePreparedStatement("SELECT VERSION()"); + LinkedList<LinkedHashMap<String, Object>> resultList = results.getRecords(); + // + return (String)resultList.get(0).get("VERSION()"); + } + +} diff --git a/7.x/src/main/java/com/marcozanon/macaco/database/MDatabaseConnectionFailureException.java b/7.x/src/main/java/com/marcozanon/macaco/database/MDatabaseConnectionFailureException.java new file mode 100644 index 0000000..d96029a --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/database/MDatabaseConnectionFailureException.java @@ -0,0 +1,30 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * See LICENSE for details. + */ + +package com.marcozanon.macaco.database; + +@SuppressWarnings("serial") +public class MDatabaseConnectionFailureException extends MDatabaseException { + + /* */ + + public MDatabaseConnectionFailureException() { + super(); + } + + public MDatabaseConnectionFailureException(String message) { + super(message); + } + + public MDatabaseConnectionFailureException(Throwable error) { + super(error); + } + + public MDatabaseConnectionFailureException(String message, Throwable error) { + super(message, error); + } + +} diff --git a/7.x/src/main/java/com/marcozanon/macaco/database/MDatabaseConnectionGenerator.java b/7.x/src/main/java/com/marcozanon/macaco/database/MDatabaseConnectionGenerator.java new file mode 100644 index 0000000..0dd2a69 --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/database/MDatabaseConnectionGenerator.java @@ -0,0 +1,121 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * See LICENSE for details. + */ + +package com.marcozanon.macaco.database; + +import com.marcozanon.macaco.MObject; +import com.marcozanon.macaco.logging.MLogListener; +import com.marcozanon.macaco.text.MText; + +public class MDatabaseConnectionGenerator extends MObject { + + protected String driver = null; + protected String url = null; + protected String username = null; + protected String password = null; + protected boolean localTypesMode = false; + protected MLogListener logListener = null; + + /* */ + + public MDatabaseConnectionGenerator(String driver, String url, String username, String password, boolean localTypesMode, MLogListener logListener) { + super(); + // + if (MText.isBlank(driver)) { + throw new IllegalArgumentException("Invalid 'driver': null or empty."); + } + if (MText.isBlank(url)) { + throw new IllegalArgumentException("Invalid 'url': null or empty."); + } + if (null == username) { + throw new IllegalArgumentException("Invalid 'username': null."); + } + if (null == password) { + throw new IllegalArgumentException("Invalid 'password': null."); + } + // + this.driver = driver; + this.url = url; + this.username = username; + this.password = password; + this.localTypesMode = localTypesMode; + this.setLogListener(logListener); + } + + /* Driver. */ + + public String getDriver() { + return this.driver; + } + + /* Url. */ + + public String getUrl() { + return this.url; + } + + /* Username. */ + + public String getUsername() { + return this.username; + } + + /* Password. */ + + public String getPassword() { + return this.password; + } + + /* Local types mode. */ + + public boolean getLocalTypesMode() { + return this.localTypesMode; + } + + /* Log listener. */ + + public MLogListener getLogListener() { + return this.logListener; + } + + public void setLogListener(MLogListener logListener) { + this.logListener = logListener; + } + + /* Generator. */ + + public MDatabaseConnection getNewDatabaseConnection() throws MDatabaseConnectionFailureException { + return new MDatabaseConnection(this.getDriver(), this.getUrl(), this.getUsername(), this.getPassword(), this.getLocalTypesMode(), this.getLogListener()); + } + + public boolean isGeneratorFor(MDatabaseConnection databaseConnection) { + if (null == databaseConnection) { + throw new IllegalArgumentException("Invalid 'databaseConnection': null."); + } + // + if (!databaseConnection.getDriver().equals(this.getDriver())) { + return false; + } + if (!databaseConnection.getUrl().equals(this.getUrl())) { + return false; + } + if (!databaseConnection.getUsername().equals(this.getUsername())) { + return false; + } + if (!databaseConnection.getPassword().equals(this.getPassword())) { + return false; + } + if (databaseConnection.getLocalTypesMode() != this.getLocalTypesMode()) { + return false; + } + if (databaseConnection.getLogListener() != this.getLogListener()) { + return false; + } + // + return true; + } + +} diff --git a/7.x/src/main/java/com/marcozanon/macaco/database/MDatabaseConnectionPool.java b/7.x/src/main/java/com/marcozanon/macaco/database/MDatabaseConnectionPool.java new file mode 100644 index 0000000..40fb91e --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/database/MDatabaseConnectionPool.java @@ -0,0 +1,132 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * See LICENSE for details. + */ + +package com.marcozanon.macaco.database; + +import com.marcozanon.macaco.MObject; +import com.marcozanon.macaco.logging.MLogListener; +import java.util.LinkedList; + +public class MDatabaseConnectionPool extends MObject { + + protected MDatabaseConnectionGenerator databaseConnectionGenerator = null; + + protected LinkedList<MDatabaseConnection> databaseConnections = new LinkedList<MDatabaseConnection>(); + protected int minimumSize = 0; + protected int maximumSize = 0; + + /* */ + + public MDatabaseConnectionPool(String driver, String url, String username, String password, boolean localTypesMode, int minimumSize, int maximumSize, MLogListener logListener) throws MDatabaseConnectionFailureException { + super(); + // + if (0 > minimumSize) { + throw new IllegalArgumentException(String.format("Invalid 'minimumSize': %s.", minimumSize)); + } + if (1 > maximumSize) { + throw new IllegalArgumentException(String.format("Invalid 'maximumSize': %s.", maximumSize)); + } + else if (minimumSize > maximumSize) { + throw new IllegalArgumentException(String.format("Invalid 'maximumSize': %s < %s ('minimumSize').", maximumSize, minimumSize)); + } + // + this.databaseConnectionGenerator = new MDatabaseConnectionGenerator(driver, url, username, password, localTypesMode, logListener); + this.minimumSize = minimumSize; + this.maximumSize = maximumSize; + // + this.initialize(); + } + + protected void initialize() throws MDatabaseConnectionFailureException { + for (int c = 0; c < this.getMinimumSize(); c++) { + MDatabaseConnection databaseConnection = this.getDatabaseConnectionGenerator().getNewDatabaseConnection(); + this.pushDatabaseConnection(databaseConnection); + } + } + + protected void closeConnections() { + LinkedList<MDatabaseConnection> databaseConnections = this.getDatabaseConnections(); + while (0 < databaseConnections.size()) { + MDatabaseConnection databaseConnection = databaseConnections.removeLast(); + // + try { + databaseConnection.close(); + } + catch (MDatabaseConnectionFailureException exception) { + } + } + } + + @Override + protected void finalize() { + this.closeConnections(); + } + + /* Database connection generator. */ + + protected MDatabaseConnectionGenerator getDatabaseConnectionGenerator() { + return this.databaseConnectionGenerator; + } + + /* Database connections. */ + + protected LinkedList<MDatabaseConnection> getDatabaseConnections() { + return this.databaseConnections; + } + + protected int getMinimumSize() { + return this.minimumSize; + } + + protected int getMaximumSize() { + return this.maximumSize; + } + + public synchronized MDatabaseConnection popDatabaseConnection() throws MDatabaseConnectionFailureException { + LinkedList<MDatabaseConnection> databaseConnections = this.getDatabaseConnections(); + MDatabaseConnection databaseConnection = null; + if (0 == databaseConnections.size()) { + databaseConnection = this.getDatabaseConnectionGenerator().getNewDatabaseConnection(); + } + else { + databaseConnection = databaseConnections.removeLast(); + } + if (this.getMinimumSize() > databaseConnections.size()) { + databaseConnections.add(this.getDatabaseConnectionGenerator().getNewDatabaseConnection()); + } + return databaseConnection; + } + + public synchronized void pushDatabaseConnection(MDatabaseConnection databaseConnection) throws MDatabaseConnectionFailureException { + if (null == databaseConnection) { + throw new IllegalArgumentException("Invalid 'databaseConnection': null."); + } + else if (databaseConnection.isClosed()) { + throw new IllegalArgumentException("Invalid 'databaseConnection': closed."); + } + // + if (!this.getDatabaseConnectionGenerator().isGeneratorFor(databaseConnection)) { + databaseConnection.close(); + } + else { + LinkedList<MDatabaseConnection> databaseConnections = this.getDatabaseConnections(); + if (this.getMaximumSize() >= databaseConnections.size()) { + databaseConnections.add(databaseConnection); + } + } + } + + /* Logging. */ + + public synchronized void setLogListener(MLogListener logListener) { + this.getDatabaseConnectionGenerator().setLogListener(logListener); + // + for (MDatabaseConnection databaseConnection: this.getDatabaseConnections()) { + databaseConnection.setLogListener(logListener); + } + } + +} diff --git a/7.x/src/main/java/com/marcozanon/macaco/database/MDatabaseException.java b/7.x/src/main/java/com/marcozanon/macaco/database/MDatabaseException.java new file mode 100644 index 0000000..2637efe --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/database/MDatabaseException.java @@ -0,0 +1,31 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * See LICENSE for details. + */ + +package com.marcozanon.macaco.database; + +import com.marcozanon.macaco.MException; + +public abstract class MDatabaseException extends MException { + + /* */ + + public MDatabaseException() { + super(); + } + + public MDatabaseException(String message) { + super(message); + } + + public MDatabaseException(Throwable error) { + super(error); + } + + public MDatabaseException(String message, Throwable error) { + super(message, error); + } + +} diff --git a/7.x/src/main/java/com/marcozanon/macaco/database/MSqlStatementException.java b/7.x/src/main/java/com/marcozanon/macaco/database/MSqlStatementException.java new file mode 100644 index 0000000..ff2e4e2 --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/database/MSqlStatementException.java @@ -0,0 +1,30 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * See LICENSE for details. + */ + +package com.marcozanon.macaco.database; + +@SuppressWarnings("serial") +public class MSqlStatementException extends MDatabaseException { + + /* */ + + public MSqlStatementException() { + super(); + } + + public MSqlStatementException(String message) { + super(message); + } + + public MSqlStatementException(Throwable error) { + super(error); + } + + public MSqlStatementException(String message, Throwable error) { + super(message, error); + } + +} diff --git a/7.x/src/main/java/com/marcozanon/macaco/database/MSqlStatementResults.java b/7.x/src/main/java/com/marcozanon/macaco/database/MSqlStatementResults.java new file mode 100644 index 0000000..3322c73 --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/database/MSqlStatementResults.java @@ -0,0 +1,149 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * See LICENSE for details. + */ + +package com.marcozanon.macaco.database; + +import com.marcozanon.macaco.MObject; +import com.marcozanon.macaco.text.MText; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; + +public class MSqlStatementResults extends MObject { + + protected PreparedStatement preparedStatement = null; + protected boolean localTypesMode = false; + + protected ResultSet resultSet = null; + + protected LinkedList<LinkedHashMap<String, Object>> records = new LinkedList<LinkedHashMap<String, Object>>(); + + protected LinkedHashSet<Object> generatedKeys = new LinkedHashSet<Object>(); + + /* */ + + public MSqlStatementResults(PreparedStatement preparedStatement, boolean localTypesMode) throws MSqlStatementException { + super(); + // + if (null == preparedStatement) { + throw new IllegalArgumentException("Invalid 'preparedStatement': null."); + } + // + this.preparedStatement = preparedStatement; + this.localTypesMode = localTypesMode; + // + try { + this.resultSet = this.getPreparedStatement().getResultSet(); + if (null != this.getResultSet()) { + while (this.getResultSet().next()) { + ResultSetMetaData resultSetMetaData = this.getResultSet().getMetaData(); + LinkedHashMap<String, Object> record = new LinkedHashMap<String, Object>(); + // + for (int f = 0; resultSetMetaData.getColumnCount() > f; f++) { + String field = resultSetMetaData.getColumnLabel(f + 1); + Object value = this.getResultSet().getObject(field); + // + if ((value instanceof java.sql.Date) && (this.getLocalTypesMode())) { + value = ((java.sql.Date)value).toLocalDate(); + } + else if ((value instanceof java.sql.Time) && (this.getLocalTypesMode())) { + value = ((java.sql.Time)value).toLocalTime(); + } + else if ((value instanceof java.sql.Timestamp) && (this.getLocalTypesMode())) { + value = ((java.sql.Timestamp)value).toLocalDateTime(); + } + // + record.put(field, value); + } + // + this.getRecords().add(record); + } + } + } + catch (SQLException exception) { + throw new MSqlStatementException("Could not retrieve statement records.", exception); + } + // + try { + ResultSet generatedKeysResultSet = this.getPreparedStatement().getGeneratedKeys(); + while (generatedKeysResultSet.next()) { + this.getGeneratedKeys().add(generatedKeysResultSet.getObject(1)); + } + generatedKeysResultSet.close(); + } + catch (SQLException exception) { + throw new MSqlStatementException("Could not retrieve generated keys.", exception); + } + } + + public void close() throws MDatabaseConnectionFailureException { + try { + if (null != this.getResultSet()) { + this.getResultSet().close(); + } + this.getPreparedStatement().close(); + } + catch (SQLException exception) { + throw new MDatabaseConnectionFailureException("Could not close statement and/or result set references.", exception); + } + } + + @Override + protected void finalize() { + try { + this.close(); + } + catch (Exception exception) { + } + } + + /* References. */ + + protected PreparedStatement getPreparedStatement() { + return this.preparedStatement; + } + + public ResultSet getResultSet() { + return this.resultSet; + } + + public boolean getLocalTypesMode() { + return this.localTypesMode; + } + + /* Records. */ + + public LinkedList<LinkedHashMap<String, Object>> getRecords() { + return this.records; + } + + public LinkedList<Object> getRecordsByField(String field) { + if (MText.isBlank(field)) { + throw new IllegalArgumentException("Invalid 'field': null or empty."); + } + // + LinkedList<Object> recordsByField = new LinkedList<Object>(); + for (LinkedHashMap<String, Object> record: this.getRecords()) { + Object r = record.get(field); + if (null == r) { + throw new IllegalArgumentException(String.format("Invalid 'field': %s.", field)); + } + recordsByField.add(r); + } + return recordsByField; + } + + /* Generated keys. */ + + public LinkedHashSet<Object> getGeneratedKeys() { + return this.generatedKeys; + } + +} diff --git a/7.x/src/main/java/com/marcozanon/macaco/database/MSqlTable.java b/7.x/src/main/java/com/marcozanon/macaco/database/MSqlTable.java new file mode 100644 index 0000000..cfdf380 --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/database/MSqlTable.java @@ -0,0 +1,172 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * See LICENSE for details. + */ + +package com.marcozanon.macaco.database; + +import com.marcozanon.macaco.MObject; +import com.marcozanon.macaco.text.MText; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.StringJoiner; + +public class MSqlTable extends MObject { + + protected MDatabaseConnection databaseConnection = null; + protected String table = null; + protected String primaryKey = null; + + /* */ + + public MSqlTable(MDatabaseConnection databaseConnection, String table, String primaryKey) { + super(); + // + if (null == databaseConnection) { + throw new IllegalArgumentException("Invalid 'databaseConnection': null."); + } + if (MText.isBlank(table)) { + throw new IllegalArgumentException("Invalid 'table': null or empty."); + } + if (MText.isBlank(primaryKey)) { + throw new IllegalArgumentException("Invalid 'primaryKey': null or empty."); + } + // + this.databaseConnection = databaseConnection; + this.table = table; + this.primaryKey = primaryKey; + } + + /* Database connection. */ + + protected MDatabaseConnection getDatabaseConnection() { + return this.databaseConnection; + } + + /* Table. */ + + public String getTable() { + return this.table; + } + + /* Primary key. */ + + public String getPrimaryKey() { + return this.primaryKey; + } + + /* Statements. */ + + protected MSqlStatementResults insertRecord(LinkedHashMap<String, Object> map, boolean loggableStatement) throws MDatabaseConnectionFailureException, MSqlStatementException { + if (null == map) { + throw new IllegalArgumentException("Invalid 'map': null."); + } + // + LinkedList<Object> p = new LinkedList<Object>(); + StringBuilder fields = new StringBuilder(""); + StringBuilder s = new StringBuilder(""); + for (String field: map.keySet()) { + fields.append("\"" + this.getTable() + "\".\"" + field + "\", "); + s.append("?, "); + p.add(map.get(field)); + } + fields.delete(fields.length() - 2, fields.length()); + s.delete(s.length() - 2, s.length()); + // + return this.getDatabaseConnection().executePreparedStatement(" INSERT INTO \"" + this.getTable() + "\"" + + " (" + fields + ")" + + " VALUES (" + s + ")", + p, true, loggableStatement); + } + + protected MSqlStatementResults updateRecord(LinkedHashMap<String, Object> map, Object id, boolean loggableStatement) throws MDatabaseConnectionFailureException, MSqlStatementException { + if (null == map) { + throw new IllegalArgumentException("Invalid 'map': null."); + } + if (null == id) { + throw new IllegalArgumentException("Invalid 'id': null."); + } + // + LinkedList<Object> p = new LinkedList<Object>(); + StringBuilder s = new StringBuilder(""); + for (String field: map.keySet()) { + s.append("\"" + this.getTable() + "\".\"" + field + "\" = ?, "); + p.add(map.get(field)); + } + s.delete(s.length() - 2, s.length()); + p.add(id); + // + return this.getDatabaseConnection().executePreparedStatement(" UPDATE \"" + this.getTable() + "\"" + + " SET " + s + + " WHERE (\"" + this.getTable() + "\".\"" + this.getPrimaryKey() + "\" = ?)", + p, true, loggableStatement); + } + + public MSqlStatementResults setRecord(LinkedHashMap<String, Object> map, Object id) throws MDatabaseConnectionFailureException, MSqlStatementException { + return this.setRecord(map, id, true); + } + + public MSqlStatementResults setRecord(LinkedHashMap<String, Object> map, Object id, boolean loggableStatement) throws MDatabaseConnectionFailureException, MSqlStatementException { + if (null == id) { + return this.insertRecord(map, loggableStatement); + } + else { + return this.updateRecord(map, id, loggableStatement); + } + } + + public MSqlStatementResults getRecord(Object id) throws MDatabaseConnectionFailureException, MSqlStatementException { + if (null == id) { + throw new IllegalArgumentException("Invalid 'id': null."); + } + // + LinkedList<Object> p = new LinkedList<Object>(); + p.add(id); + // + return this.getDatabaseConnection().executePreparedStatement(" SELECT *" + + " FROM \"" + this.getTable() + "\"" + + " WHERE (\"" + this.getTable() + "\".\"" + this.getPrimaryKey() + "\" = ?)", + p); + } + + public MSqlStatementResults deleteRecord(Object id) throws MDatabaseConnectionFailureException, MSqlStatementException { + if (null == id) { + throw new IllegalArgumentException("Invalid 'id': null."); + } + // + LinkedList<Object> p = new LinkedList<Object>(); + p.add(id); + // + return this.getDatabaseConnection().executePreparedStatement(" DELETE FROM \"" + this.getTable() + "\"" + + " WHERE (\"" + this.getTable() + "\".\"" + this.getPrimaryKey() + "\" = ?)", + p); + } + + public MSqlStatementResults deleteRecords(LinkedHashSet idSet) throws MDatabaseConnectionFailureException, MSqlStatementException { + if (null == idSet) { + throw new IllegalArgumentException("Invalid 'idSet': null."); + } + // + String whereClause = null; + LinkedList<Object> p = null; + if (0 == idSet.size()) { + whereClause = "0"; + } + else { + StringJoiner s = new StringJoiner(", "); + p = new LinkedList<Object>(); + for (Object id: idSet) { + s.add("?"); + p.add(id); + } + whereClause = "\"" + this.getTable() + "\".\"" + this.getPrimaryKey() + "\" IN (" + s.toString() + ")"; + } + // + return this.getDatabaseConnection().executePreparedStatement(" DELETE FROM \"" + this.getTable() + "\"" + + " WHERE (" + whereClause + ")", + p); + } + +} diff --git a/7.x/src/main/java/com/marcozanon/macaco/database/MSqlTransactionException.java b/7.x/src/main/java/com/marcozanon/macaco/database/MSqlTransactionException.java new file mode 100644 index 0000000..b6554d8 --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/database/MSqlTransactionException.java @@ -0,0 +1,30 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * See LICENSE for details. + */ + +package com.marcozanon.macaco.database; + +@SuppressWarnings("serial") +public class MSqlTransactionException extends MDatabaseException { + + /* */ + + public MSqlTransactionException() { + super(); + } + + public MSqlTransactionException(String message) { + super(message); + } + + public MSqlTransactionException(Throwable error) { + super(error); + } + + public MSqlTransactionException(String message, Throwable error) { + super(message, error); + } + +} diff --git a/7.x/src/main/java/com/marcozanon/macaco/json/MInvalidJsonValueException.java b/7.x/src/main/java/com/marcozanon/macaco/json/MInvalidJsonValueException.java new file mode 100644 index 0000000..d22771f --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/json/MInvalidJsonValueException.java @@ -0,0 +1,30 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * See LICENSE for details. + */ + +package com.marcozanon.macaco.json; + +@SuppressWarnings("serial") +public class MInvalidJsonValueException extends MJsonException { + + /* */ + + public MInvalidJsonValueException() { + super(); + } + + public MInvalidJsonValueException(String message) { + super(message); + } + + public MInvalidJsonValueException(Throwable error) { + super(error); + } + + public MInvalidJsonValueException(String message, Throwable error) { + super(message, error); + } + +} diff --git a/7.x/src/main/java/com/marcozanon/macaco/json/MJsonArray.java b/7.x/src/main/java/com/marcozanon/macaco/json/MJsonArray.java new file mode 100644 index 0000000..ff94a5a --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/json/MJsonArray.java @@ -0,0 +1,210 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * See LICENSE for details. + */ + +package com.marcozanon.macaco.json; + +import com.marcozanon.macaco.text.MText; +import java.util.LinkedList; + +public class MJsonArray extends MJsonValue { + + protected static enum ParsingMode { + START, + POST_VALUE, + POST_VALUE_SEPARATOR, + ERROR + }; + + protected LinkedList<MJsonValue> values = new LinkedList<MJsonValue>(); + + /* */ + + public MJsonArray() throws MInvalidJsonValueException { + this("[]"); + } + + public MJsonArray(String x) throws MInvalidJsonValueException { + super(); + // + this.parseString(x); + } + + @Override + public MJsonArray clone() { + MJsonArray tmpMJsonArray = null; + try { + tmpMJsonArray = new MJsonArray(this.getJsonValue()); + } + catch (MInvalidJsonValueException exception) { // cannot happen + } + return tmpMJsonArray; + } + + /* Value handlers. */ + + public void addValue(MJsonValue x) { + if (null == x) { + throw new IllegalArgumentException("Invalid 'x': null."); + } + // + this.getValues().add(x); + } + + public void setValue(int index, MJsonValue x) { + if ((0 > index) || ((this.getValueCount() - 1) < index)) { + throw new IllegalArgumentException(String.format("Invalid 'index': %s: out of range.", index)); + } + if (null == x) { + throw new IllegalArgumentException("Invalid 'x': null."); + } + // + this.getValues().set(index, x); + } + + protected LinkedList<MJsonValue> getValues() { + return this.values; + } + + public MJsonValue getValue(int index) { + if ((0 > index) || ((this.getValueCount() - 1) < index)) { + throw new IllegalArgumentException(String.format("Invalid 'index': %s: out of range.", index)); + } + // + return this.getValues().get(index).clone(); + } + + public int getValueCount() { + return this.getValues().size(); + } + + public void removeValue(int index) { + if ((0 > index) || ((this.getValueCount() - 1) < index)) { + throw new IllegalArgumentException(String.format("Invalid 'index': %s: out of range.", index)); + } + // + this.getValues().remove(index); + } + + public void clearValues() { + this.getValues().clear(); + } + + /* Parsers. */ + + protected static int getTokenLength(String x) { + if (null == x) { + throw new IllegalArgumentException("Invalid 'x': null."); + } + // + if ((2 > x.length()) || ('[' != x.charAt(0))) { + return 0; + } + int position = x.indexOf("]", 1); + while (-1 < position) { + try { + MJsonArray testValue = new MJsonArray(); + testValue.parseString(x.substring(0, position + 1)); + return position + 1; + } + catch (MInvalidJsonValueException exception) { + position = x.indexOf("]", position + 1); + } + } + return 0; + } + + public void parseString(String x) throws MInvalidJsonValueException { + if (MText.isBlank(x)) { + throw new IllegalArgumentException("Invalid 'x': null or empty."); + } + // + if ((2 > x.length()) || ('[' != x.charAt(0)) || (']' != x.charAt(x.length() - 1))) { + throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: must begin and end with square brackets.", x)); + } + String y = x.substring(1, x.length() - 1); + int parsingPosition = 0; + MJsonArray.ParsingMode parsingMode = MJsonArray.ParsingMode.START; + while ((y.length() > parsingPosition) && (MJsonArray.ParsingMode.ERROR != parsingMode)) { + int c = y.codePointAt(parsingPosition); + switch (parsingMode) { + case START: + case POST_VALUE_SEPARATOR: // a value is expected + if (MJsonValue.isWhitespace(c)) { + parsingPosition++; + } + else if ((MJsonValue.isValueSeparator(c)) || (MJsonValue.isNameSeparator(c)) || (MJsonValue.isClosingStructuralCharacter(c))) { + parsingMode = MJsonArray.ParsingMode.ERROR; + } + else { + int offset = 0; + if (0 < (offset = MJsonNull.getTokenLength(y.substring(parsingPosition)))) { + this.addValue(new MJsonNull(y.substring(parsingPosition, parsingPosition + offset))); + } + else if (0 < (offset = MJsonBoolean.getTokenLength(y.substring(parsingPosition)))) { + this.addValue(new MJsonBoolean(y.substring(parsingPosition, parsingPosition + offset))); + } + else if (0 < (offset = MJsonNumber.getTokenLength(y.substring(parsingPosition)))) { + this.addValue(new MJsonNumber(y.substring(parsingPosition, parsingPosition + offset))); + } + else if (0 < (offset = MJsonString.getTokenLength(y.substring(parsingPosition)))) { + this.addValue(new MJsonString(y.substring(parsingPosition, parsingPosition + offset))); + } + else if (0 < (offset = MJsonArray.getTokenLength(y.substring(parsingPosition)))) { + this.addValue(new MJsonArray(y.substring(parsingPosition, parsingPosition + offset))); + } + else if (0 < (offset = MJsonObject.getTokenLength(y.substring(parsingPosition)))) { + this.addValue(new MJsonObject(y.substring(parsingPosition, parsingPosition + offset))); + } + if (0 < offset) { + parsingPosition += offset; + parsingMode = MJsonArray.ParsingMode.POST_VALUE; + } + else { + parsingMode = MJsonArray.ParsingMode.ERROR; + } + } + break; + case POST_VALUE: + if (MJsonValue.isWhitespace(c)) { + parsingPosition++; + } + else if (MJsonValue.isValueSeparator(c)) { + parsingPosition++; + parsingMode = MJsonArray.ParsingMode.POST_VALUE_SEPARATOR; + } + else { + parsingMode = MJsonArray.ParsingMode.ERROR; + } + break; + case ERROR: // malformed string + break; + } + } + if (MJsonArray.ParsingMode.POST_VALUE_SEPARATOR == parsingMode) { + parsingMode = MJsonArray.ParsingMode.ERROR; + } + if (MJsonArray.ParsingMode.ERROR == parsingMode) { + throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json array.", x)); + } + } + + /* Formatter. */ + + @Override + public String getJsonValue() { + StringBuilder s = new StringBuilder(""); + s.append("["); + for (int i = 0; this.getValueCount() > i; i++) { + if (0 < i) { + s.append(", "); + } + s.append(this.getValues().get(i).getJsonValue()); + } + s.append("]"); + return s.toString(); + } + +} diff --git a/7.x/src/main/java/com/marcozanon/macaco/json/MJsonBoolean.java b/7.x/src/main/java/com/marcozanon/macaco/json/MJsonBoolean.java new file mode 100644 index 0000000..c1f26ef --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/json/MJsonBoolean.java @@ -0,0 +1,86 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * See LICENSE for details. + */ + +package com.marcozanon.macaco.json; + +import com.marcozanon.macaco.text.MText; + +public class MJsonBoolean extends MJsonValue { + + protected Boolean value = null; + + /* */ + + public MJsonBoolean() throws MInvalidJsonValueException { + this("false"); + } + + public MJsonBoolean(String x) throws MInvalidJsonValueException { + super(); + // + this.parseString(x); + } + + @Override + public MJsonBoolean clone() { + MJsonBoolean tmpMJsonBoolean = null; + try { + tmpMJsonBoolean = new MJsonBoolean(this.getJsonValue()); + } + catch (MInvalidJsonValueException exception) { // cannot happen + } + return tmpMJsonBoolean; + } + + /* Value handlers. */ + + public void setValue(Boolean x) { + if (null == x) { + throw new IllegalArgumentException("Invalid 'x': null."); + } + // + this.value = x; + } + + public Boolean getValue() { + return this.value; + } + + /* Parsers. */ + + protected static int getTokenLength(String x) { + if (null == x) { + throw new IllegalArgumentException("Invalid 'x': null."); + } + // + if (x.startsWith("true")) { + return "true".length(); + } + else if (x.startsWith("false")) { + return "false".length(); + } + return 0; + } + + public void parseString(String x) throws MInvalidJsonValueException { + if (MText.isBlank(x)) { + throw new IllegalArgumentException("Invalid 'x': null or empty."); + } + // + if ((!"true".equals(x)) && (!"false".equals(x))) { + throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json boolean.", x)); + } + this.setValue(Boolean.valueOf(x)); + } + + /* Formatter. */ + + @Override + public String getJsonValue() { + return this.getValue().toString(); + } + +} diff --git a/7.x/src/main/java/com/marcozanon/macaco/json/MJsonException.java b/7.x/src/main/java/com/marcozanon/macaco/json/MJsonException.java new file mode 100644 index 0000000..35bae21 --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/json/MJsonException.java @@ -0,0 +1,31 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * 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/7.x/src/main/java/com/marcozanon/macaco/json/MJsonNull.java b/7.x/src/main/java/com/marcozanon/macaco/json/MJsonNull.java new file mode 100644 index 0000000..a6e4339 --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/json/MJsonNull.java @@ -0,0 +1,66 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * See LICENSE for details. + */ + +package com.marcozanon.macaco.json; + +import com.marcozanon.macaco.text.MText; + +public class MJsonNull extends MJsonValue { + + /* */ + + public MJsonNull() throws MInvalidJsonValueException { + this("null"); + } + + public MJsonNull(String x) throws MInvalidJsonValueException { + super(); + // + this.parseString(x); + } + + @Override + public MJsonNull clone() { + MJsonNull tmpMJsonNull = null; + try { + tmpMJsonNull = new MJsonNull(this.getJsonValue()); + } + catch (MInvalidJsonValueException exception) { // cannot happen + } + return tmpMJsonNull; + } + + /* Parsers. */ + + protected static int getTokenLength(String x) { + if (null == x) { + throw new IllegalArgumentException("Invalid 'x': null."); + } + // + if (x.startsWith("null")) { + return "null".length(); + } + return 0; + } + + public void parseString(String x) throws MInvalidJsonValueException { + if (MText.isBlank(x)) { + throw new IllegalArgumentException("Invalid 'x': null or empty."); + } + // + if (!"null".equals(x)) { + throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json null.", x)); + } + } + + /* Formatter. */ + + @Override + public String getJsonValue() { + return "null"; + } + +} diff --git a/7.x/src/main/java/com/marcozanon/macaco/json/MJsonNumber.java b/7.x/src/main/java/com/marcozanon/macaco/json/MJsonNumber.java new file mode 100644 index 0000000..8e20402 --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/json/MJsonNumber.java @@ -0,0 +1,106 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * See LICENSE for details. + */ + +package com.marcozanon.macaco.json; + +import com.marcozanon.macaco.text.MText; +import java.math.BigDecimal; + +public class MJsonNumber extends MJsonValue { + + protected BigDecimal value = null; + + /* */ + + public MJsonNumber() throws MInvalidJsonValueException { + this("0"); + } + + public MJsonNumber(String x) throws MInvalidJsonValueException { + super(); + // + this.parseString(x); + } + + @Override + public MJsonNumber clone() { + MJsonNumber tmpMJsonNumber = null; + try { + tmpMJsonNumber = new MJsonNumber(this.getJsonValue()); + } + catch (MInvalidJsonValueException exception) { // cannot happen + } + return tmpMJsonNumber; + } + + /* Value handlers. */ + + public void setValue(Number x) { + if (null == x) { + throw new IllegalArgumentException("Invalid 'x': null."); + } + // + this.value = new BigDecimal(x.toString()); + } + + public BigDecimal getValue() { + return this.value; + } + + /* Parsers. */ + + protected static int getTokenLength(String x) { + if (null == x) { + throw new IllegalArgumentException("Invalid 'x': null."); + } + // + if (' ' == x.charAt(0)) { + return 0; + } + int position = 0; + int validPosition = 0; + BigDecimal y = null; + while (x.length() > position) { + if (' ' == x.charAt(position)) { + break; + } + else { + try { + y = new BigDecimal(x.substring(0, position + 1)); + position++; + validPosition = position; + } + catch (NumberFormatException exception) { + position++; + } + } + } + return validPosition; + } + + public void parseString(String x) throws MInvalidJsonValueException { + if (MText.isBlank(x)) { + throw new IllegalArgumentException("Invalid 'x': null or empty."); + } + // + BigDecimal y = null; + try { + y = new BigDecimal(x); + } + catch (NumberFormatException exception) { + throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json number.", x)); // no need to propagate exception + } + this.setValue(y); + } + + /* Formatter. */ + + @Override + public String getJsonValue() { + return this.getValue().toString(); + } + +} diff --git a/7.x/src/main/java/com/marcozanon/macaco/json/MJsonObject.java b/7.x/src/main/java/com/marcozanon/macaco/json/MJsonObject.java new file mode 100644 index 0000000..76101f0 --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/json/MJsonObject.java @@ -0,0 +1,264 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * See LICENSE for details. + */ + +package com.marcozanon.macaco.json; + +import com.marcozanon.macaco.text.MText; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; + +public class MJsonObject extends MJsonValue { + + protected static enum ParsingMode { + START, + POST_NAME, + POST_NAME_SEPARATOR, + POST_VALUE, + POST_VALUE_SEPARATOR, + ERROR + }; + + protected LinkedHashMap<String, MJsonValue> values = new LinkedHashMap<String, MJsonValue>(); + + /* */ + + public MJsonObject() throws MInvalidJsonValueException { + this("{}"); + } + + public MJsonObject(String x) throws MInvalidJsonValueException { + super(); + // + this.parseString(x); + } + + @Override + public MJsonObject clone() { + MJsonObject tmpMJsonObject = null; + try { + tmpMJsonObject = new MJsonObject(this.getJsonValue()); + } + catch (MInvalidJsonValueException exception) { // cannot happen + } + return tmpMJsonObject; + } + + /* Value handlers. */ + + public void setValue(String key, MJsonValue x) { + if (MText.isBlank(key)) { + throw new IllegalArgumentException("Invalid 'key': null or empty."); + } + if (null == x) { + throw new IllegalArgumentException("Invalid 'x': null."); + } + // + this.getValues().put(key, x); + } + + protected LinkedHashMap<String, MJsonValue> getValues() { + return this.values; + } + + public MJsonValue getValue(String key) { + if (MText.isBlank(key)) { + throw new IllegalArgumentException("Invalid 'key': null or empty."); + } + // + if (!this.containsKey(key)) { + throw new IllegalArgumentException(String.format("Invalid 'key': %s: not available.", key)); + } + return this.getValues().get(key).clone(); + } + + public int getValueCount() { + return this.getValues().size(); + } + + public void removeValue(String key) { + if (MText.isBlank(key)) { + throw new IllegalArgumentException("Invalid 'key': null or empty."); + } + // + if (!this.containsKey(key)) { + throw new IllegalArgumentException(String.format("Invalid 'key': %s: not available.", key)); + } + this.getValues().remove(key); + } + + public void clearValues() { + this.getValues().clear(); + } + + public boolean containsKey(String key) { + if (MText.isBlank(key)) { + throw new IllegalArgumentException("Invalid 'key': null or empty."); + } + // + return this.getValues().containsKey(key); + } + + public LinkedHashSet<String> getKeys() { + LinkedHashSet<String> keys = new LinkedHashSet<String>(); + for (String key: this.getValues().keySet()) { + keys.add(key); + } + return keys; + } + + /* Parsers. */ + + protected static int getTokenLength(String x) { + if (null == x) { + throw new IllegalArgumentException("Invalid 'x': null."); + } + // + if ((2 > x.length()) || ('{' != x.charAt(0))) { + return 0; + } + int position = x.indexOf("}", 1); + while (-1 < position) { + try { + MJsonObject testValue = new MJsonObject(); + testValue.parseString(x.substring(0, position + 1)); + return position + 1; + } + catch (MInvalidJsonValueException exception) { + position = x.indexOf("}", position + 1); + } + } + return 0; + } + + public void parseString(String x) throws MInvalidJsonValueException { + if (MText.isBlank(x)) { + throw new IllegalArgumentException("Invalid 'x': null or empty."); + } + // + if ((2 > x.length()) || ('{' != x.charAt(0)) || ('}' != x.charAt(x.length() - 1))) { + throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: must begin and end with curly brackets.", x)); + } + String y = x.substring(1, x.length() - 1); + int parsingPosition = 0; + MJsonObject.ParsingMode parsingMode = MJsonObject.ParsingMode.START; + String lastKey = ""; + while ((y.length() > parsingPosition) && (MJsonObject.ParsingMode.ERROR != parsingMode)) { + int c = y.codePointAt(parsingPosition); + switch (parsingMode) { + case START: + case POST_VALUE_SEPARATOR: // a name is expected + if (MJsonValue.isWhitespace(c)) { + parsingPosition++; + } + else if ((MJsonValue.isValueSeparator(c)) || (MJsonValue.isNameSeparator(c)) || (MJsonValue.isOpeningStructuralCharacter(c)) || (MJsonValue.isClosingStructuralCharacter(c))) { + parsingMode = MJsonObject.ParsingMode.ERROR; + } + else { + int offset = MJsonString.getTokenLength(y.substring(parsingPosition)); + if (0 < offset) { + lastKey = (new MJsonString(y.substring(parsingPosition, parsingPosition + offset))).getValue(); + parsingPosition += offset; + parsingMode = MJsonObject.ParsingMode.POST_NAME; + } + else { + parsingMode = MJsonObject.ParsingMode.ERROR; + } + } + break; + case POST_NAME: + if (MJsonValue.isWhitespace(c)) { + parsingPosition++; + } + else if (MJsonValue.isNameSeparator(c)) { + parsingPosition++; + parsingMode = MJsonObject.ParsingMode.POST_NAME_SEPARATOR; + } + else { + parsingMode = MJsonObject.ParsingMode.ERROR; + } + break; + case POST_NAME_SEPARATOR: // a value is expected + if (MJsonValue.isWhitespace(c)) { + parsingPosition++; + } + else if ((MJsonValue.isValueSeparator(c)) || (MJsonValue.isNameSeparator(c)) || (MJsonValue.isClosingStructuralCharacter(c))) { + parsingMode = MJsonObject.ParsingMode.ERROR; + } + else { + int offset = 0; + if (0 < (offset = MJsonNull.getTokenLength(y.substring(parsingPosition)))) { + this.setValue(lastKey, new MJsonNull(y.substring(parsingPosition, parsingPosition + offset))); + } + else if (0 < (offset = MJsonBoolean.getTokenLength(y.substring(parsingPosition)))) { + this.setValue(lastKey, new MJsonBoolean(y.substring(parsingPosition, parsingPosition + offset))); + } + else if (0 < (offset = MJsonNumber.getTokenLength(y.substring(parsingPosition)))) { + this.setValue(lastKey, new MJsonNumber(y.substring(parsingPosition, parsingPosition + offset))); + } + else if (0 < (offset = MJsonString.getTokenLength(y.substring(parsingPosition)))) { + this.setValue(lastKey, new MJsonString(y.substring(parsingPosition, parsingPosition + offset))); + } + else if (0 < (offset = MJsonArray.getTokenLength(y.substring(parsingPosition)))) { + this.setValue(lastKey, new MJsonArray(y.substring(parsingPosition, parsingPosition + offset))); + } + else if (0 < (offset = MJsonObject.getTokenLength(y.substring(parsingPosition)))) { + this.setValue(lastKey, new MJsonObject(y.substring(parsingPosition, parsingPosition + offset))); + } + if (0 < offset) { + lastKey = ""; + parsingPosition += offset; + parsingMode = MJsonObject.ParsingMode.POST_VALUE; + } + else { + parsingMode = MJsonObject.ParsingMode.ERROR; + } + } + break; + case POST_VALUE: + if (MJsonValue.isWhitespace(c)) { + parsingPosition++; + } + else if (MJsonValue.isValueSeparator(c)) { + parsingPosition++; + parsingMode = MJsonObject.ParsingMode.POST_VALUE_SEPARATOR; + } + else { + parsingMode = MJsonObject.ParsingMode.ERROR; + } + break; + case ERROR: // malformed string + break; + } + } + if ((MJsonObject.ParsingMode.POST_NAME == parsingMode) || (MJsonObject.ParsingMode.POST_NAME_SEPARATOR == parsingMode) || (MJsonObject.ParsingMode.POST_VALUE_SEPARATOR == parsingMode)) { + parsingMode = MJsonObject.ParsingMode.ERROR; + } + if (MJsonObject.ParsingMode.ERROR == parsingMode) { + throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json object.", x)); + } + } + + /* Formatter. */ + + @Override + public String getJsonValue() { + StringBuilder s = new StringBuilder(""); + s.append("{"); + int k = 0; + for (String key: this.getValues().keySet()) { + if (0 < k) { + s.append(", "); + } + s.append("\"" + MJsonString.getEscapedString(key, false) + "\""); + s.append(": "); + s.append(this.getValues().get(key).getJsonValue()); + k++; + } + s.append("}"); + return s.toString(); + } + +} diff --git a/7.x/src/main/java/com/marcozanon/macaco/json/MJsonString.java b/7.x/src/main/java/com/marcozanon/macaco/json/MJsonString.java new file mode 100644 index 0000000..2e22d12 --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/json/MJsonString.java @@ -0,0 +1,294 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * See LICENSE for details. + */ + +package com.marcozanon.macaco.json; + +import com.marcozanon.macaco.text.MText; + +public class MJsonString extends MJsonValue { + + protected boolean extendedEscapeMode = false; + + protected String value = null; + + /* */ + + public MJsonString() throws MInvalidJsonValueException { + this("\"\""); + } + + public MJsonString(String x) throws MInvalidJsonValueException { + this(x, false); + } + + public MJsonString(String x, boolean extendedEscapeMode) throws MInvalidJsonValueException { + super(); + // + this.setExtendedEscapeMode(extendedEscapeMode); + this.parseString(x); + } + + @Override + public MJsonString clone() { + MJsonString tmpMJsonString = null; + try { + tmpMJsonString = new MJsonString(this.getJsonValue()); + } + catch (MInvalidJsonValueException exception) { // cannot happen + } + return tmpMJsonString; + } + + /* Extended escape mode. */ + + public void setExtendedEscapeMode(boolean extendedEscapeMode) { + this.extendedEscapeMode = extendedEscapeMode; + } + + public boolean getExtendedEscapeMode() { + return this.extendedEscapeMode; + } + + /* Value handlers. */ + + public void setValue(String x) { + if (null == x) { + throw new IllegalArgumentException("Invalid 'x': null."); + } + // + this.value = x; + } + + public String getValue() { + return this.value; + } + + /* Helpers. */ + + protected static String getEscapedString(String x, boolean extendedEscapeMode) { + if (null == x) { + throw new IllegalArgumentException("Invalid 'x': null."); + } + // + StringBuilder y = new StringBuilder(); + for (int i = 0; x.length() > i; i++) { + int c = x.codePointAt(i); + if (extendedEscapeMode) { + if (0x00001F >= c) { // control Ascii character (0x000000-0x00001F) + y.append("\\u" + String.format("%4H", c).replace(" ", "0")); + } + else if (0x00007F >= c) { // non-control Ascii character (0x000020-0x00007F) + if ('"' == c) { + y.append("\\\""); + } + else if ('\\' == c) { + y.append("\\\\"); + } + else if ('/' == c) { + y.append("\\/"); + } + else { + y.appendCodePoint(c); + } + } + else if (0x00FFFF >= c) { // non-Ascii Bmp character (0x000080-0x00FFFF) + y.append("\\u" + String.format("%4H", c).replace(" ", "0")); + } + else { // non-Bmp character (0x010000-0x10FFFF) + int cc = c - 0x010000; + int ch = 0xD800 + (cc >> 10); + int cl = 0xDC00 + (cc & 0x03FF); + y.append("\\u" + String.format("%4H", ch).replace(" ", "0") + "\\u" + String.format("%4H", cl).replace(" ", "0")); + i++; + } + } + else { + if ('"' == c) { + y.append("\\\""); + } + else if ('\\' == c) { + y.append("\\\\"); + } + else if ('/' == c) { + y.append("\\/"); + } + else if ('\b' == c) { + y.append("\\b"); + } + else if ('\f' == c) { + y.append("\\f"); + } + else if ('\n' == c) { + y.append("\\n"); + } + else if ('\r' == c) { + y.append("\\r"); + } + else if ('\t' == c) { + y.append("\\t"); + } + else if (0x00001F >= c) { // non-special control Ascii character (0x000000-0x00001F) + y.append("\\u" + String.format("%4H", c).replace(" ", "0")); + } + else { + y.appendCodePoint(c); + if (0x010000 <= c) { + i++; + } + } + } + } + return y.toString(); + } + + protected static String getUnescapedString(String x) throws MInvalidJsonValueException { + if (null == x) { + throw new IllegalArgumentException("Invalid 'x': null."); + } + // + StringBuilder y = new StringBuilder(); + for (int i = 0; x.length() > i; i++) { + if (x.indexOf("\\\"", i) == i) { + y.append("\""); + i++; + } + else if (x.indexOf("\\\\", i) == i) { + y.append("\\"); + i++; + } + else if (x.indexOf("\\/", i) == i) { + y.append("/"); + i++; + } + else if (x.indexOf("\\b", i) == i) { + y.append("\b"); + i++; + } + else if (x.indexOf("\\f", i) == i) { + y.append("\f"); + i++; + } + else if (x.indexOf("\\n", i) == i) { + y.append("\n"); + i++; + } + else if (x.indexOf("\\r", i) == i) { + y.append("\r"); + i++; + } + else if (x.indexOf("\\t", i) == i) { + y.append("\t"); + i++; + } + else if (x.indexOf("\\u", i) == i) { + if ((i + 2 + 4) > x.length()) { + throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json string.", x)); + } + else { + int ch = -1; + try { + ch = Integer.parseInt(x.substring(i + 2, i + 2 + 4), 16); + i += 5; + if ((0xD800 <= ch) && (0xDBFF >= ch)) { + i++; + if (x.indexOf("\\u", i) != i) { + throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json string.", x)); + } + else if ((i + 2 + 4) > x.length()) { + throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json string.", x)); + } + else { + int cl = -1; + try { + cl = Integer.parseInt(x.substring(i + 2, i + 2 + 4), 16); + i += 5; + if ((0xDC00 >= cl) || (0xDFFF <= cl)) { + throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json string.", x)); + } + int c = ((ch - 0xD800) << 10) + (cl - 0xDC00) + 0x010000; + y.appendCodePoint(c); + } + catch (NumberFormatException exception) { + throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json string.", x)); // no need to propagate exception + } + } + } + else { + y.appendCodePoint(ch); + } + } + catch (NumberFormatException exception) { + throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json string.", x)); // no need to propagate exception + } + } + } + else { + int c = x.codePointAt(i); + if (0x010000 <= c) { + i++; + } + y.appendCodePoint(c); + } + } + return y.toString(); + } + + /* Parsers. */ + + protected static int getTokenLength(String x) { + if (null == x) { + throw new IllegalArgumentException("Invalid 'x': null."); + } + // + if ((2 > x.length()) || ('"' != x.charAt(0))) { + return 0; + } + if ('"' == x.charAt(1)) { + return 1 + 1; + } + String y = x.substring(1); + int quotationMarkPosition = 0; + while (-1 < (quotationMarkPosition = y.indexOf("\"", quotationMarkPosition + 1))) { + int c = 0; + StringBuilder s = new StringBuilder(""); + while ((c <= quotationMarkPosition) && (y.substring(quotationMarkPosition - c, quotationMarkPosition).equals(s.toString()))) { + c++; + s.append("\\"); + } + c--; + if (0 == (c % 2)) { + break; + } + } + if (-1 == quotationMarkPosition) { + return 0; + } + return (quotationMarkPosition + 1) + 1; + } + + public void parseString(String x) throws MInvalidJsonValueException { + if (MText.isBlank(x)) { + throw new IllegalArgumentException("Invalid 'x': null or empty."); + } + // + if ((2 > x.length()) || ('"' != x.charAt(0)) || ('"' != x.charAt(x.length() - 1))) { + throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json string.", x)); + } + String y = x.substring(1, x.length() - 1); + if (-1 < y.replaceAll("\\\\\"", "__").indexOf("\"")) { + throw new MInvalidJsonValueException(String.format("Invalid 'x': %s: not a Json string.", x)); + } + this.setValue(MJsonString.getUnescapedString(y)); + } + + /* Formatter. */ + + @Override + public String getJsonValue() { + return "\"" + MJsonString.getEscapedString(this.getValue(), this.getExtendedEscapeMode()) + "\""; + } + +} diff --git a/7.x/src/main/java/com/marcozanon/macaco/json/MJsonValue.java b/7.x/src/main/java/com/marcozanon/macaco/json/MJsonValue.java new file mode 100644 index 0000000..85fbe8a --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/json/MJsonValue.java @@ -0,0 +1,44 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * See LICENSE for details. + */ + +package com.marcozanon.macaco.json; + +import com.marcozanon.macaco.MObject; + +public abstract class MJsonValue extends MObject { + + /* */ + + @Override + 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/7.x/src/main/java/com/marcozanon/macaco/logging/MLogDatabaseTable.java b/7.x/src/main/java/com/marcozanon/macaco/logging/MLogDatabaseTable.java new file mode 100644 index 0000000..07d213f --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/logging/MLogDatabaseTable.java @@ -0,0 +1,117 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * See LICENSE for details. + */ + +package com.marcozanon.macaco.logging; + +import com.marcozanon.macaco.database.MDatabaseConnection; +import com.marcozanon.macaco.database.MDatabaseConnectionFailureException; +import com.marcozanon.macaco.database.MDatabaseConnectionPool; +import com.marcozanon.macaco.database.MSqlStatementException; +import com.marcozanon.macaco.database.MSqlTable; +import com.marcozanon.macaco.text.MText; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.LinkedHashMap; + +public class MLogDatabaseTable extends MLogTarget { + + protected MDatabaseConnectionPool databaseConnectionPool = null; + + protected String logDatabaseTable = null; + protected String logDatabaseTablePrimaryKey = null; + protected String logDatabaseField = null; + + protected MDatabaseConnection databaseConnection = null; + + /* */ + + public MLogDatabaseTable(MDatabaseConnectionPool databaseConnectionPool, String logDatabaseTable, String logDatabaseTablePrimaryKey, String logDatabaseField) throws MLoggingException { + super(); + // + if (null == databaseConnectionPool) { + throw new IllegalArgumentException("Invalid 'databaseConnectionPool': null."); + } + if (MText.isEmpty(logDatabaseTable)) { + throw new IllegalArgumentException("Invalid 'logDatabaseTable': null or empty."); + } + if (MText.isEmpty(logDatabaseTablePrimaryKey)) { + throw new IllegalArgumentException("Invalid 'logDatabaseTablePrimaryKey': null or empty."); + } + if (MText.isEmpty(logDatabaseField)) { + throw new IllegalArgumentException("Invalid 'logDatabaseField': null or empty."); + } + // + this.databaseConnectionPool = databaseConnectionPool; + this.logDatabaseTable = logDatabaseTable; + this.logDatabaseTablePrimaryKey = logDatabaseTablePrimaryKey; + this.logDatabaseField = logDatabaseField; + } + + @Override + public void close() throws MLoggingException { + } + + /* Database connection pool. */ + + protected MDatabaseConnectionPool getDatabaseConnectionPool() { + return this.databaseConnectionPool; + } + + /* Database logging parameters. */ + + protected String getLogDatabaseTable() { + return this.logDatabaseTable; + } + + protected String getLogDatabaseTablePrimaryKey() { + return this.logDatabaseTablePrimaryKey; + } + + protected String getLogDatabaseField() { + return this.logDatabaseField; + } + + /* Output. */ + + @Override + public void appendMessage(String message) throws MLoggingException { + this.appendMessage(message, 0); + } + + @Override + public void appendMessage(String message, int indentation) throws MLoggingException { + if (null == message) { + throw new IllegalArgumentException("Invalid 'message': null."); + } + if (0 > indentation) { + throw new IllegalArgumentException(String.format("Invalid 'indentation': %s: cannot be negative.", indentation)); + } + // + try { + String timestamp = (new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")).format(new Date()); + // + StringBuilder separator = new StringBuilder(" "); + for (int i = 0; i < indentation; i++) { + separator.append(" "); + } + // + MDatabaseConnection databaseConnection = this.getDatabaseConnectionPool().popDatabaseConnection(); + // + LinkedHashMap<String, Object> p = new LinkedHashMap<String, Object>(); + p.put(this.getLogDatabaseField(), timestamp + separator + message); + (new MSqlTable(databaseConnection, this.getLogDatabaseTable(), this.getLogDatabaseTablePrimaryKey())).setRecord(p, null, false); + // + this.getDatabaseConnectionPool().pushDatabaseConnection(databaseConnection); + } + catch (MDatabaseConnectionFailureException exception) { + throw new MLoggingException("Could not write to database table.", exception); + } + catch (MSqlStatementException exception) { + throw new MLoggingException("Could not write to database table.", exception); + } + } + +} diff --git a/7.x/src/main/java/com/marcozanon/macaco/logging/MLogFilter.java b/7.x/src/main/java/com/marcozanon/macaco/logging/MLogFilter.java new file mode 100644 index 0000000..c5bfcca --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/logging/MLogFilter.java @@ -0,0 +1,125 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * See LICENSE for details. + */ + +package com.marcozanon.macaco.logging; + +import com.marcozanon.macaco.MObject; +import java.util.LinkedList; + +public class MLogFilter extends MObject { + + public static enum Threshold { + STANDARD, + DEBUG + }; + + protected MLogFilter.Threshold threshold = null; + + protected LinkedList<MLogTarget> logTargets = new LinkedList<MLogTarget>(); + + protected boolean pausedState = false; + protected LinkedList<MLogMessage> logMessageQueue = new LinkedList<MLogMessage>(); + + /* */ + + public MLogFilter(MLogFilter.Threshold threshold) { + super(); + // + this.setThreshold(threshold); + } + + public void close() throws MLoggingException { + for (MLogTarget t: this.getLogTargets()) { + t.close(); + } + } + + @Override + protected void finalize() { + try { + this.close(); + } + catch (Exception exception) { + } + } + + /* Threshold. */ + + public void setThreshold(MLogFilter.Threshold threshold) { + if (null == threshold) { + throw new IllegalArgumentException("Invalid 'threshold': null."); + } + // + this.threshold = threshold; + } + + public MLogFilter.Threshold getThreshold() { + return this.threshold; + } + + /* Log targets. */ + + public void addLogTarget(MLogTarget logTarget) { + if (null == logTarget) { + throw new IllegalArgumentException("Invalid 'logTarget': null."); + } + // + synchronized (this.logTargets) { + this.logTargets.add(logTarget); + } + } + + protected LinkedList<MLogTarget> getLogTargets() { + return this.logTargets; + } + + /* Output. */ + + public void setPausedState(boolean pausedState) throws MLoggingException { + this.pausedState = pausedState; + // + if (!this.getPausedState()) { + this.flushMessages(); + } + } + + public boolean getPausedState() { + return this.pausedState; + } + + public void appendMessage(MLogFilter.Threshold level, String message) throws MLoggingException { + this.appendMessage(level, message, 0); + } + + public void appendMessage(MLogFilter.Threshold level, String message, int indentation) throws MLoggingException { + if (null == level) { + throw new IllegalArgumentException("Invalid 'level': null."); + } + // + if (level.ordinal() > this.getThreshold().ordinal()) { + return; + } + // + this.logMessageQueue.add(new MLogMessage(message, indentation)); + // + if (!this.getPausedState()) { + this.flushMessages(); + } + } + + protected void flushMessages() throws MLoggingException { + while (0 < this.logMessageQueue.size()) { + MLogMessage logMessage = this.logMessageQueue.remove(); + String message = logMessage.getMessage(); + int indentation = logMessage.getIndentation(); + // + for (MLogTarget logTarget: this.getLogTargets()) { + logTarget.appendMessage(message, indentation); + } + } + } + +} diff --git a/7.x/src/main/java/com/marcozanon/macaco/logging/MLogListener.java b/7.x/src/main/java/com/marcozanon/macaco/logging/MLogListener.java new file mode 100644 index 0000000..53956e6 --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/logging/MLogListener.java @@ -0,0 +1,15 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * See LICENSE for details. + */ + +package com.marcozanon.macaco.logging; + +public interface MLogListener { + + /* Logging. */ + + public void onMessageLogging(String message); + +} diff --git a/7.x/src/main/java/com/marcozanon/macaco/logging/MLogMessage.java b/7.x/src/main/java/com/marcozanon/macaco/logging/MLogMessage.java new file mode 100644 index 0000000..13b6f1b --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/logging/MLogMessage.java @@ -0,0 +1,40 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * 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/7.x/src/main/java/com/marcozanon/macaco/logging/MLogPlainTextFile.java b/7.x/src/main/java/com/marcozanon/macaco/logging/MLogPlainTextFile.java new file mode 100644 index 0000000..3a13d75 --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/logging/MLogPlainTextFile.java @@ -0,0 +1,103 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * See LICENSE for details. + */ + +package com.marcozanon.macaco.logging; + +import com.marcozanon.macaco.MConstants; +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), MConstants.TEXT_ENCODING)); + } + catch (FileNotFoundException exception) { + throw new MLoggingException("Could not open file.", exception); + } + catch (UnsupportedEncodingException exception) { // cannot happen + } + } + + @Override + public void close() throws MLoggingException { + try { + this.getBuffer().close(); + } + catch (IOException exception) { + throw new MLoggingException("Could not close file.", exception); + } + } + + /* File. */ + + protected String getFile() { + return this.file; + } + + /* Buffer. */ + + protected BufferedWriter getBuffer() { + return this.buffer; + } + + /* Output. */ + + @Override + public void appendMessage(String message) throws MLoggingException { + this.appendMessage(message, 0); + } + + @Override + public void appendMessage(String message, int indentation) throws MLoggingException { + if (null == message) { + throw new IllegalArgumentException("Invalid 'message': null."); + } + if (0 > indentation) { + throw new IllegalArgumentException(String.format("Invalid 'indentation': %s: cannot be negative.", indentation)); + } + // + try { + String timestamp = (new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")).format(new Date()); + // + StringBuilder separator = new StringBuilder(" "); + for (int i = 0; i < indentation; i++) { + separator.append(" "); + } + // + synchronized (this.getBuffer()) { + this.getBuffer().write(timestamp + separator + message); + this.getBuffer().newLine(); + this.getBuffer().flush(); + } + } + catch (IOException exception) { + throw new MLoggingException("Could not write to file.", exception); + } + } + +} diff --git a/7.x/src/main/java/com/marcozanon/macaco/logging/MLogTarget.java b/7.x/src/main/java/com/marcozanon/macaco/logging/MLogTarget.java new file mode 100644 index 0000000..c9bb083 --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/logging/MLogTarget.java @@ -0,0 +1,32 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * 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; + + @Override + 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/7.x/src/main/java/com/marcozanon/macaco/logging/MLoggingException.java b/7.x/src/main/java/com/marcozanon/macaco/logging/MLoggingException.java new file mode 100644 index 0000000..f9abdf6 --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/logging/MLoggingException.java @@ -0,0 +1,32 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * 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/7.x/src/main/java/com/marcozanon/macaco/text/MText.java b/7.x/src/main/java/com/marcozanon/macaco/text/MText.java new file mode 100644 index 0000000..587aaf5 --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/text/MText.java @@ -0,0 +1,482 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * See LICENSE for details. + */ + +package com.marcozanon.macaco.text; + +import com.marcozanon.macaco.MConstants; +import com.marcozanon.macaco.MObject; +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.StringJoiner; +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(); + } + + public static String removeLine(String x, int lineNumber) { + if (null == x) { + throw new IllegalArgumentException("Invalid 'x': null."); + } + // + String[] xLines = x.split(System.getProperty("line.separator")); + // + if ((0 > lineNumber) || (xLines.length - 1 < lineNumber)) { + throw new IllegalArgumentException(String.format("Invalid 'lineNumber': %s.", lineNumber)); + } + // + StringJoiner stringJoiner = new StringJoiner(System.getProperty("line.separator")); + for (int l = 0; xLines.length > l; l++) { + if (l == lineNumber) { + continue; + } + // + stringJoiner.add(xLines[l]); + } + // + return stringJoiner.toString(); + } + + public static String removeFirstLine(String x) { + return MText.removeLine(x, 0); + } + + public static String removeLastLine(String x) { + if (null == x) { + throw new IllegalArgumentException("Invalid 'x': null."); + } + // + String[] xLines = x.split(System.getProperty("line.separator")); + // + return MText.removeLine(x, xLines.length - 1); + } + + /* Javascript escape. */ + + public static String getJavascriptEscapedString(String x) { + if (null == x) { + return null; + } + // + String text = x; + // + text = text.replace("\\", "\\\\"); + text = text.replace("'", "\\\'"); + // + return text; + } + + /* Xhtml escape and safety. */ + + public static String getXhtmlEscapedString(String x) { // similar to PHP's htmlspecialchars() + if (null == x) { + return null; + } + // + String text = x; + // + text = text.replace("&", "&"); + text = text.replace("\"", """); + text = text.replace("'", "'"); + text = text.replace("<", "<"); + text = text.replace(">", ">"); + // + text = text.replace("\n", "<br />"); + // + return text; + } + + public static String getXhtmlNumericEntitiesString(String x) { // for compatibility with innerHTML + if (null == x) { + return null; + } + // + String text = x; + // + text = text.replace(" ", " "); + text = text.replace("¡", "¡"); + text = text.replace("¢", "¢"); + text = text.replace("£", "£"); + text = text.replace("¤", "¤"); + text = text.replace("¥", "¥"); + text = text.replace("¦", "¦"); + text = text.replace("§", "§"); + text = text.replace("¨", "¨"); + text = text.replace("©", "©"); + text = text.replace("ª", "ª"); + text = text.replace("«", "«"); + text = text.replace("¬", "¬"); + text = text.replace("­", "­"); + text = text.replace("®", "®"); + text = text.replace("¯", "¯"); + text = text.replace("°", "°"); + text = text.replace("±", "±"); + text = text.replace("²", "²"); + text = text.replace("³", "³"); + text = text.replace("´", "´"); + text = text.replace("µ", "µ"); + text = text.replace("¶", "¶"); + text = text.replace("·", "·"); + text = text.replace("¸", "¸"); + text = text.replace("¹", "¹"); + text = text.replace("º", "º"); + text = text.replace("»", "»"); + text = text.replace("¼", "¼"); + text = text.replace("½", "½"); + text = text.replace("¾", "¾"); + text = text.replace("¿", "¿"); + text = text.replace("À", "À"); + text = text.replace("Á", "Á"); + text = text.replace("Â", "Â"); + text = text.replace("Ã", "Ã"); + text = text.replace("Ä", "Ä"); + text = text.replace("Å", "Å"); + text = text.replace("Æ", "Æ"); + text = text.replace("Ç", "Ç"); + text = text.replace("È", "È"); + text = text.replace("É", "É"); + text = text.replace("Ê", "Ê"); + text = text.replace("Ë", "Ë"); + text = text.replace("Ì", "Ì"); + text = text.replace("Í", "Í"); + text = text.replace("Î", "Î"); + text = text.replace("Ï", "Ï"); + text = text.replace("Ð", "Ð"); + text = text.replace("Ñ", "Ñ"); + text = text.replace("Ò", "Ò"); + text = text.replace("Ó", "Ó"); + text = text.replace("Ô", "Ô"); + text = text.replace("Õ", "Õ"); + text = text.replace("Ö", "Ö"); + text = text.replace("×", "×"); + text = text.replace("Ø", "Ø"); + text = text.replace("Ù", "Ù"); + text = text.replace("Ú", "Ú"); + text = text.replace("Û", "Û"); + text = text.replace("Ü", "Ü"); + text = text.replace("Ý", "Ý"); + text = text.replace("Þ", "Þ"); + text = text.replace("ß", "ß"); + text = text.replace("à", "à"); + text = text.replace("á", "á"); + text = text.replace("â", "â"); + text = text.replace("ã", "ã"); + text = text.replace("ä", "ä"); + text = text.replace("å", "å"); + text = text.replace("æ", "æ"); + text = text.replace("ç", "ç"); + text = text.replace("è", "è"); + text = text.replace("é", "é"); + text = text.replace("ê", "ê"); + text = text.replace("ë", "ë"); + text = text.replace("ì", "ì"); + text = text.replace("í", "í"); + text = text.replace("î", "î"); + text = text.replace("ï", "ï"); + text = text.replace("ð", "ð"); + text = text.replace("ñ", "ñ"); + text = text.replace("ò", "ò"); + text = text.replace("ó", "ó"); + text = text.replace("ô", "ô"); + text = text.replace("õ", "õ"); + text = text.replace("ö", "ö"); + text = text.replace("÷", "÷"); + text = text.replace("ø", "ø"); + text = text.replace("ù", "ù"); + text = text.replace("ú", "ú"); + text = text.replace("û", "û"); + text = text.replace("ü", "ü"); + text = text.replace("ý", "ý"); + text = text.replace("þ", "þ"); + text = text.replace("ÿ", "ÿ"); + text = text.replace("Œ", "Œ"); + text = text.replace("œ", "œ"); + text = text.replace("Š", "Š"); + text = text.replace("š", "š"); + text = text.replace("Ÿ", "Ÿ"); + text = text.replace("ƒ", "ƒ"); + text = text.replace("ˆ", "ˆ"); + text = text.replace("˜", "˜"); + text = text.replace("Α", "Α"); + text = text.replace("Β", "Β"); + text = text.replace("Γ", "Γ"); + text = text.replace("Δ", "Δ"); + text = text.replace("Ε", "Ε"); + text = text.replace("Ζ", "Ζ"); + text = text.replace("Η", "Η"); + text = text.replace("Θ", "Θ"); + text = text.replace("Ι", "Ι"); + text = text.replace("Κ", "Κ"); + text = text.replace("Λ", "Λ"); + text = text.replace("Μ", "Μ"); + text = text.replace("Ν", "Ν"); + text = text.replace("Ξ", "Ξ"); + text = text.replace("Ο", "Ο"); + text = text.replace("Π", "Π"); + text = text.replace("Ρ", "Ρ"); + text = text.replace("Σ", "Σ"); + text = text.replace("Τ", "Τ"); + text = text.replace("Υ", "Υ"); + text = text.replace("Φ", "Φ"); + text = text.replace("Χ", "Χ"); + text = text.replace("Ψ", "Ψ"); + text = text.replace("Ω", "Ω"); + text = text.replace("α", "α"); + text = text.replace("β", "β"); + text = text.replace("γ", "γ"); + text = text.replace("δ", "δ"); + text = text.replace("ε", "ε"); + text = text.replace("ζ", "ζ"); + text = text.replace("η", "η"); + text = text.replace("θ", "θ"); + text = text.replace("ι", "ι"); + text = text.replace("κ", "κ"); + text = text.replace("λ", "λ"); + text = text.replace("μ", "μ"); + text = text.replace("ν", "ν"); + text = text.replace("ξ", "ξ"); + text = text.replace("ο", "ο"); + text = text.replace("π", "π"); + text = text.replace("ρ", "ρ"); + text = text.replace("ς", "ς"); + text = text.replace("σ", "σ"); + text = text.replace("τ", "τ"); + text = text.replace("υ", "υ"); + text = text.replace("φ", "φ"); + text = text.replace("χ", "χ"); + text = text.replace("ψ", "ψ"); + text = text.replace("ω", "ω"); + text = text.replace("ϑ", "ϑ"); + text = text.replace("ϒ", "ϒ"); + text = text.replace("ϖ", "ϖ"); + text = text.replace(" ", " "); + text = text.replace(" ", " "); + text = text.replace(" ", " "); + text = text.replace("‌", "‌"); + text = text.replace("‍", "‍"); + text = text.replace("‎", "‎"); + text = text.replace("‏", "‏"); + text = text.replace("–", "–"); + text = text.replace("—", "—"); + text = text.replace("‘", "‘"); + text = text.replace("’", "’"); + text = text.replace("‚", "‚"); + text = text.replace("“", "“"); + text = text.replace("”", "”"); + text = text.replace("„", "„"); + text = text.replace("†", "†"); + text = text.replace("‡", "‡"); + text = text.replace("•", "•"); + text = text.replace("…", "…"); + text = text.replace("‰", "‰"); + text = text.replace("′", "′"); + text = text.replace("″", "″"); + text = text.replace("‹", "‹"); + text = text.replace("›", "›"); + text = text.replace("‾", "‾"); + text = text.replace("⁄", "⁄"); + text = text.replace("€", "€"); + text = text.replace("ℑ", "ℑ"); + text = text.replace("℘", "℘"); + text = text.replace("ℜ", "ℜ"); + text = text.replace("™", "™"); + text = text.replace("ℵ", "ℵ"); + text = text.replace("←", "←"); + text = text.replace("↑", "↑"); + text = text.replace("→", "→"); + text = text.replace("↓", "↓"); + text = text.replace("↔", "↔"); + text = text.replace("↵", "↵"); + text = text.replace("⇐", "⇐"); + text = text.replace("⇑", "⇑"); + text = text.replace("⇒", "⇒"); + text = text.replace("⇓", "⇓"); + text = text.replace("⇔", "⇔"); + text = text.replace("∀", "∀"); + text = text.replace("∂", "∂"); + text = text.replace("∃", "∃"); + text = text.replace("∅", "∅"); + text = text.replace("∇", "∇"); + text = text.replace("∈", "∈"); + text = text.replace("∉", "∉"); + text = text.replace("∋", "∋"); + text = text.replace("∏", "∏"); + text = text.replace("∑", "∑"); + text = text.replace("−", "−"); + text = text.replace("∗", "∗"); + text = text.replace("√", "√"); + text = text.replace("∝", "∝"); + text = text.replace("∞", "∞"); + text = text.replace("∠", "∠"); + text = text.replace("∧", "∧"); + text = text.replace("∨", "∨"); + text = text.replace("∩", "∩"); + text = text.replace("∪", "∪"); + text = text.replace("∫", "∫"); + text = text.replace("∴", "∴"); + text = text.replace("∼", "∼"); + text = text.replace("≅", "≅"); + text = text.replace("≈", "≈"); + text = text.replace("≠", "≠"); + text = text.replace("≡", "≡"); + text = text.replace("≤", "≤"); + text = text.replace("≥", "≥"); + text = text.replace("⊂", "⊂"); + text = text.replace("⊃", "⊃"); + text = text.replace("⊄", "⊄"); + text = text.replace("⊆", "⊆"); + text = text.replace("⊇", "⊇"); + text = text.replace("⊕", "⊕"); + text = text.replace("⊗", "⊗"); + text = text.replace("⊥", "⊥"); + text = text.replace("⋅", "⋅"); + text = text.replace("⌈", "⌈"); + text = text.replace("⌉", "⌉"); + text = text.replace("⌊", "⌊"); + text = text.replace("⌋", "⌋"); + text = text.replace("⟨", "〈"); + text = text.replace("⟩", "〉"); + text = text.replace("◊", "◊"); + text = text.replace("♠", "♠"); + text = text.replace("♣", "♣"); + text = text.replace("♥", "♥"); + text = text.replace("♦", "♦"); + // + return text; + } + + public static String getXhtmlSafeString(String x) throws MXhtmlUnsafeStringException { + if (null == x) { + return null; + } + // + String text = x; + // + StringBuilder fakeXhtmlPageContent = new StringBuilder(""); + fakeXhtmlPageContent.append(String.format("<?xml version=\"1.0\" encoding=\"%s\" ?>", MConstants.TEXT_ENCODING)); + fakeXhtmlPageContent.append("<!DOCTYPE html SYSTEM \"fake-dtd\" >"); + fakeXhtmlPageContent.append("<html xmlns=\"http://www.w3.org/1999/xhtml\">"); + fakeXhtmlPageContent.append("<head><title /></head>"); + fakeXhtmlPageContent.append(String.format("<body>%s</body>", text)); + fakeXhtmlPageContent.append("</html>"); + // Validate Xhtml (with no dangerous tags and event attributes). + try { + SAXParserFactory factory = SAXParserFactory.newInstance(); + factory.setValidating(true); + factory.setNamespaceAware(true); + SAXParser parser = factory.newSAXParser(); + XMLReader reader = parser.getXMLReader(); + reader.setEntityResolver(new EntityResolver() { + + @Override + 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() { + + @Override + 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() { + + @Override + public void error(SAXParseException exception) throws SAXException { + throw new SAXException(exception); + } + + @Override + public void fatalError(SAXParseException exception) throws SAXException { + throw new SAXException(exception); + } + + @Override + public void warning(SAXParseException exception) throws SAXException { + throw new SAXException(exception); + } + + }); + // + reader.parse(new InputSource(new ByteArrayInputStream(fakeXhtmlPageContent.toString().getBytes(MConstants.TEXT_ENCODING)))); + } + catch (ParserConfigurationException exception) { // cannot happen + } + catch (SAXException exception) { + throw new MXhtmlUnsafeStringException("Invalid 'x': unsafe tags or attributes inside.", exception); + } + catch (UnsupportedEncodingException exception) { // cannot happen + } + catch (IOException exception) { // cannot happen; put here not to bypass UnsupportedEncodingException + } + // Also convert named entities to numeric entities. + return MText.getXhtmlNumericEntitiesString(text); + } + +} diff --git a/7.x/src/main/java/com/marcozanon/macaco/text/MTextException.java b/7.x/src/main/java/com/marcozanon/macaco/text/MTextException.java new file mode 100644 index 0000000..b451608 --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/text/MTextException.java @@ -0,0 +1,31 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * 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/7.x/src/main/java/com/marcozanon/macaco/text/MTranslationException.java b/7.x/src/main/java/com/marcozanon/macaco/text/MTranslationException.java new file mode 100644 index 0000000..2b43602 --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/text/MTranslationException.java @@ -0,0 +1,29 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * 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/7.x/src/main/java/com/marcozanon/macaco/text/MTranslationFileParsingException.java b/7.x/src/main/java/com/marcozanon/macaco/text/MTranslationFileParsingException.java new file mode 100644 index 0000000..b33b9d3 --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/text/MTranslationFileParsingException.java @@ -0,0 +1,30 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * See LICENSE for details. + */ + +package com.marcozanon.macaco.text; + +@SuppressWarnings("serial") +public class MTranslationFileParsingException extends MTranslationException { + + /* */ + + public MTranslationFileParsingException() { + super(); + } + + public MTranslationFileParsingException(String message) { + super(message); + } + + public MTranslationFileParsingException(Throwable error) { + super(error); + } + + public MTranslationFileParsingException(String message, Throwable error) { + super(message, error); + } + +} diff --git a/7.x/src/main/java/com/marcozanon/macaco/text/MTranslationValueNotFoundException.java b/7.x/src/main/java/com/marcozanon/macaco/text/MTranslationValueNotFoundException.java new file mode 100644 index 0000000..4610c66 --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/text/MTranslationValueNotFoundException.java @@ -0,0 +1,30 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * See LICENSE for details. + */ + +package com.marcozanon.macaco.text; + +@SuppressWarnings("serial") +public class MTranslationValueNotFoundException extends MTranslationException { + + /* */ + + public MTranslationValueNotFoundException() { + super(); + } + + public MTranslationValueNotFoundException(String message) { + super(message); + } + + public MTranslationValueNotFoundException(Throwable error) { + super(error); + } + + public MTranslationValueNotFoundException(String message, Throwable error) { + super(message, error); + } + +} diff --git a/7.x/src/main/java/com/marcozanon/macaco/text/MTranslator.java b/7.x/src/main/java/com/marcozanon/macaco/text/MTranslator.java new file mode 100644 index 0000000..4760d27 --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/text/MTranslator.java @@ -0,0 +1,184 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * See LICENSE for details. + */ + +package com.marcozanon.macaco.text; + +import com.marcozanon.macaco.MConstants; +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 Locale basicLocale = null; + + protected LinkedHashMap<String, LinkedHashMap<String, String>> messages = new LinkedHashMap<String, LinkedHashMap<String, String>>(); + + /* */ + + public MTranslator(Locale basicLocale) { + super(); + // + if (null == basicLocale) { + throw new IllegalArgumentException("Invalid 'basicLocale': null."); + } + // + this.basicLocale = basicLocale; + } + + public MTranslator(Locale basicLocale, LinkedHashMap<String, LinkedHashMap<String, String>> messages) { + this(basicLocale); + // + this.messages = messages; + } + + public MTranslator(Locale basicLocale, String file) throws MTranslationFileParsingException { + this(basicLocale); + // + this.parseFile(file); + } + + @Override + public MTranslator clone() { + return new MTranslator(this.getBasicLocale(), this.getMessages()); + } + + /* Locale. */ + + protected Locale getBasicLocale() { + return this.basicLocale; + } + + /* String management. */ + + protected LinkedHashMap<String, LinkedHashMap<String, String>> getMessages() { + return this.messages; + } + + public void parseFile(String file) throws MTranslationFileParsingException { + if (MText.isBlank(file)) { + throw new IllegalArgumentException("Invalid 'file': null or empty."); + } + // + try (LineNumberReader buffer = new LineNumberReader(new InputStreamReader(new FileInputStream(file), MConstants.TEXT_ENCODING))) { + String message = null; + String line = null; + synchronized (this.getMessages()) { + while (true) { + try { + line = buffer.readLine(); + } + catch (IOException exception) { + throw new MTranslationFileParsingException("Could not read file.", exception); + } + if (null == line) { + break; + } + line = line.trim(); + if (MText.isEmpty(line)) { + message = null; + continue; + } + else if ((line.startsWith("#")) || (line.startsWith(";"))) { + continue; + } + else if (null == message) { + message = line; + if (this.getMessages().containsKey(message)) { + throw new MTranslationFileParsingException(String.format("Invalid line: %s: duplicated message: %s.", buffer.getLineNumber(), message)); + } + this.getMessages().put(message, new LinkedHashMap<String, String>()); + } + else { + int a = line.indexOf("="); + if (-1 == a) { + throw new MTranslationFileParsingException(String.format("Invalid line: %s: string malformed: %s.", buffer.getLineNumber(), line)); + } + String localeRepresentation = line.substring(0, a).trim(); + String translation = line.substring(a + 1).trim(); + if (this.getMessages().get(message).containsKey(localeRepresentation)) { + throw new MTranslationFileParsingException(String.format("Invalid line: %s: duplicated translation for locale: %s.", buffer.getLineNumber(), message)); + } + this.getMessages().get(message).put(localeRepresentation, translation); + } + } + } + } + catch (FileNotFoundException exception) { + throw new MTranslationFileParsingException("Could not open file.", exception); + } + catch (UnsupportedEncodingException exception) { // cannot happen + } + catch (IOException exception) { + throw new MTranslationFileParsingException("Could not open / close file.", exception); + } + } + + public void clear() { + synchronized (this.getMessages()) { + this.getMessages().clear(); + } + } + + public String t(String message, Locale locale) { + return this.getLenientTranslation(message, locale); + } + + public String getLenientTranslation(String message, Locale locale) { + String translation = null; + try { + translation = this.getTranslation(message, locale, false); + } + catch (MTranslationValueNotFoundException exception) { // cannot happen + } + return translation; + } + + public String getStrictTranslation(String message, Locale locale) throws MTranslationValueNotFoundException { + return this.getTranslation(message, locale, true); + } + + protected String getTranslation(String message, Locale locale, boolean strictMode) throws MTranslationValueNotFoundException { + if (MText.isBlank(message)) { + throw new IllegalArgumentException("Invalid 'message': null or empty."); + } + if (null == locale) { + throw new IllegalArgumentException("Invalid 'locale': null."); + } + // + if (!this.getMessages().containsKey(message)) { + if (strictMode) { + throw new MTranslationValueNotFoundException(String.format("Invalid 'message': %s: not available.", message)); + } + return message; + } +/* + if (this.getBasicLocale().equals(locale)) { + return message; + } +*/ + LinkedHashMap<String, String> messageTranslations = this.getMessages().get(message); + String localeRepresentation = locale.toString(); + if (!messageTranslations.containsKey(localeRepresentation)) { + if (strictMode) { + throw new MTranslationValueNotFoundException(String.format("Invalid 'locale': %s: translation not available for message: %s.", localeRepresentation, message)); + } + String localeFallbackRepresentation = locale.getLanguage(); + if (!messageTranslations.containsKey(localeFallbackRepresentation)) { + return message; + } + return messageTranslations.get(localeFallbackRepresentation); + } + return messageTranslations.get(localeRepresentation); + } + +} diff --git a/7.x/src/main/java/com/marcozanon/macaco/text/MXhtmlUnsafeStringException.java b/7.x/src/main/java/com/marcozanon/macaco/text/MXhtmlUnsafeStringException.java new file mode 100644 index 0000000..abdc9f1 --- /dev/null +++ b/7.x/src/main/java/com/marcozanon/macaco/text/MXhtmlUnsafeStringException.java @@ -0,0 +1,30 @@ +/** + * Macaco + * Copyright (c) 2009-2022 Marco Zanon <info@marcozanon.com>. + * See LICENSE for details. + */ + +package com.marcozanon.macaco.text; + +@SuppressWarnings("serial") +public class MXhtmlUnsafeStringException extends MTextException { + + /* */ + + public MXhtmlUnsafeStringException() { + super(); + } + + public MXhtmlUnsafeStringException(String message) { + super(message); + } + + public MXhtmlUnsafeStringException(Throwable error) { + super(error); + } + + public MXhtmlUnsafeStringException(String message, Throwable error) { + super(message, error); + } + +}