Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Model using jackson #8810

Closed
wants to merge 2 commits into from
Closed

Conversation

TarekkMA
Copy link
Contributor

@TarekkMA TarekkMA commented May 12, 2021

Pull Request template

Purpose / Description

Describe the problem or feature and motivation

Fixes

Related to #8668

Approach

How does this change address the problem?

How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration (SDK version(s), emulator or physical, etc)

Learning (optional, can help others)

Describe the research stage

Links to blog posts, patterns, libraries or addons used to solve this problem

Checklist

Please, go through these checks before submitting the PR.

  • You have not changed whitespace unnecessarily (it makes diffs hard to read)
  • You have a descriptive commit message with a short title (first line, max 50 chars).
  • Your code follows the style of the project (e.g. never omit braces in if statements)
  • You have commented your code, particularly in hard-to-understand areas
  • You have performed a self-review of your own code
  • UI changes: include screenshots of all affected screens (in particular showing any new or changed strings)
  • UI Changes: You have tested your change using the Google Accessibility Scanner

@TarekkMA TarekkMA marked this pull request as draft May 12, 2021 15:01
@TarekkMA TarekkMA changed the title wip models using jackson WIP: Model using jackson May 12, 2021
@TarekkMA
Copy link
Contributor Author

TarekkMA commented May 12, 2021

a proof of concept PR to show how Jackson can be used to parse Model json.

Things that are highlited in this PR:

  • Parse array model.req into a class using @JsonFormat(shape= ARRAY)
  • Names of the fields are dependant from the name of the json
    • bqfmt is now called mBrowserQuestionFormat and we use setBrowserQuestionFormat and getBrowserQuestionFormat to change it.
  • Unknown props will be retained and not lost
  • enum like classes (RequirementType, ModelType) that do save unknown values

Copy link
Member

@Arthur-Milchior Arthur-Milchior left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enum like classes (RequirementType, ModelType) that do save unknown values

What do you mean by "unknown values"?
You can read more about requirement on https://github.com/Arthur-Milchior/anki/blob/commented/documentation/templates_generation_rules.md
I think it is not used anymore and will probably disappear with rust

public static RequirementType[] values = new RequirementType[] {NONE, ALL, ANY};

@JsonCreator
public static RequirementType createFromValue(@Nullable String value) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Nullable RequirementType

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used @Contract("null -> null; !null -> !null")

}


private final String mValue;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NonNull String

}
RequirementType that = (RequirementType) o;
return mUnknown == that.mUnknown &&
Objects.equals(mValue, that.mValue);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can probably uses mValue.equals(that.mValue) since you know that mValue can't be null

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, that was the auto-generated code from the IDE.

@JsonProperty("ord")
private long mOrd;
private Long mOrd;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What the point of "move to object instead of primitives"
If we decide to go with Object, then this could be directly in the first commit I assume.

How did you test that it has the same performance?
Creating a Long object takes time. On long operation such as searching for empty card or cleaning collection, it's actually noticeable in the profiler.

You may be perfectly right, since all sqlite queries only take objects and no primitive, so we always convert long to Long when we need to send a query.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did a benchmark between one implementation with primitives and the other with objects and they both are pretty much the same.

The benefit that we will have with objects is being able to set them to null, and Jackson will not include any null values to the outputted json.

while primitives can't be null.

If the idea of null is not very okay we can use Optional<T> for every field.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

about the commits I will clean them once I make everything ready

Copy link
Member

@Arthur-Milchior Arthur-Milchior left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, last comment was sent too early.

Thanks a lot for attacking this problem. I really appreciate it.

Did you try to use it really by replacing the models from libanki by your models?

I'm of two of mind.
On the one hand, it's extremely verbose. I don't like it. It make it hard to review because a single letter typo would not be easy to catch (that would be one good point of using static String instead of literals as I wanted to, but I digress). It also seems to add to the code complexity, while in fact almost everything is really simple here.

On the other hand, having getter/setter instead of accessing elements by String will be far more efficient, as there is not hashtable involved. I really really love it. Similarly, not having to deal with transforming checked exception into unchecked one will clearly be a great win!


import androidx.annotation.Nullable;

