Tak, sorry. Moja niecierpliwość wzięła górę. Zwyczajnie chciałem się poddać ocenie. Tomcat jest bo zrobiłem go wcześniej, a nie wydzieliłem procesora do osobnego projektu bo ...nie wiem, a powinienem i będzie. Na razie jest mi wygodnie, testuję sobie procesor na module tomcat. Nie dostarcza na razie nic poza generowaniem pliku properties i inicjalizowania pól statycznych. To co chciałem osiągnąłem - Postaram się w najbliższym czasie dostarczyć kompletny osobny projekt procesora adnotacji z testami end to end jako zależność. Do jakiej wersji javy zejść, 17?
Mam zamiar wprowadzić inicjalizacje pól, oprócz static on init, również w momencie tworzenia obiektu - na polach instancyjnych, może hiden class (dla security) , możliwość rozdzielania propertisów np osobno do db, osobno do serwera, wprowadzenie własnych nazw dla value oprócz defaultowego, oraz możliwość generowania nie tylko propertisów ale również yamla, tomla, confa, jsona.
Prosił bym o skupienie się na module lib.
Wyjaśnię może pokrótce dlaczego jest to tak rozbudowane i podzielone.
Przede wszystkim już w momencie tworzenia procesora wiedziałem że chcę mieć bezproblemową możliwość rozbudowy - dlatego też pojawiła się klasa SupportDispatcher która dostarcza odpowiednią implementację w zależności od wyszukanych przez roundenvironment anotowanych elementów dla obsługiwanej przez procesor adnotacji. Rozbudowa klasy rozszerzającej AbstractProcesor polegała na dostarczeniu jak największej ilości informacji użytkownikowi procesora. SupportDestiny prawdopodobnie nie będzie potrzebne. SupportDestinyPUBLIC odnosi się do generowanych klas z których użytkownik korzysta zanim zostały utworzone. Czyli coś w stylu - piszemy sobie kod i wszystko świeci na czerwono. Nie podoba mi się coś takiego, wprowadza chaos, ale zaimplementowałem taką możliwość - nic wielkiego ( wyczytałem od jakiegoś annotation processor guru na stackoverflow że nie należy przeszkadzać javacowi żadnymi wyjątkami kiedy generuje publicznie dostępny kod nawet jeśli użytkownik popełnił błędy. Javac a w zasadzie procesor w kolejnych rundach będzie podejmował kolejne próby wygenerowania jak największej ilości kodu. Użytkownik jak coś spieprzy to dostanie trochę mniej funkcjonalności - ale dostanie.)
UtilBucket zawiera niepowiązane z żadną klasą helper metody - pojawił się później w momencie refactoringu i rozdzielenia odpowiedzialności. ProcLogger również, chciałem wywalić z klas wszystkie messegery (zwyczajnie opakować w coś znanego i od razu kojarzonego jak Logger) . ProcEnvironment również pojawił się później. Pojawił się dlatego że wydziela środowisko dla obsługi wyszukanych elementów które jest tylko jedno w cyklu życia procesora - jest thread safe. SourceBuilder jak sama nazwa wskazuje odpowiada tylko za zbudowanie klasy na podstawie specyfikacji albo pliku - używa do tego Fillera który jest jedynym współdzielonym elementem (dla tego też ewentualna współbieżność została oddelegowana do osobnej klasy - aby wyglądało na prosto i zrozumiale - ale chyba tak nie jest - proszę o komentarz). W końcu odpowiedzialność - implementacje tego jak tworzyć source spoczywa na klasach pakietu autoconfigurablegroup (obsługującego adnotację @Autoconfig). Tam i tylko tam jest zaimplementowane tworzenie specyfikacji klasy (TypeSpec) oraz pliku propierties . ElementProvider dostarcza prawidłowe elementy dla klas ich używających tak aby zdjąc z nich odpowiedzialność za wyselekcjonowanie prawidłowo anotowanych elementów. W zasadzie lepiej jak by ElementProvider był bezstanowy ( nazywał by się wtedy ElementSelector) a za stan odpowiadał by jakiś obiekt cachujący prawidłowe elementy ( po selekcji) . Na ten moment implementacja takiego rozwiązanie nie żadnego merytorycznego uzasadnienia
Jeśli to mało mogę zagłębić się w szczegóły.
To co chciałem wstępnie zrobiłem - podzieliłem na elementy reużywalne, oddelegowałem odpowiedzialności do wyspecjalizowanych klas, ograniczyłem zależności, wskazałem gdzie leży kwestia ewentualnej współbieżności. Teraz dodawanie kolejnych funkcjonalności w ramach tej samej adnotacji polega na kopaniu tylko w pakiecie obsługującym tą adnotację, a dodanie kolejnej adnotacji polega na utworzeniu pakietu obsługującego ją gdzie implementacja korzysta z reużywalnych elementów czyli SourceBuilder, ProcEnvironment, ElementProvider, ProcLogger. Do StartupProcessor wystarczy dodać adnotację którą ma obsługiwać, do SupportDispatchera również i tyle.
Droga do współbieżnego generowania źródeł też jest otwarta. Metoda generate może być uruchamiana na wszystkich wyspecjalizowanych klasach jednocześnie ( wystarczy kilka modyfikacji w strukturach danych na thread safe StartupProcessora), create też.
Słowo na temat AutoConfigBase (internal bez dostępu)- jest to klasa która implementuje wszystko to czego procesor adnotacji nie musi tworzyć ( bo po co ). Żyje sobie w tym samym pakiecie co wygenerowana klasa procesorem adnotacji.
To co jest dostępne dla użytkownika jest w pakiecie api ( adnotacja i handler z 1 metodą). AutoInitialize wygląda tak a nie inaczej ze względu na organizację kodu. Dla mnie jest to przejrzyste - użytkownik ma tylko dostęp do jednej metody.
Natomiast ktoś kto chciał by się pobawić procesorem ma dostęp do tych klas pakietu processor które są publiczne.
Przykład wygenerowanej klasy:
public final class AutoConfig {
private static final Map<String, String[]> autoGenMap = new HashMap<>();
static {
String key;
String[] autoGenVal;
autoGenVal = new String[3];
autoGenVal[0] = "org.aujee.com.searchengine.TomcatServer";
autoGenVal[1] = "inSecurePort";
autoGenVal[2] = "Integer";
key = autoGenVal[0] + "." + autoGenVal[1];
autoGenMap.put(key, autoGenVal);
autoGenVal = new String[3];
autoGenVal[0] = "org.aujee.com.searchengine.TomcatServer";
autoGenVal[1] = "keyStoreFile";
autoGenVal[2] = "String";
key = autoGenVal[0] + "." + autoGenVal[1];
autoGenMap.put(key, autoGenVal);
autoGenVal = new String[3];
autoGenVal[0] = "org.aujee.com.searchengine.TomcatServer";
autoGenVal[1] = "hostName";
autoGenVal[2] = "String";
key = autoGenVal[0] + "." + autoGenVal[1];
autoGenMap.put(key, autoGenVal);
autoGenVal = new String[3];
autoGenVal[0] = "org.aujee.com.searchengine.TomcatServer";
autoGenVal[1] = "keyAlias";
autoGenVal[2] = "String";
key = autoGenVal[0] + "." + autoGenVal[1];
autoGenMap.put(key, autoGenVal);
autoGenVal = new String[3];
autoGenVal[0] = "org.aujee.com.searchengine.TomcatServer";
autoGenVal[1] = "securePort";
autoGenVal[2] = "int";
key = autoGenVal[0] + "." + autoGenVal[1];
autoGenMap.put(key, autoGenVal);
autoGenVal = new String[3];
autoGenVal[0] = "org.aujee.com.searchengine.TomcatServer";
autoGenVal[1] = "keystorePassword";
autoGenVal[2] = "String";
key = autoGenVal[0] + "." + autoGenVal[1];
autoGenMap.put(key, autoGenVal);
}
private AutoConfig() {
}
public static void initialize() {
AutoConfBase.load(autoGenMap);
AutoConfBase.initialize();
}
}
Wsółbieżne tworzenie źródeł nie za bardzo ma sens w obecnym stanie, ale ewentualna implementacja jest bezproblemowa.
Co istotne implementacja kolejnych funkcjonalnosći nie wymaga zastanawiania się czy kontrakt z api zostanie złamany.
Mam kilka pomysłów na kolejne wyspecjalizowane funkcjonalności, jednak na razie nie chce o nich gadać -nie wiem czy jest sens robienia tego ( zwyczajnie po roku nauki jeszcze nie wiem co ma sens a co nie). na pewno było by to związane z brakiem stanu, lambdami, niemutowalnością, współbieżnością...
Jak na razie muszę coś porobić w springu (chociaż ciągnię mnie do robienia niskopoziomowo) - bo chcę się przebranżowić (chociaż jak na IT stary już jestem).
Jeśli ktoś mógłby się pochylić nad tym i ocenić mój kod - rozwiązania, styl, zależności, spójność, architektura, organizacja kodu, SOLID, nazewnictwo, wyjątki, czy mam już szerokie pojęcie o Javie, czy jeszcze nie - to był bym niezmiernie wdzięczny. (rozumiem że od procesorów adnotacji prawdopodobnie nie jest tu za wiele specjalistów - dlatego też nie proszę o to żeby ktoś siedział i poświęcał wieczór na rozkminianie przepływu sterowania i tego czy w ogóle dobrze rozpoznałem javax annotation processor)