Modify extension bytecode to fix SimpleDateFormat cannot parse errors

This commit is contained in:
Syer10
2021-06-26 12:50:04 -04:00
parent d06c3586fd
commit fc8bb10ca3
13 changed files with 1338 additions and 17 deletions
+7 -4
View File
@@ -25,13 +25,13 @@ dependencies {
// compileOnly( fileTree(dir: new File(rootProject.rootDir, "libs/other"), include: "*.jar")
// JSON
compileOnly( "com.google.code.gson:gson:2.8.6")
compileOnly("com.google.code.gson:gson:2.8.6")
// Javassist
compileOnly( "org.javassist:javassist:3.27.0-GA")
compileOnly("org.javassist:javassist:3.27.0-GA")
// XML
compileOnly( group= "xmlpull", name= "xmlpull", version= "1.1.3.1")
compileOnly(group= "xmlpull", name= "xmlpull", version= "1.1.3.1")
// Config API
implementation(project(":AndroidCompat:Config"))
@@ -40,7 +40,7 @@ dependencies {
compileOnly("com.android.tools.build:apksig:4.2.0-alpha13")
// AndroidX annotations
compileOnly( "androidx.annotation:annotation:1.2.0-alpha01")
compileOnly("androidx.annotation:annotation:1.2.0-alpha01")
// substitute for duktape-android
// 'org.mozilla:rhino' includes some code that we don't need so use 'org.mozilla:rhino-runtime' instead
@@ -52,6 +52,9 @@ dependencies {
val multiplatformSettingsVersion = "0.7.7"
implementation("com.russhwolf:multiplatform-settings-jvm:$multiplatformSettingsVersion")
implementation("com.russhwolf:multiplatform-settings-serialization-jvm:$multiplatformSettingsVersion")
// Android version of SimpleDateFormat
implementation("com.ibm.icu:icu4j:69.1")
}
tasks {
@@ -26,5 +26,8 @@ class AndroidCompatInitializer {
ApplicationInfoConfigModule.register(GlobalConfigManager.config),
SystemConfigModule.register(GlobalConfigManager.config)
)
// Set some properties extensions use
System.setProperty("http.agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
}
}
@@ -0,0 +1,289 @@
package xyz.nulldev.androidcompat.replace;
import com.ibm.icu.text.DateFormat;
import com.ibm.icu.util.TimeZone;
import com.ibm.icu.util.ULocale;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
public class CalendarDelegate extends Calendar {
private com.ibm.icu.util.Calendar delegate;
public CalendarDelegate(com.ibm.icu.util.Calendar delegate) {
this.delegate = delegate;
}
public static java.util.Calendar getInstance() {
return new CalendarDelegate(com.ibm.icu.util.Calendar.getInstance());
}
public static com.ibm.icu.util.Calendar getInstance(TimeZone zone) {
return com.ibm.icu.util.Calendar.getInstance(zone);
}
public static java.util.Calendar getInstance(Locale aLocale) {
return new CalendarDelegate(com.ibm.icu.util.Calendar.getInstance(aLocale));
}
public static com.ibm.icu.util.Calendar getInstance(ULocale locale) {
return com.ibm.icu.util.Calendar.getInstance(locale);
}
public static com.ibm.icu.util.Calendar getInstance(TimeZone zone, Locale aLocale) {
return com.ibm.icu.util.Calendar.getInstance(zone, aLocale);
}
public static com.ibm.icu.util.Calendar getInstance(TimeZone zone, ULocale locale) {
return com.ibm.icu.util.Calendar.getInstance(zone, locale);
}
public static Locale[] getAvailableLocales() {
return com.ibm.icu.util.Calendar.getAvailableLocales();
}
@Override
protected void computeTime() {}
@Override
protected void computeFields() {}
public static ULocale[] getAvailableULocales() {
return com.ibm.icu.util.Calendar.getAvailableULocales();
}
public static String[] getKeywordValuesForLocale(String key, ULocale locale, boolean commonlyUsed) {
return com.ibm.icu.util.Calendar.getKeywordValuesForLocale(key, locale, commonlyUsed);
}
@Override
public long getTimeInMillis() {
return delegate.getTimeInMillis();
}
@Override
public void setTimeInMillis(long millis) {
delegate.setTimeInMillis(millis);
}
@Deprecated
public int getRelatedYear() {
return delegate.getRelatedYear();
}
@Deprecated
public void setRelatedYear(int year) {
delegate.setRelatedYear(year);
}
@Override
public boolean equals(Object obj) {
return delegate.equals(obj);
}
public boolean isEquivalentTo(com.ibm.icu.util.Calendar other) {
return delegate.isEquivalentTo(other);
}
@Override
public int hashCode() {
return delegate.hashCode();
}
@Override
public boolean before(Object when) {
return delegate.before(when);
}
@Override
public boolean after(Object when) {
return delegate.after(when);
}
@Override
public int getActualMaximum(int field) {
return delegate.getActualMaximum(field);
}
@Override
public int getActualMinimum(int field) {
return delegate.getActualMinimum(field);
}
@Override
public void roll(int field, int amount) {
delegate.roll(field, amount);
}
@Override
public void add(int field, int amount) {
delegate.add(field, amount);
}
@Override
public void roll(int field, boolean up) {
roll(field, up ? 1 : -1);
}
public String getDisplayName(Locale loc) {
return delegate.getDisplayName(loc);
}
public String getDisplayName(ULocale loc) {
return delegate.getDisplayName(loc);
}
public int compareTo(com.ibm.icu.util.Calendar that) {
return delegate.compareTo(that);
}
public DateFormat getDateTimeFormat(int dateStyle, int timeStyle, Locale loc) {
return delegate.getDateTimeFormat(dateStyle, timeStyle, loc);
}
public DateFormat getDateTimeFormat(int dateStyle, int timeStyle, ULocale loc) {
return delegate.getDateTimeFormat(dateStyle, timeStyle, loc);
}
@Deprecated
public static String getDateTimePattern(com.ibm.icu.util.Calendar cal, ULocale uLocale, int dateStyle) {
return com.ibm.icu.util.Calendar.getDateTimePattern(cal, uLocale, dateStyle);
}
public int fieldDifference(Date when, int field) {
return delegate.fieldDifference(when, field);
}
public void setTimeZone(TimeZone value) {
delegate.setTimeZone(value);
}
@Override
public java.util.TimeZone getTimeZone() {
return new TimeZoneDelegate(delegate.getTimeZone());
}
@Override
public void setLenient(boolean lenient) {
delegate.setLenient(lenient);
}
@Override
public boolean isLenient() {
return delegate.isLenient();
}
public void setRepeatedWallTimeOption(int option) {
delegate.setRepeatedWallTimeOption(option);
}
public int getRepeatedWallTimeOption() {
return delegate.getRepeatedWallTimeOption();
}
public void setSkippedWallTimeOption(int option) {
delegate.setSkippedWallTimeOption(option);
}
public int getSkippedWallTimeOption() {
return delegate.getSkippedWallTimeOption();
}
@Override
public void setFirstDayOfWeek(int value) {
delegate.setFirstDayOfWeek(value);
}
@Override
public int getFirstDayOfWeek() {
return delegate.getFirstDayOfWeek();
}
@Override
public void setMinimalDaysInFirstWeek(int value) {
delegate.setMinimalDaysInFirstWeek(value);
}
@Override
public int getMinimalDaysInFirstWeek() {
return delegate.getMinimalDaysInFirstWeek();
}
@Override
public int getMinimum(int field) {
return delegate.getMinimum(field);
}
@Override
public int getMaximum(int field) {
return delegate.getMaximum(field);
}
@Override
public int getGreatestMinimum(int field) {
return delegate.getGreatestMinimum(field);
}
@Override
public int getLeastMaximum(int field) {
return delegate.getLeastMaximum(field);
}
@Deprecated
public int getDayOfWeekType(int dayOfWeek) {
return delegate.getDayOfWeekType(dayOfWeek);
}
@Deprecated
public int getWeekendTransition(int dayOfWeek) {
return delegate.getWeekendTransition(dayOfWeek);
}
public boolean isWeekend(Date date) {
return delegate.isWeekend(date);
}
public boolean isWeekend() {
return delegate.isWeekend();
}
@Override
public Object clone() {
return delegate.clone();
}
@Override
public String toString() {
return delegate.toString();
}
public static com.ibm.icu.util.Calendar.WeekData getWeekDataForRegion(String region) {
return com.ibm.icu.util.Calendar.getWeekDataForRegion(region);
}
public com.ibm.icu.util.Calendar.WeekData getWeekData() {
return delegate.getWeekData();
}
public com.ibm.icu.util.Calendar setWeekData(com.ibm.icu.util.Calendar.WeekData wdata) {
return delegate.setWeekData(wdata);
}
public int getFieldCount() {
return delegate.getFieldCount();
}
public String getType() {
return delegate.getType();
}
@Deprecated
public boolean haveDefaultCentury() {
return delegate.haveDefaultCentury();
}
public ULocale getLocale(ULocale.Type type) {
return delegate.getLocale(type);
}
}
@@ -0,0 +1,243 @@
package xyz.nulldev.androidcompat.replace;
import com.ibm.icu.text.DisplayContext;
import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.util.Currency;
import com.ibm.icu.util.CurrencyAmount;
import com.ibm.icu.util.ULocale;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.AttributedCharacterIterator;
import java.text.FieldPosition;
import java.text.ParseException;
import java.text.ParsePosition;
import java.util.Locale;
public class NumberFormatDelegate extends java.text.NumberFormat {
private com.ibm.icu.text.NumberFormat delegate;
public NumberFormatDelegate(com.ibm.icu.text.NumberFormat delegate) {
this.delegate = delegate;
}
public StringBuffer format(Object number, StringBuffer toAppendTo, FieldPosition pos) {
return delegate.format(number, toAppendTo, pos);
}
public String format(BigInteger number) {
return delegate.format(number);
}
public String format(BigDecimal number) {
return delegate.format(number);
}
public String format(com.ibm.icu.math.BigDecimal number) {
return delegate.format(number);
}
public String format(CurrencyAmount currAmt) {
return delegate.format(currAmt);
}
public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) {
return delegate.format(number, toAppendTo, pos);
}
public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition pos) {
return delegate.format(number, toAppendTo, pos);
}
public StringBuffer format(BigInteger number, StringBuffer toAppendTo, FieldPosition pos) {
return delegate.format(number, toAppendTo, pos);
}
public StringBuffer format(BigDecimal number, StringBuffer toAppendTo, FieldPosition pos) {
return delegate.format(number, toAppendTo, pos);
}
public StringBuffer format(com.ibm.icu.math.BigDecimal number, StringBuffer toAppendTo, FieldPosition pos) {
return delegate.format(number, toAppendTo, pos);
}
public StringBuffer format(CurrencyAmount currAmt, StringBuffer toAppendTo, FieldPosition pos) {
return delegate.format(currAmt, toAppendTo, pos);
}
public Number parse(String text, ParsePosition parsePosition) {
return delegate.parse(text, parsePosition);
}
public Number parse(String text) throws ParseException {
return delegate.parse(text);
}
public CurrencyAmount parseCurrency(CharSequence text, ParsePosition pos) {
return delegate.parseCurrency(text, pos);
}
public boolean isParseIntegerOnly() {
return delegate.isParseIntegerOnly();
}
public void setParseIntegerOnly(boolean value) {
delegate.setParseIntegerOnly(value);
}
public void setParseStrict(boolean value) {
delegate.setParseStrict(value);
}
public boolean isParseStrict() {
return delegate.isParseStrict();
}
public void setContext(DisplayContext context) {
delegate.setContext(context);
}
public DisplayContext getContext(DisplayContext.Type type) {
return delegate.getContext(type);
}
public static java.text.NumberFormat getInstance(Locale inLocale) {
return new NumberFormatDelegate(NumberFormat.getInstance(inLocale));
}
public static NumberFormat getInstance(ULocale inLocale) {
return NumberFormat.getInstance(inLocale);
}
public static NumberFormat getInstance(int style) {
return NumberFormat.getInstance(style);
}
public static NumberFormat getInstance(Locale inLocale, int style) {
return NumberFormat.getInstance(inLocale, style);
}
public static NumberFormat getNumberInstance(ULocale inLocale) {
return NumberFormat.getNumberInstance(inLocale);
}
public static NumberFormat getIntegerInstance(ULocale inLocale) {
return NumberFormat.getIntegerInstance(inLocale);
}
public static NumberFormat getCurrencyInstance(ULocale inLocale) {
return NumberFormat.getCurrencyInstance(inLocale);
}
public static NumberFormat getPercentInstance(ULocale inLocale) {
return NumberFormat.getPercentInstance(inLocale);
}
public static NumberFormat getScientificInstance(ULocale inLocale) {
return NumberFormat.getScientificInstance(inLocale);
}
public static Locale[] getAvailableLocales() {
return NumberFormat.getAvailableLocales();
}
public static ULocale[] getAvailableULocales() {
return NumberFormat.getAvailableULocales();
}
public static Object registerFactory(NumberFormat.NumberFormatFactory factory) {
return NumberFormat.registerFactory(factory);
}
public static boolean unregister(Object registryKey) {
return NumberFormat.unregister(registryKey);
}
@Override
public int hashCode() {
return delegate.hashCode();
}
@Override
public boolean equals(Object obj) {
return delegate.equals(obj);
}
@Override
public Object clone() {
return delegate.clone();
}
public boolean isGroupingUsed() {
return delegate.isGroupingUsed();
}
public void setGroupingUsed(boolean newValue) {
delegate.setGroupingUsed(newValue);
}
public int getMaximumIntegerDigits() {
return delegate.getMaximumIntegerDigits();
}
public void setMaximumIntegerDigits(int newValue) {
delegate.setMaximumIntegerDigits(newValue);
}
public int getMinimumIntegerDigits() {
return delegate.getMinimumIntegerDigits();
}
public void setMinimumIntegerDigits(int newValue) {
delegate.setMinimumIntegerDigits(newValue);
}
public int getMaximumFractionDigits() {
return delegate.getMaximumFractionDigits();
}
public void setMaximumFractionDigits(int newValue) {
delegate.setMaximumFractionDigits(newValue);
}
public int getMinimumFractionDigits() {
return delegate.getMinimumFractionDigits();
}
public void setMinimumFractionDigits(int newValue) {
delegate.setMinimumFractionDigits(newValue);
}
public void setCurrency(Currency theCurrency) {
delegate.setCurrency(theCurrency);
}
public java.util.Currency getCurrency() {
return java.util.Currency.getInstance(delegate.getCurrency().getCurrencyCode());
}
public void setRoundingMode(int roundingMode) {
delegate.setRoundingMode(roundingMode);
}
public static NumberFormat getInstance(ULocale desiredLocale, int choice) {
return NumberFormat.getInstance(desiredLocale, choice);
}
@Deprecated
public static String getPatternForStyle(ULocale forLocale, int choice) {
return NumberFormat.getPatternForStyle(forLocale, choice);
}
public ULocale getLocale(ULocale.Type type) {
return delegate.getLocale(type);
}
public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
return delegate.formatToCharacterIterator(obj);
}
public Object parseObject(String source) throws ParseException {
return delegate.parseObject(source);
}
}
@@ -0,0 +1,340 @@
package xyz.nulldev.androidcompat.replace;
import com.ibm.icu.text.DateFormatSymbols;
import com.ibm.icu.text.DisplayContext;
import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.text.TimeZoneFormat;
import com.ibm.icu.util.Calendar;
import com.ibm.icu.util.TimeZone;
import com.ibm.icu.util.ULocale;
import java.text.*;
import java.util.Date;
import java.util.Locale;
/**
* Overridden to switch to Android implementation
*/
public class SimpleDateFormat extends DateFormat {
private com.ibm.icu.text.SimpleDateFormat delegate;
public SimpleDateFormat() {
delegate = new com.ibm.icu.text.SimpleDateFormat();
}
private SimpleDateFormat(com.ibm.icu.text.SimpleDateFormat delegate) {
this.delegate = delegate;
}
public SimpleDateFormat(String pattern) {
delegate = new com.ibm.icu.text.SimpleDateFormat(pattern);
}
public SimpleDateFormat(String pattern, Locale loc) {
delegate = new com.ibm.icu.text.SimpleDateFormat(pattern, loc);
}
public SimpleDateFormat(String pattern, ULocale loc) {
delegate = new com.ibm.icu.text.SimpleDateFormat(pattern, loc);
}
public SimpleDateFormat(String pattern, String override, ULocale loc) {
delegate = new com.ibm.icu.text.SimpleDateFormat(pattern, override, loc);
}
public SimpleDateFormat(String pattern, DateFormatSymbols formatData) {
delegate = new com.ibm.icu.text.SimpleDateFormat(pattern, formatData);
}
public SimpleDateFormat(String pattern, DateFormatSymbols formatData, ULocale loc) {
delegate = new com.ibm.icu.text.SimpleDateFormat(pattern, formatData, loc);
}
@Deprecated
public static SimpleDateFormat getInstance(Calendar.FormatConfiguration formatConfig) {
return new SimpleDateFormat(com.ibm.icu.text.SimpleDateFormat.getInstance(formatConfig));
}
public void set2DigitYearStart(Date startDate) {
delegate.set2DigitYearStart(startDate);
}
public Date get2DigitYearStart() {
return delegate.get2DigitYearStart();
}
public void setContext(DisplayContext context) {
delegate.setContext(context);
}
public StringBuffer format(Calendar cal, StringBuffer toAppendTo, FieldPosition pos) {
return delegate.format(cal, toAppendTo, pos);
}
public void setNumberFormat(NumberFormat newNumberFormat) {
delegate.setNumberFormat(newNumberFormat);
}
public void parse(String text, Calendar cal, ParsePosition parsePos) {
delegate.parse(text, cal, parsePos);
}
public String toPattern() {
return delegate.toPattern();
}
public String toLocalizedPattern() {
return delegate.toLocalizedPattern();
}
public void applyPattern(String pat) {
delegate.applyPattern(pat);
}
public void applyLocalizedPattern(String pat) {
delegate.applyLocalizedPattern(pat);
}
public DateFormatSymbols getDateFormatSymbols() {
return delegate.getDateFormatSymbols();
}
public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols) {
delegate.setDateFormatSymbols(newFormatSymbols);
}
public TimeZoneFormat getTimeZoneFormat() {
return delegate.getTimeZoneFormat();
}
public void setTimeZoneFormat(TimeZoneFormat tzfmt) {
delegate.setTimeZoneFormat(tzfmt);
}
@Override
public Object clone() {
return delegate.clone();
}
@Override
public int hashCode() {
return delegate.hashCode();
}
@Override
public boolean equals(Object obj) {
return delegate.equals(obj);
}
@Override
public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
return delegate.formatToCharacterIterator(obj);
}
@Deprecated
public StringBuffer intervalFormatByAlgorithm(Calendar fromCalendar, Calendar toCalendar, StringBuffer appendTo, FieldPosition pos) throws IllegalArgumentException {
return delegate.intervalFormatByAlgorithm(fromCalendar, toCalendar, appendTo, pos);
}
public void setNumberFormat(String fields, NumberFormat overrideNF) {
delegate.setNumberFormat(fields, overrideNF);
}
public NumberFormat getNumberFormat(char field) {
return delegate.getNumberFormat(field);
}
@Override
public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) {
return delegate.format(date, toAppendTo, fieldPosition);
}
@Override
public Date parse(String text) throws ParseException {
return delegate.parse(text);
}
@Override
public Date parse(String text, ParsePosition pos) {
return delegate.parse(text, pos);
}
@Override
public Object parseObject(String source, ParsePosition pos) {
return delegate.parseObject(source, pos);
}
public static com.ibm.icu.text.DateFormat getTimeInstance(int style, ULocale locale) {
return com.ibm.icu.text.DateFormat.getTimeInstance(style, locale);
}
public static com.ibm.icu.text.DateFormat getDateInstance(int style, ULocale locale) {
return com.ibm.icu.text.DateFormat.getDateInstance(style, locale);
}
public static com.ibm.icu.text.DateFormat getDateTimeInstance(int dateStyle, int timeStyle, ULocale locale) {
return com.ibm.icu.text.DateFormat.getDateTimeInstance(dateStyle, timeStyle, locale);
}
public static Locale[] getAvailableLocales() {
return com.ibm.icu.text.DateFormat.getAvailableLocales();
}
public static ULocale[] getAvailableULocales() {
return com.ibm.icu.text.DateFormat.getAvailableULocales();
}
@Override
public void setCalendar(java.util.Calendar newCalendar) {
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone(newCalendar.getTimeZone().getID()));
cal.setTimeInMillis(newCalendar.getTimeInMillis());
delegate.setCalendar(cal);
}
@Override
public java.util.Calendar getCalendar() {
return new CalendarDelegate(delegate.getCalendar());
}
@Override
public java.text.NumberFormat getNumberFormat() {
return new NumberFormatDelegate(delegate.getNumberFormat());
}
@Override
public void setTimeZone(java.util.TimeZone zone) {
delegate.setTimeZone(TimeZone.getTimeZone(zone.getID()));
}
@Override
public java.util.TimeZone getTimeZone() {
return new TimeZoneDelegate(delegate.getTimeZone());
}
@Override
public void setLenient(boolean lenient) {
delegate.setLenient(lenient);
}
@Override
public boolean isLenient() {
return delegate.isLenient();
}
public void setCalendarLenient(boolean lenient) {
delegate.setCalendarLenient(lenient);
}
public boolean isCalendarLenient() {
return delegate.isCalendarLenient();
}
public com.ibm.icu.text.DateFormat setBooleanAttribute(com.ibm.icu.text.DateFormat.BooleanAttribute key, boolean value) {
return delegate.setBooleanAttribute(key, value);
}
public boolean getBooleanAttribute(com.ibm.icu.text.DateFormat.BooleanAttribute key) {
return delegate.getBooleanAttribute(key);
}
public DisplayContext getContext(DisplayContext.Type type) {
return delegate.getContext(type);
}
public static com.ibm.icu.text.DateFormat getDateInstance(Calendar cal, int dateStyle, Locale locale) {
return com.ibm.icu.text.DateFormat.getDateInstance(cal, dateStyle, locale);
}
public static com.ibm.icu.text.DateFormat getDateInstance(Calendar cal, int dateStyle, ULocale locale) {
return com.ibm.icu.text.DateFormat.getDateInstance(cal, dateStyle, locale);
}
public static com.ibm.icu.text.DateFormat getTimeInstance(Calendar cal, int timeStyle, Locale locale) {
return com.ibm.icu.text.DateFormat.getTimeInstance(cal, timeStyle, locale);
}
public static com.ibm.icu.text.DateFormat getTimeInstance(Calendar cal, int timeStyle, ULocale locale) {
return com.ibm.icu.text.DateFormat.getTimeInstance(cal, timeStyle, locale);
}
public static com.ibm.icu.text.DateFormat getDateTimeInstance(Calendar cal, int dateStyle, int timeStyle, Locale locale) {
return com.ibm.icu.text.DateFormat.getDateTimeInstance(cal, dateStyle, timeStyle, locale);
}
public static com.ibm.icu.text.DateFormat getDateTimeInstance(Calendar cal, int dateStyle, int timeStyle, ULocale locale) {
return com.ibm.icu.text.DateFormat.getDateTimeInstance(cal, dateStyle, timeStyle, locale);
}
public static com.ibm.icu.text.DateFormat getInstance(Calendar cal, Locale locale) {
return com.ibm.icu.text.DateFormat.getInstance(cal, locale);
}
public static com.ibm.icu.text.DateFormat getInstance(Calendar cal, ULocale locale) {
return com.ibm.icu.text.DateFormat.getInstance(cal, locale);
}
public static com.ibm.icu.text.DateFormat getInstance(Calendar cal) {
return com.ibm.icu.text.DateFormat.getInstance(cal);
}
public static com.ibm.icu.text.DateFormat getDateInstance(Calendar cal, int dateStyle) {
return com.ibm.icu.text.DateFormat.getDateInstance(cal, dateStyle);
}
public static com.ibm.icu.text.DateFormat getTimeInstance(Calendar cal, int timeStyle) {
return com.ibm.icu.text.DateFormat.getTimeInstance(cal, timeStyle);
}
public static com.ibm.icu.text.DateFormat getDateTimeInstance(Calendar cal, int dateStyle, int timeStyle) {
return com.ibm.icu.text.DateFormat.getDateTimeInstance(cal, dateStyle, timeStyle);
}
public static com.ibm.icu.text.DateFormat getInstanceForSkeleton(String skeleton) {
return com.ibm.icu.text.DateFormat.getInstanceForSkeleton(skeleton);
}
public static com.ibm.icu.text.DateFormat getInstanceForSkeleton(String skeleton, Locale locale) {
return com.ibm.icu.text.DateFormat.getInstanceForSkeleton(skeleton, locale);
}
public static com.ibm.icu.text.DateFormat getInstanceForSkeleton(String skeleton, ULocale locale) {
return com.ibm.icu.text.DateFormat.getInstanceForSkeleton(skeleton, locale);
}
public static com.ibm.icu.text.DateFormat getInstanceForSkeleton(Calendar cal, String skeleton, Locale locale) {
return com.ibm.icu.text.DateFormat.getInstanceForSkeleton(cal, skeleton, locale);
}
public static com.ibm.icu.text.DateFormat getInstanceForSkeleton(Calendar cal, String skeleton, ULocale locale) {
return com.ibm.icu.text.DateFormat.getInstanceForSkeleton(cal, skeleton, locale);
}
public static com.ibm.icu.text.DateFormat getPatternInstance(String skeleton) {
return com.ibm.icu.text.DateFormat.getPatternInstance(skeleton);
}
public static com.ibm.icu.text.DateFormat getPatternInstance(String skeleton, Locale locale) {
return com.ibm.icu.text.DateFormat.getPatternInstance(skeleton, locale);
}
public static com.ibm.icu.text.DateFormat getPatternInstance(String skeleton, ULocale locale) {
return com.ibm.icu.text.DateFormat.getPatternInstance(skeleton, locale);
}
public static com.ibm.icu.text.DateFormat getPatternInstance(Calendar cal, String skeleton, Locale locale) {
return com.ibm.icu.text.DateFormat.getPatternInstance(cal, skeleton, locale);
}
public static com.ibm.icu.text.DateFormat getPatternInstance(Calendar cal, String skeleton, ULocale locale) {
return com.ibm.icu.text.DateFormat.getPatternInstance(cal, skeleton, locale);
}
public ULocale getLocale(ULocale.Type type) {
return delegate.getLocale(type);
}
@Override
public Object parseObject(String source) throws ParseException {
return delegate.parseObject(source);
}
}
@@ -0,0 +1,190 @@
package xyz.nulldev.androidcompat.replace;
import com.ibm.icu.util.ULocale;
import java.util.Date;
import java.util.Locale;
import java.util.Set;
import java.util.TimeZone;
public class TimeZoneDelegate extends TimeZone {
private com.ibm.icu.util.TimeZone delegate;
public TimeZoneDelegate(com.ibm.icu.util.TimeZone delegate) {
this.delegate = delegate;
}
@Override
public int getOffset(int era, int year, int month, int day, int dayOfWeek, int milliseconds) {
return delegate.getOffset(era, year, month, day, dayOfWeek, milliseconds);
}
@Override
public int getOffset(long date) {
return delegate.getOffset(date);
}
public void getOffset(long date, boolean local, int[] offsets) {
delegate.getOffset(date, local, offsets);
}
@Override
public void setRawOffset(int offsetMillis) {
delegate.setRawOffset(offsetMillis);
}
@Override
public int getRawOffset() {
return delegate.getRawOffset();
}
@Override
public String getID() {
return delegate.getID();
}
@Override
public void setID(String ID) {
delegate.setID(ID);
}
public String getDisplayName(ULocale locale) {
return delegate.getDisplayName(locale);
}
@Override
public String getDisplayName(boolean daylight, int style, Locale locale) {
return delegate.getDisplayName(daylight, style, locale);
}
public String getDisplayName(boolean daylight, int style, ULocale locale) {
return delegate.getDisplayName(daylight, style, locale);
}
@Override
public int getDSTSavings() {
return delegate.getDSTSavings();
}
@Override
public boolean useDaylightTime() {
return delegate.useDaylightTime();
}
@Override
public boolean observesDaylightTime() {
return delegate.observesDaylightTime();
}
@Override
public boolean inDaylightTime(Date date) {
return delegate.inDaylightTime(date);
}
public static TimeZone getTimeZone(String ID) {
return new TimeZoneDelegate(com.ibm.icu.util.TimeZone.getTimeZone(ID));
}
public static com.ibm.icu.util.TimeZone getFrozenTimeZone(String ID) {
return com.ibm.icu.util.TimeZone.getFrozenTimeZone(ID);
}
public static com.ibm.icu.util.TimeZone getTimeZone(String ID, int type) {
return com.ibm.icu.util.TimeZone.getTimeZone(ID, type);
}
public static void setDefaultTimeZoneType(int type) {
com.ibm.icu.util.TimeZone.setDefaultTimeZoneType(type);
}
public static int getDefaultTimeZoneType() {
return com.ibm.icu.util.TimeZone.getDefaultTimeZoneType();
}
public static Set<String> getAvailableIDs(com.ibm.icu.util.TimeZone.SystemTimeZoneType zoneType, String region, Integer rawOffset) {
return com.ibm.icu.util.TimeZone.getAvailableIDs(zoneType, region, rawOffset);
}
public static String[] getAvailableIDs(int rawOffset) {
return com.ibm.icu.util.TimeZone.getAvailableIDs(rawOffset);
}
public static String[] getAvailableIDs(String country) {
return com.ibm.icu.util.TimeZone.getAvailableIDs(country);
}
public static String[] getAvailableIDs() {
return com.ibm.icu.util.TimeZone.getAvailableIDs();
}
public static int countEquivalentIDs(String id) {
return com.ibm.icu.util.TimeZone.countEquivalentIDs(id);
}
public static String getEquivalentID(String id, int index) {
return com.ibm.icu.util.TimeZone.getEquivalentID(id, index);
}
public static TimeZone getDefault() {
return new TimeZoneDelegate(com.ibm.icu.util.TimeZone.getDefault());
}
public static void setDefault(com.ibm.icu.util.TimeZone tz) {
com.ibm.icu.util.TimeZone.setDefault(tz);
}
public boolean hasSameRules(com.ibm.icu.util.TimeZone other) {
return delegate.hasSameRules(other);
}
@Override
public Object clone() {
return delegate.clone();
}
@Override
public boolean equals(Object obj) {
return delegate.equals(obj);
}
@Override
public int hashCode() {
return delegate.hashCode();
}
public static String getTZDataVersion() {
return com.ibm.icu.util.TimeZone.getTZDataVersion();
}
public static String getCanonicalID(String id) {
return com.ibm.icu.util.TimeZone.getCanonicalID(id);
}
public static String getCanonicalID(String id, boolean[] isSystemID) {
return com.ibm.icu.util.TimeZone.getCanonicalID(id, isSystemID);
}
public static String getRegion(String id) {
return com.ibm.icu.util.TimeZone.getRegion(id);
}
public static String getWindowsID(String id) {
return com.ibm.icu.util.TimeZone.getWindowsID(id);
}
public static String getIDForWindowsID(String winid, String region) {
return com.ibm.icu.util.TimeZone.getIDForWindowsID(winid, region);
}
public boolean isFrozen() {
return delegate.isFrozen();
}
public com.ibm.icu.util.TimeZone freeze() {
return delegate.freeze();
}
public com.ibm.icu.util.TimeZone cloneAsThawed() {
return delegate.cloneAsThawed();
}
}
+1 -1
View File
@@ -43,7 +43,7 @@ configure(projects) {
// Kotlin
implementation(kotlin("stdlib-jdk8"))
implementation(kotlin("reflect"))
testImplementation(kotlin("test"))
testImplementation(kotlin("test-junit5"))
// coroutines
val coroutinesVersion = "1.4.3"
+3 -3
View File
@@ -58,6 +58,9 @@ dependencies {
implementation("com.github.salomonbrys.kotson:kotson:2.5.0")
// asm for fixing SimpleDateFormat (must match Dex2Jar version)
implementation("org.ow2.asm:asm-debug-all:5.0.3")
// Source models and interfaces from Tachiyomi 1.x
// using source class from tachiyomi commit 9493577de27c40ce8b2b6122cc447d025e34c477 to not depend on tachiyomi.sourceapi
// implementation("tachiyomi.sourceapi:source-api:1.1")
@@ -68,9 +71,6 @@ dependencies {
// uncomment to test extensions directly
// implementation(fileTree("lib/"))
// Testing
testImplementation(kotlin("test-junit5"))
}
val MainClass = "suwayomi.tachidesk.MainKt"
@@ -22,6 +22,7 @@ import org.kodein.di.conf.global
import org.kodein.di.instance
import org.w3c.dom.Element
import org.w3c.dom.Node
import suwayomi.tachidesk.manga.impl.util.BytecodeEditor
import suwayomi.tachidesk.server.ApplicationDirs
import xyz.nulldev.androidcompat.pm.InstalledPackage.Companion.toList
import xyz.nulldev.androidcompat.pm.toPackageInfo
@@ -80,6 +81,8 @@ object PackageTools {
""".trimIndent()
)
handler.dump(errorFile, emptyArray<String>())
} else {
BytecodeEditor.fixAndroidClasses(jarFilePath.toFile())
}
}
@@ -0,0 +1,218 @@
package suwayomi.tachidesk.manga.impl.util
import mu.KotlinLogging
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.FieldVisitor
import org.objectweb.asm.Handle
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.ClassNode
import suwayomi.tachidesk.manga.impl.util.storage.use
import java.io.File
import java.io.IOException
import java.util.jar.JarEntry
import java.util.jar.JarFile
import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
object BytecodeEditor {
private val logger = KotlinLogging.logger {}
fun fixAndroidClasses(jarFile: File) {
val nodes = loadClasses(jarFile)
.mapValues { (className, classFileBuffer) ->
logger.trace { "Processing class $className" }
transform(classFileBuffer)
} + loadNonClasses(jarFile)
saveAsJar(nodes, jarFile)
}
private fun loadClasses(jar: File): Map<String, ByteArray> {
return JarFile(jar).use { jarFile ->
jarFile.entries()
.asSequence()
.mapNotNull {
readJar(jarFile, it)
}
.toMap()
}
}
private fun readJar(jar: JarFile, entry: JarEntry): Pair<String, ByteArray>? {
return try {
jar.getInputStream(entry).use { stream ->
if (entry.name.endsWith(".class")) {
val bytes = stream.readBytes()
if (bytes.size < 4) {
// Invalid class size
return@use null
}
val cafebabe = String.format(
"%02X%02X%02X%02X",
bytes[0],
bytes[1],
bytes[2],
bytes[3]
)
if (cafebabe.toLowerCase() != "cafebabe") {
// Corrupted class
return@use null
}
getNode(bytes).name to bytes
} else null
}
} catch (e: IOException) {
logger.error(e) { "Error loading jar file" }
null
}
}
private fun getNode(bytes: ByteArray): ClassNode {
val cr = ClassReader(bytes)
return ClassNode().also { cr.accept(it, ClassReader.EXPAND_FRAMES) }
}
private const val simpleDateFormat = "java/text/SimpleDateFormat"
private const val replacementSimpleDateFormat = "xyz/nulldev/androidcompat/replace/SimpleDateFormat"
private fun String?.replaceFormatFully() = if (this == simpleDateFormat) {
replacementSimpleDateFormat
} else this
private fun String?.replaceFormat() = this?.replace(simpleDateFormat, replacementSimpleDateFormat)
private fun transform(classfileBuffer: ByteArray): ByteArray {
val cr = ClassReader(classfileBuffer)
val cw = ClassWriter(cr, 0)
cr.accept(
object : ClassVisitor(Opcodes.ASM5, cw) {
override fun visitField(
access: Int,
name: String?,
desc: String?,
signature: String?,
cst: Any?
): FieldVisitor? {
logger.trace { "CLass Field" to "${desc.replaceFormat()}: ${cst?.let { it::class.java.simpleName }}: $cst" }
return super.visitField(access, name, desc.replaceFormat(), signature, cst)
}
override fun visit(
version: Int,
access: Int,
name: String?,
signature: String?,
superName: String?,
interfaces: Array<out String>?
) {
logger.trace { "Visiting $name: $signature: $superName" }
super.visit(version, access, name, signature, superName, interfaces)
}
override fun visitMethod(
access: Int,
name: String,
desc: String,
signature: String?,
exceptions: Array<String?>?
): MethodVisitor {
logger.trace { "Processing method $name: ${desc.replaceFormat()}: $signature" }
val mv: MethodVisitor? = super.visitMethod(
access, name, desc.replaceFormat(), signature, exceptions
)
return object : MethodVisitor(Opcodes.ASM5, mv) {
override fun visitLdcInsn(cst: Any?) {
logger.trace { "Ldc" to "${cst?.let { "${it::class.java.simpleName}: $it" }}" }
super.visitLdcInsn(cst)
}
override fun visitTypeInsn(opcode: Int, type: String?) {
logger.trace {
"Type" to "$opcode: ${type.replaceFormatFully()}"
}
super.visitTypeInsn(
opcode,
type.replaceFormatFully()
)
}
override fun visitMethodInsn(
opcode: Int,
owner: String?,
name: String?,
desc: String?,
itf: Boolean
) {
logger.trace {
"Method" to "$opcode: ${owner.replaceFormatFully()}: $name: ${desc.replaceFormat()}"
}
super.visitMethodInsn(
opcode,
owner.replaceFormatFully(),
name,
desc.replaceFormat(),
itf
)
}
override fun visitFieldInsn(
opcode: Int,
owner: String?,
name: String?,
desc: String?
) {
logger.trace { "Field" to "$opcode: $owner: $name: ${desc.replaceFormat()}" }
super.visitFieldInsn(opcode, owner, name, desc.replaceFormat())
}
override fun visitInvokeDynamicInsn(
name: String?,
desc: String?,
bsm: Handle?,
vararg bsmArgs: Any?
) {
logger.trace { "InvokeDynamic" to "$name: $desc" }
super.visitInvokeDynamicInsn(name, desc, bsm, *bsmArgs)
}
}
}
},
0
)
return cw.toByteArray()
}
private fun loadNonClasses(jarFile: File): Map<String, ByteArray> {
val entries = mutableMapOf<String, ByteArray>()
ZipInputStream(jarFile.inputStream()).use { stream ->
var nextEntry: ZipEntry?
while (stream.nextEntry.also { nextEntry = it } != null) {
nextEntry?.use(stream) { entry ->
if (!entry.name.endsWith(".class") && !entry.isDirectory) {
val bytes = stream.readBytes()
entries[entry.name] = bytes
}
}
}
}
return entries
}
private fun saveAsJar(outBytes: Map<String, ByteArray>, file: File) {
JarOutputStream(file.outputStream()).use { out ->
outBytes.forEach { (entry, value) ->
// Append extension to class entries
out.putNextEntry(
ZipEntry(
entry + if (entry.contains(".")) "" else ".class"
)
)
out.write(value)
out.closeEntry()
}
}
}
}
@@ -81,6 +81,8 @@ object PackageTools {
""".trimIndent()
)
handler.dump(errorFile, emptyArray<String>())
} else {
BytecodeEditor.fixAndroidClasses(jarFilePath.toFile())
}
}
@@ -0,0 +1,24 @@
package suwayomi.tachidesk.manga.impl.util.storage
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
fun ZipEntry.use(stream: ZipInputStream, block: (ZipEntry) -> Unit) {
var exception: Throwable? = null
try {
return block(this)
} catch (e: Throwable) {
exception = e
throw e
} finally {
if (exception == null) {
stream.closeEntry()
} else {
try {
stream.closeEntry()
} catch (closeException: Throwable) {
exception.addSuppressed(closeException)
}
}
}
}
@@ -31,6 +31,7 @@ import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
import suwayomi.tachidesk.manga.model.dataclass.ExtensionDataClass
import suwayomi.tachidesk.server.applicationSetup
import java.io.File
import java.util.concurrent.atomic.AtomicInteger
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestExtensions {
@@ -48,7 +49,7 @@ class TestExtensions {
@BeforeAll
fun setup() {
val dataRoot = File("tmp/TestDesk").absolutePath
System.setProperty("suwayomi.tachidesk.rootDir", dataRoot)
System.setProperty("suwayomi.tachidesk.server.rootDir", dataRoot)
applicationSetup()
setLoggingEnabled(false)
@@ -63,6 +64,7 @@ class TestExtensions {
updateExtension(it.pkgName)
}
else -> {
uninstallExtension(it.pkgName)
installExtension(it.pkgName)
}
}
@@ -77,10 +79,11 @@ class TestExtensions {
fun runTest() {
runBlocking(Dispatchers.Default) {
val semaphore = Semaphore(10)
sources.mapIndexed { index, source ->
val popularCount = AtomicInteger(1)
sources.map { source ->
async {
semaphore.withPermit {
logger.info { "$index - Now fetching popular manga from $source" }
logger.info { "${popularCount.getAndIncrement()} - Now fetching popular manga from $source" }
try {
mangaToFetch += source to (
source.fetchPopularManga(1)
@@ -102,10 +105,11 @@ class TestExtensions {
)
logger.info { "Now fetching manga info from ${mangaToFetch.size} sources" }
mangaToFetch.mapIndexed { index, (source, manga) ->
val mangaCount = AtomicInteger(1)
mangaToFetch.map { (source, manga) ->
async {
semaphore.withPermit {
logger.info { "$index - Now fetching manga from $source" }
logger.info { "${mangaCount.getAndIncrement()} - Now fetching manga from $source" }
try {
manga.copyFrom(source.fetchMangaDetails(manga).awaitSingleRepeat())
manga.initialized = true
@@ -127,10 +131,11 @@ class TestExtensions {
)
logger.info { "Now fetching manga chapters from ${mangaToFetch.size} sources" }
mangaToFetch.filter { it.second.initialized }.mapIndexed { index, (source, manga) ->
val chapterCount = AtomicInteger(1)
mangaToFetch.filter { it.second.initialized }.map { (source, manga) ->
async {
semaphore.withPermit {
logger.info { "$index - Now fetching manga chapters from $source" }
logger.info { "${chapterCount.getAndIncrement()} - Now fetching manga chapters from $source" }
try {
chaptersToFetch += Triple(
source,
@@ -160,10 +165,11 @@ class TestExtensions {
}
)
chaptersToFetch.mapIndexed { index, (source, manga, chapter) ->
val pageListCount = AtomicInteger(1)
chaptersToFetch.map { (source, manga, chapter) ->
async {
semaphore.withPermit {
logger.info { "$index - Now fetching page list from $source" }
logger.info { "${pageListCount.getAndIncrement()} - Now fetching page list from $source" }
try {
source.fetchPageList(chapter).awaitSingleRepeat()
} catch (e: Exception) {