public class ModelType {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably would want to reuse com/ichi2/libanki/Model.java since it also represents a model

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, this is the goal, but replacing the old model with the new one will result in many refactorings. so I'm saving it to the last step

@TarekkMA
Copy link
Contributor Author

enum like classes (RequirementType, ModelType) that do save unknown values

What do you mean by "unknown values"?
You can read more about requirement on https://github.com/Arthur-Milchior/anki/blob/commented/documentation/templates_generation_rules.md
I think it is not used anymore and will probably disappear with rust

@Arthur-Milchior the goal is to not miss with the data if we don't know how to handle it. for example if have an enum defined in AnkiDroid with A, B we need the ability to preserve other unknown values like C, D. incase Anki desktop implemented a feature that is not yet found in AnkiDroid.

tbh my knowledge of how anki handles the low-level details is not good, I will try to read the attached link

@Arthur-Milchior
Copy link
Member

You really don't have to read this link. It's probably deprecated and only used on old version of anki.

I understand better what you mean about "unknown", thanks. Nice to see you allow to accept any kind of value later!

@TarekkMA
Copy link
Contributor Author

Yes it's very verbose, the idea behind adding annotation to every field is to make the field name independent of the json name.

and also becase the nature of java being verbose if we go with kotlin we would only have annotated constructor that's it.

we will get setter/getter copy (clone) method equals/hash code all for free

data class Model(
        @JsonProperty("id")
        var id: Long?,
        @JsonProperty("name")
        var name: String?,
        @JsonProperty("type")
        var type: ModelType?,
        @JsonProperty("mod")
        var mod: Long?,
        @JsonProperty("usn")
        var usn: Long?,
        @JsonProperty("sortf")
        var sortf: Long?,
        @JsonProperty("did")
        var did: Long?,
        @JsonProperty("tmpls")
        var tmpls: List<Template>?,
        @JsonProperty("flds")
        var flds: List<Field>?,
        @JsonProperty("css")
        var css: String?,
        @JsonProperty("latexPre")
        var latexPre: String?,
        @JsonProperty("latexPost")
        var latexPost: String?,
        @JsonProperty("latexsvg")
        var latexsvg: Boolean?,
        @JsonProperty("req")
        var req: List<Requirement>?,
        @JsonProperty("tags")
        var tags: List<Any>?,
        @JsonProperty("vers")
        var vers: List<Any>?,
        @JsonIgnore
        @get:JsonAnyGetter
        val mAdditionalProperties: MutableMap<String, Any> = HashMap()
) {
    @JsonAnySetter
    fun setAdditionalProperty(name: String, value: Any) {
        mAdditionalProperties[name] = value
    }
}

and we can use static constants inside annotations.

@TarekkMA
Copy link
Contributor Author

I have made the code less verbose, we don't need to annotate every setter and getter, annotating the field is enough I think.

@dae
Copy link
Contributor

dae commented May 13, 2021

The latest Rust code exposes the various Anki objects like notetypes as typed protobuf messages, and notetypes are stored in the database as protobuf internally. While JSON-bound classes are a nice ergonomic improvement, given the changes coming with backend updates, I'm not sure it'd be worth the work involved to use them at the moment. Presumably you can use Jackson with the existing string-key API to get performance gains without requiring so many code changes?

@TarekkMA
Copy link
Contributor Author

Thank you, Having the Anki objects saves as binary will be very nice since some JSON fields are more than 30 MBs. hopefully this will speed things

I will try to use jackson treemodel to replace the current json setup.

I noticed that much of the code in are depended on JSONObject/JSONArray object and even objects like Model and others are just subclasses of them. so lots of work are waiting in the future.

@github-actions
Copy link
Contributor

Hello 👋, this PR has been opened for more than 2 months with no activity on it. If you think this is a mistake please comment and ping a maintainer to get this merged ASAP! Thanks for contributing! You have 7 days until this gets closed automatically

@github-actions github-actions bot added the Stale label Jul 12, 2021
@Arthur-Milchior
Copy link
Member

Let us know @TarekkMA if you want us to take over it?

@github-actions github-actions bot removed the Stale label Jul 12, 2021
@TarekkMA
Copy link
Contributor Author

@Arthur-Milchior Yes, thank you. Sorry I have some issues I'm dealing with 😞.

@github-actions
Copy link
Contributor

Hello 👋, this PR has been opened for more than 2 months with no activity on it. If you think this is a mistake please comment and ping a maintainer to get this merged ASAP! Thanks for contributing! You have 7 days until this gets closed automatically

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants