conventionschedule-android/app/src/main/java/com/adlerosn/brasilfurfest/schedule/managers/ConventionJsonReader.kt

455 lines
22 KiB
Kotlin

package com.adlerosn.brasilfurfest.schedule.managers
import androidx.annotation.DrawableRes
import com.adlerosn.brasilfurfest.R
import com.adlerosn.brasilfurfest.helper.*
import com.adlerosn.brasilfurfest.schedule.abstractDataTypes.GregorianCalendarRange
import com.adlerosn.brasilfurfest.schedule.abstractDataTypes.convention.*
import org.json.JSONArray
import org.json.JSONObject
import java.io.File
import java.io.InputStream
import java.util.*
object ConventionJsonReader {
internal enum class Icons(@DrawableRes val drawableId: Int) {
ic_telegram(R.drawable.ic_telegram),
ic_twitter(R.drawable.ic_twitter),
ic_facebook_official(R.drawable.ic_facebook_official),
ic_youtube_play(R.drawable.ic_youtube_play),
ic_instagram(R.drawable.ic_instagram),
ic_web_black_24dp(R.drawable.ic_web_black_24dp)
}
internal class UuidHashResolver{
val conventionSeries = mutableMapOf<String, ConventionSeries>()
val conventions = mutableMapOf<String, Convention>()
val events = mutableMapOf<String, Event>()
val places = mutableMapOf<String, Place>()
val registrationTiers = mutableMapOf<String, RegistrationTier>()
val socialMedias = mutableMapOf<String, SocialMedia>()
val tags = mutableMapOf<String, Tag>()
val languageStrings = mutableMapOf<String, Map<Language, String>>()
val additionalRules = mutableMapOf<String, AddtionalRules>()
val banners = mutableMapOf<String, Banner>()
val maps = mutableMapOf<String, MapImage>()
val registrationLinks = mutableMapOf<String, RegistrationLink>()
val announcements = mutableMapOf<String, Announcement>()
}
fun readFromFile(file: String) =
readFromFile(File(file))
fun readFromFile(file: File) =
readFromJson(file.readText(Charsets.UTF_8))
fun readFromInputStream(inputStream: InputStream): ConventionSeries =
readFromJson(inputStream.bufferedReader(Charsets.UTF_8).readText())
fun readFromByteArray(byteArray: ByteArray): ConventionSeries =
readFromInputStream(byteArray.inputStream())
fun readFromJson(json: String) =
readFromJson(JSONObject(json))
fun readFromJson(json: JSONObject) =
getConventionSeries(json, UuidHashResolver())
private fun getConventionSeries(jsonObject: JSONObject, uuidHashResolver: UuidHashResolver): ConventionSeries {
val uuid = jsonObject.getString("uuid")
val previouslyKnown = uuidHashResolver.conventionSeries[uuid]
if (previouslyKnown != null) return previouslyKnown
val name = parseLanguageString(jsonObject.getJSONObject("name"), uuidHashResolver)
val language = parseLanguage(jsonObject.getString("language"))
val timezone = parseTimeZone(jsonObject.getJSONObject("timezone"))
val statute = parseLanguageString(jsonObject.getJSONObject("statute"), uuidHashResolver)
val socialMedias = parseSocialMedias(jsonObject.getJSONArray("social_medias"), uuidHashResolver)
val editions = parseConventions(jsonObject.getJSONArray("editions"), uuidHashResolver, statute, socialMedias, name)
val featured = uuidHashResolver.conventions[jsonObject.getString("featured")] ?: uuidHashResolver.conventions.values.first()
val defaultBanner = parseLanguageImage(jsonObject.getJSONObject("default_banner"), uuidHashResolver)!!
val banners = parseBanners(jsonObject.getJSONArray("banners"), uuidHashResolver)
val announcements = parseAnnouncements(jsonObject.getJSONArray("announcements"), uuidHashResolver)
return ConventionSeries(
name,
featured,
editions,
timezone,
socialMedias,
language,
statute,
defaultBanner,
banners,
1.0/6,
announcements
).apply {
uuidHashResolver.conventionSeries[uuid] = this
}
}
private fun parseAnnouncements(jsonArray: JSONArray, uuidHashResolver: ConventionJsonReader.UuidHashResolver): List<Announcement> =
jsonArray.asSequence<JSONObject>().map { parseAnnouncement(it, uuidHashResolver) }.toList()
private fun parseAnnouncement(jsonObject: JSONObject, uuidHashResolver: UuidHashResolver): Announcement {
val uuid = jsonObject.getString("uuid")
val previouslyKnown = uuidHashResolver.announcements[uuid]
if (previouslyKnown != null) return previouslyKnown
val title = parseLanguageString(jsonObject.getJSONObject("title"), uuidHashResolver)
val body = parseLanguageString(jsonObject.getJSONObject("body"), uuidHashResolver)
val link = parseLanguageString(jsonObject.getJSONObject("link"), uuidHashResolver)
val created = parseGregCal(jsonObject.getJSONObject("created"))
val modified = parseGregCal(jsonObject.getJSONObject("modified"))
return Announcement(
uuid,
title,
body,
link,
created,
modified
).apply {
uuidHashResolver.announcements[uuid] = this
}
}
private fun parseBanners(jsonArray: JSONArray, uuidHashResolver: UuidHashResolver) =
jsonArray.asSequence<JSONObject>().map { parseBanner(it, uuidHashResolver) }.toList()
private fun parseBanner(jsonObject: JSONObject, uuidHashResolver: UuidHashResolver): Banner {
val uuid = jsonObject.getString("uuid")
val previouslyKnown = uuidHashResolver.banners[uuid]
if (previouslyKnown != null) return previouslyKnown
val alias = jsonObject.getString("alias")
val image = parseLanguageImage(jsonObject.getJSONObject("banner"), uuidHashResolver)!!
val lifetime = GregorianCalendarRange(
parseGregCal(jsonObject.getJSONObject("show_up_after")),
parseGregCal(jsonObject.getJSONObject("hide_after"))
)
return Banner(
alias,
lifetime,
image
).apply {
uuidHashResolver.banners[uuid] = this
}
}
private fun parseConventions(jsonArray: JSONArray, uuidHashResolver: UuidHashResolver, statute: Map<Language, String>, socialMedias: List<SocialMedia>, name: Map<Language, String>): List<Convention> =
jsonArray.asSequence<JSONObject>().map {
parseConvention(it, uuidHashResolver, statute, socialMedias, name)
}.toList()
private fun parseConvention(jsonObject: JSONObject, uuidHashResolver: UuidHashResolver, rules: Map<Language, String>, socialMedias: List<SocialMedia>, genericName: Map<Language, String>): Convention {
val uuid = jsonObject.getString("uuid")
val previouslyKnown = uuidHashResolver.conventions[uuid]
if (previouslyKnown != null) return previouslyKnown
val notificationFireMinutesBefore = jsonObject.getInt("fire_notifications_n_minutes_before")
val paddingStartOfDayMillis = parseGregCal(jsonObject.getJSONObject("convention_day_start_time")).let {
val hr = it.get(GregorianCalendar.HOUR_OF_DAY)
val mn = it.get(GregorianCalendar.MINUTE)
val sc = it.get(GregorianCalendar.SECOND)
((hr*3600L)+(mn*60L)+sc)*1000L
}
val paddingEndOfDayMillis = parseGregCal(jsonObject.getJSONObject("convention_day_end_time")).let {
val hr = it.get(GregorianCalendar.HOUR_OF_DAY)
val mn = it.get(GregorianCalendar.MINUTE)
val sc = it.get(GregorianCalendar.SECOND)
((hr*3600L)+(mn*60L)+sc)*1000L
}.let {
if (it == 0L)
24*3600*1000L
else
it
}
val name = parseLanguageString(jsonObject.getJSONObject("name"), uuidHashResolver)
val places = parsePlaces(jsonObject.getJSONArray("places"), uuidHashResolver)
val tags = parseTags(jsonObject.getJSONArray("tags"), uuidHashResolver)
val hashtagReminder = jsonObject.getString("hashtag_reminder")
val theme = parseLanguageString(jsonObject.getJSONObject("theme"), uuidHashResolver)
val registrationTiers = parseRegistrationTiers(jsonObject.getJSONArray("registration_tiers"), uuidHashResolver)
val lowestRegistrationTier = uuidHashResolver.registrationTiers[jsonObject.getString("lowest_registration_tier")]!!
val language = parseLanguage(jsonObject.getString("language"))
val r621checker = jsonObject.getBoolean("rule_621_checker")
val maps = parseMaps(jsonObject.getJSONArray("maps"), uuidHashResolver)
val imageDefaultEvent = parseLanguageImage(jsonObject.getJSONObject("image_default_event"), uuidHashResolver)
val imageFavorites = parseLanguageImage(jsonObject.getJSONObject("image_favorites"), uuidHashResolver)
val splitDayIn = jsonObject.getInt("split_day_into_n_parts")
val timeZone = parseTimeZone(jsonObject.getJSONObject("timezone"))
val nextEdition = parseGregCal(jsonObject.getJSONObject("next_edition"))
val officialLifespan = GregorianCalendarRange(
parseGregCal(jsonObject.getJSONObject("ceremony_opening_time")),
parseGregCal(jsonObject.getJSONObject("ceremony_closing_time"))
)
val registrationLinks = parseRegistrationLinks(jsonObject.getJSONArray("registration_links"), uuidHashResolver)
val additionalRules = parseAdditionalRulesPlural(jsonObject.getJSONArray("additional_rules"), uuidHashResolver)
val events = parseEvents(jsonObject.getJSONArray("events"), uuidHashResolver, language)
val eventDates = events.flatMap {
Pair(it.timeRange.start, it.timeRange.finish).toList()
}.distinctBy { it.timeInMillis }.sortedBy { it.timeInMillis }
val startCalendarOn = eventDates.first()
val endCalendarOn = eventDates.last()
val days = events.map {
it.timeRange.start.dayRange.start
}.distinctBy { it.timeInMillis }.sortedBy{ it.timeInMillis }
return Convention(
events,
places,
registrationTiers,
lowestRegistrationTier,
tags,
uuid,
name[language]!!,
genericName[language]!!,
theme,
rules,
additionalRules,
officialLifespan,
startCalendarOn,
endCalendarOn,
paddingStartOfDayMillis,
paddingEndOfDayMillis,
days,
splitDayIn,
timeZone,
socialMedias,
notificationFireMinutesBefore,
r621checker,
maps,
registrationLinks,
imageDefaultEvent,
imageFavorites,
hashtagReminder,
nextEdition
).apply { uuidHashResolver.conventions[uuid] = this }
}
private fun parseRegistrationLinks(jsonArray: JSONArray, uuidHashResolver: ConventionJsonReader.UuidHashResolver): List<RegistrationLink> =
jsonArray.asSequence<JSONObject>().map { parseRegistrationLink(it, uuidHashResolver) }.toList()
private fun parseRegistrationLink(jsonObject: JSONObject, uuidHashResolver: UuidHashResolver): RegistrationLink {
val uuid = jsonObject.getString("uuid")
val previouslyKnown = uuidHashResolver.registrationLinks[uuid]
if (previouslyKnown != null) return previouslyKnown
val ordering = jsonObject.getInt("ordering")
val alias = jsonObject.getString("alias")
val name = parseLanguageString(jsonObject.getJSONObject("name"), uuidHashResolver)
val url = parseLanguageString(jsonObject.getJSONObject("url"), uuidHashResolver)
val color = parseColorNullable(jsonObject.getJSONObject("color"))
val lifespan = GregorianCalendarRange(
parseGregCal(jsonObject.getJSONObject("appears")),
parseGregCal(jsonObject.getJSONObject("vanishes"))
)
return RegistrationLink(
alias,
ordering,
name,
url,
color,
lifespan
).apply { uuidHashResolver.registrationLinks[uuid] = this }
}
private fun parseColorNullable(jsonObject: JSONObject?): Int? =
try {
parseColor(jsonObject!!)
} catch (t: Throwable) {
null
}
private fun parseMaps(jsonArray: JSONArray, uuidHashResolver: ConventionJsonReader.UuidHashResolver): List<MapImage> =
jsonArray.asSequence<JSONObject>().map { parseMap(it, uuidHashResolver) }.toList()
private fun parseMap(jsonObject: JSONObject, uuidHashResolver: ConventionJsonReader.UuidHashResolver): MapImage {
val uuid = jsonObject.getString("uuid")
val previouslyKnown = uuidHashResolver.maps[uuid]
if (previouslyKnown != null) return previouslyKnown
val alias = jsonObject.getString("alias")
val name = parseLanguageString(jsonObject.getJSONObject("name"), uuidHashResolver)
val image = parseLanguageImage(jsonObject.getJSONObject("image"), uuidHashResolver)!!
return MapImage(
alias,
name,
image
).apply { uuidHashResolver.maps[uuid] = this }
}
private fun parseAdditionalRulesPlural(jsonArray: JSONArray, uuidHashResolver: UuidHashResolver): List<AddtionalRules> =
jsonArray.asSequence<JSONObject>().map {
parseAdditionalRules(it, uuidHashResolver)
}.toList()
private fun parseAdditionalRules(jsonObject: JSONObject, uuidHashResolver: UuidHashResolver): AddtionalRules {
val uuid = jsonObject.getString("uuid")
val previouslyKnown = uuidHashResolver.additionalRules[uuid]
if (previouslyKnown != null) return previouslyKnown
val names = parseLanguageString(jsonObject.getJSONObject("name"), uuidHashResolver)
val rules = parseLanguageString(jsonObject.getJSONObject("rules"), uuidHashResolver)
return AddtionalRules(
names,
rules
).apply { uuidHashResolver.additionalRules[uuid] = this }
}
private fun parseEvents(jsonArray: JSONArray, uuidHashResolver: UuidHashResolver, conventionLanguage: Language): List<Event> =
jsonArray.asSequence<JSONObject>().map {
parseEvent(it, uuidHashResolver, conventionLanguage)
}.toList()
private fun parseEvent(jsonObject: JSONObject, uuidHashResolver: UuidHashResolver, conventionLanguage: Language): Event {
val uuid = jsonObject.getString("uuid")
val previouslyKnown = uuidHashResolver.events[uuid]
if (previouslyKnown != null) return previouslyKnown
val conbookId = jsonObject.getString("conbook_key")
val conbookPages = jsonObject.getJSONArray("conbook_pages").toList<Int>()
val titles = parseLanguageString(jsonObject.getJSONObject("title"), uuidHashResolver)
val subtitles = parseLanguageString(jsonObject.getJSONObject("subtitle"), uuidHashResolver)
val descriptions = parseLanguageString(jsonObject.getJSONObject("description"), uuidHashResolver)
val timeRange = GregorianCalendarRange(
parseGregCal(jsonObject.getJSONObject("time_start")),
parseGregCal(jsonObject.getJSONObject("time_end"))
)
val places = jsonObject.getJSONArray("places").toList<String>().map { uuidHashResolver.places[it]!! }
val tags = jsonObject.getJSONArray("categories").toList<String>().map { uuidHashResolver.tags[it]!! }
val attendableBy = jsonObject.getJSONArray("attendable_by").toList<String>().map { uuidHashResolver.registrationTiers[it]!! }
val language = parseLanguage(jsonObject.getString("language")).let {
if(it==conventionLanguage) null else it }
val hiddenFromTimeTable = jsonObject.getBoolean("hidden_from_time_table")
val assetImage = parseLanguageImage(jsonObject.getJSONObject("picture"), uuidHashResolver)
return Event(
conbookId,
conbookPages,
titles,
subtitles,
descriptions,
timeRange,
places,
attendableBy,
tags,
language,
hiddenFromTimeTable,
assetImage
).apply { uuidHashResolver.events[uuid] = this }
}
private fun parseGregCal(jsonObject: JSONObject): GregorianCalendar =
GregorianCalendar(
jsonObject.getInt("yr"),
jsonObject.getInt("mo").minus(1),
jsonObject.getInt("dy"),
jsonObject.getInt("hr"),
jsonObject.getInt("mn"),
jsonObject.getInt("sc")
)
private fun parseRegistrationTiers(jsonArray: JSONArray, uuidHashResolver: ConventionJsonReader.UuidHashResolver): List<RegistrationTier> =
jsonArray.asSequence<JSONObject>().map {
parseRegistrationTier(it, uuidHashResolver)
}.toList()
private fun parseRegistrationTier(jsonObject: JSONObject, uuidHashResolver: UuidHashResolver): RegistrationTier {
val uuid = jsonObject.getString("uuid")
val previouslyKnown = uuidHashResolver.registrationTiers[uuid]
if (previouslyKnown != null) return previouslyKnown
val level = jsonObject.getInt("level")
val name = parseLanguageString(jsonObject.getJSONObject("tier"), uuidHashResolver)
val benefits = jsonObject.getJSONArray("benefits").asSequence<JSONObject>().map {
parseLanguageString(it, uuidHashResolver)
}.toList()
return RegistrationTier(
level,
name,
benefits
).apply { uuidHashResolver.registrationTiers[uuid] = this }
}
private fun parseTags(jsonArray: JSONArray, uuidHashResolver: ConventionJsonReader.UuidHashResolver): List<Tag> =
jsonArray.asSequence<JSONObject>().map {
parseTag(it, uuidHashResolver)
}.toList()
private fun parseTag(jsonObject: JSONObject, uuidHashResolver: ConventionJsonReader.UuidHashResolver): Tag {
val uuid = jsonObject.getString("uuid")
val previouslyKnown = uuidHashResolver.tags[uuid]
if (previouslyKnown != null) return previouslyKnown
val label = parseLanguageString(jsonObject.getJSONObject("label"), uuidHashResolver)
val color = parseColor(jsonObject.getJSONObject("color"))
return Tag(label, color).apply { uuidHashResolver.tags[uuid] = this }
}
private fun parsePlaces(jsonArray: JSONArray, uuidHashResolver: ConventionJsonReader.UuidHashResolver): List<Place> =
jsonArray.asSequence<JSONObject>().map {
parsePlace(it, uuidHashResolver)
}.toList()
private fun parsePlace(jsonObject: JSONObject, uuidHashResolver: ConventionJsonReader.UuidHashResolver): Place {
val uuid = jsonObject.getString("uuid")
val previouslyKnown = uuidHashResolver.places[uuid]
if (previouslyKnown != null) return previouslyKnown
val label = parseLanguageString(jsonObject.getJSONObject("label"), uuidHashResolver)
val color = parseColor(jsonObject.getJSONObject("color"))
return Place(label, color).apply { uuidHashResolver.places[uuid] = this }
}
private fun parseSocialMedias(jsonArray: JSONArray, uuidHashResolver: ConventionJsonReader.UuidHashResolver): List<SocialMedia> =
jsonArray.asSequence<JSONObject>().map {
parseSocialMedia(it, uuidHashResolver)
}.toList()
private fun parseSocialMedia(jsonObject: JSONObject, uuidHashResolver: ConventionJsonReader.UuidHashResolver): SocialMedia {
val uuid = jsonObject.getString("alias")
val previouslyKnown = uuidHashResolver.socialMedias[uuid]
if (previouslyKnown != null) return previouslyKnown
val name = parseLanguageString(jsonObject.getJSONObject("name"), uuidHashResolver)
val url = parseLanguageString(jsonObject.getJSONObject("url"), uuidHashResolver)
val color = parseColor(jsonObject.getJSONObject("color"))
val icon = Icons.valueOf(jsonObject.getString("icon")).drawableId
val sm = SocialMedia(
name,
url,
icon,
color
).apply { uuidHashResolver.socialMedias[uuid] = this }
return sm
}
private fun parseColor(jsonObject: JSONObject): Int =
jsonObject
.getString("color")
.toColor()
private fun parseTimeZone(jsonObject: JSONObject): TimeZone {
val name = jsonObject.getString("name")
val hr = jsonObject.getInt("hr")
val mn = jsonObject.getInt("mn")
val offset = hr * 3600 * 1000 + ( mn * 60 * 1000 * ( if ( hr >= 0 ) 1 else -1 ))
return SimpleTimeZone(offset, name)
}
private fun parseLanguage(string: String): Language {
return Language.values().firstOrNull { it.locale.language == string } ?: Language.EN
}
private fun parseLanguageImage(jsonObject: JSONObject, uuidHashResolver: UuidHashResolver): Map<Language, String>? =
parseLanguageString(jsonObject, uuidHashResolver, true)
.filterNot {
it.value.isBlank()
}.let {
if (it.isEmpty()) null else it
}
private fun parseLanguageString(jsonObject: JSONObject, uuidHashResolver: UuidHashResolver, isPath: Boolean = false): Map<Language, String> {
val uuid: String? = jsonObject.getString("uuid")
val previouslyKnown = uuidHashResolver.languageStrings[uuid]
if (previouslyKnown != null) return previouslyKnown
val langString = Language.values().map {
it to
(if(jsonObject.has(it.locale.language)) jsonObject.getString(it.locale.language) else "").let {
if(isPath)
it.trimStart('/').trim()
else
it
}
}.toMap()
uuid?.let { uuidHashResolver.languageStrings[it] = langString }
return langString
}
}