1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
//! Derive macro for automatically implementing `jaded::FromJava` for Rust types.
//!
//! This is provided as a `FromJava` macro.
//!
//! Most implementations of `FromJava` will be very similar and automatically deriving them makes
//! much more sense than writing almost identical impl blocks for every type.
//!
//! For a 'simple' bean style Java class, eg
//!
//! ```java
//! public class Person implements Serializable {
//!     private String firstName;
//!     private String familyName;
//!     private int age;
//! }
//! ```
//! The rust equivalent can be automatically derived
//! ```
//! # use ::jaded_derive::FromJava;
//! #[derive(FromJava)]
//! struct Person {
//!     firstName: String,
//!     familyName: String,
//!     age: i32,
//! }
//! ```
//!
//! To allow both Java and rust to keep to their respective naming conventions,
//! a 'rename' feature is available that allows camelCase java fields to be
//! read into snake_case rust fields
//!
//! ```
//! # use ::jaded_derive::FromJava;
//! #[derive(FromJava)]
//! #[jaded(rename)]
//! struct Person {
//!     first_name: String,
//!     family_name: String,
//!     age: i32,
//! }
//! ```
//!
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use proc_macro_error::{diagnostic, proc_macro_error, Diagnostic, Level, ResultExt};
use syn::{parse2, DeriveInput};

mod attributes;
mod internal;
mod model;

use model::Reader;
type Result<T> = std::result::Result<T, Diagnostic>;

/// # Derive FromJava for custom Rust types
///
/// By default each field is read from a field of the same name in the serialized
/// Java object. In this simple form, all fields must also implement `FromJava`
/// (similar to `derive(Debug)` requiring fields to implement `Debug`).
///
/// To modify the standard behaviour a `jaded` attribute is available
/// ## On structs
/// ### `#[jaded(rename)]`
/// Convert all fields to camelCase before looking them up in the Java object.
/// This allows a field `first_name` to be read from a Java field `firstName`.
///
/// NB. This requires the `renaming` feature as it requires additional
/// dependencies.
/// ### `#[jaded(class = "com.example.Demo")]`
/// By default, Jaded will try to read from the given fields and use them if
/// they're present without first checking the class of the object. This means
/// a class `Foo` with a single `String name` field will be treated exactly
/// the same as a class `Bar` that also has a single `String name` field.
/// This attribute allows a specific class to be given which will be checked
/// at conversion time.
///
/// ## On Fields
/// ### `#[jaded(field = "fieldName")]`
/// Customise the name of the Java field being read.
/// By default each rust field is read from a Java field of the same name. While
/// this is usually fine, it can be useful in some situations to supply a more
/// appropriate name. This allows code to better match domain language or to
/// avoid keyword clashes (eg type is a valid Java field name but can't be used
/// in Rust).
/// This could also be used to rename fields to match naming conventions if
/// the struct level `rename` attribute is not used.
///
/// ### `#[jaded(extract(method_name))]`
/// Serializable classes in Java can customise the way the builtin Serializer
/// writes their state to the output stream. As there is no standard for how this
/// may look, the custom data is written to an annotations field in the
/// intermediate representation and the field name is not available.
/// This data can be read using an interface similar to Java's `ObjectInputStream`
/// but the specifics must be user implemented and can't be derived.
/// The `extract` attribute allows a user method to be used to read a field's
/// value from the custom data stream.
///
/// The value given to the `extract` attribute must be a valid path to a function
/// with the signature:
/// `(&mut AnnotationIter) -> ConversionResult<T> where T: Into<F>`
/// and `F` is the type of the rust field being read.
///
/// By default, the `AnnotationIter` instance passed to the method is the stream
/// written by the concrete class being read. In rare situations, a parent class
/// may also write their own custom data and these can be read by specifying
/// which class in the hierarchy to use where `0` (the default) is the first class
/// in the hierarchy (top down) to include annotations, and 1 is the first subclass
/// of it to include annotations etc.
/// This is passed after the path to the method:
/// `#[jaded(extract(method_name, 1))]`.
///
/// NB. Fields that read from the annotation stream are all passed the same
///     instance of the `AnnotationIter`. This means that later fields do
///     not have to 'fast-forward' through the previous field's data but does
///     mean that the order fields are declared in is important.
/// NB. Fields read via a custom extract method do not have to implement `FromJava`.
#[proc_macro_derive(FromJava, attributes(jaded))]
#[proc_macro_error]
pub fn from_java(input: TokenStream) -> TokenStream {
    check_features();

    let input = parse(input.into()).unwrap_or_abort();
    let model = Reader::read_model(input).unwrap_or_abort();
    model.to_tokens().into()
}

/// Previous versions had a renaming feature to enable bulk camelcass conversion.
/// This is now provided by default so warn if users are still using it.
fn check_features() {
    #[cfg(feature = "renaming")]
    ::proc_macro_error::emit_warning!(
        proc_macro2::Span::call_site(),
        "Renaming feature has been deprecated. Functionality is available with no features enabled"
    );
}

/// Convert the original token stream into a DeriveInput instance.
fn parse(input: TokenStream2) -> Result<DeriveInput> {
    parse2::<DeriveInput>(input).map_err(|e| {
        diagnostic!(
            e.span(),
            Level::Error,
            "Only structs or enums can have FromJava derived",
        )
    })
}