Существует ли общий метод Java для обрезки каждой строки в графе объектов?
Я надеюсь обрезать все строки, которые являются частью графа объекта.
Итак, у меня есть граф объектов, как так
RootElement
- name (String)
- adjective (String)
- items ArrayOfItems
- getItems (List<Item>)
- get(i) (Item)
Item
- name (String)
- value (double)
- alias (String)
- references ArrayOfReferences
- getReferences (List<Reference>)
- get(i) (Reference)
Reference
- prop1 (String)
- prop2 (Integer)
- prop3 (String)
Существует пара get и set для каждого свойства каждого класса, представленного в этом графе объектов. В идеале каждое поле типа String должно быть обрезано, включая перечисление любых дочерних объектов, содержащихся в коллекциях. В графе объектов нет циклов.
Есть ли какая-либо библиотека Java, которая реализует какой-то общий шаблон посетителя графа объекта или библиотеку утилит String\Reflection, которая делает это?
Внешняя сторонняя библиотека, которая делает это, тоже подойдет, она не обязательно должна быть частью стандартных библиотек Java.
4 ответа
Ниже приведено объяснение решения, которое я построил с использованием Java Reflection API. Я разместил рабочий код (с его адресом github) ниже. Это решение в основном использует:
Java Reflection API
- Независимая обработка
Java Collections
- Рекурсия
Для начала я использовал Introspector
пройти через readMethods
из Class
опуская методы, определенные для Object
for (PropertyDescriptor propertyDescriptor : Introspector
.getBeanInfo(c, Object.class).getPropertyDescriptors()) {
Method method = propertyDescriptor.getReadMethod();
случаи
- Если текущий уровень
Property
имеет типString
- Если это
Object
Массив свойств - Если это
String
массив - Если это тип Java
Collection
учебный класс - Раздельное размещение для
Map
с особыми условиями для обработки егоkeys
а такжеvalues
Эта утилита использует Java Reflection API
пройти через граф объектов с дисциплинированным синтаксисом getters
а также setters
и обрезает все Strings
встречаются в Object
граф рекурсивно.
Код
Весь этот класс утилит с основным тестовым классом (и пользовательскими типами данных /pojos) находится здесь на моем github
Использование:
myObj = (MyObject) SpaceUtil.trimReflective(myObj);
Использовать метод:
public static Object trimReflective(Object object) throws Exception {
if (object == null)
return null;
Class<? extends Object> c = object.getClass();
try {
// Introspector usage to pick the getters conveniently thereby
// excluding the Object getters
for (PropertyDescriptor propertyDescriptor : Introspector
.getBeanInfo(c, Object.class).getPropertyDescriptors()) {
Method method = propertyDescriptor.getReadMethod();
String name = method.getName();
// If the current level of Property is of type String
if (method.getReturnType().equals(String.class)) {
String property = (String) method.invoke(object);
if (property != null) {
Method setter = c.getMethod("set" + name.substring(3),
new Class<?>[] { String.class });
if (setter != null)
// Setter to trim and set the trimmed String value
setter.invoke(object, property.trim());
}
}
// If an Object Array of Properties - added additional check to
// avoid getBytes returning a byte[] and process
if (method.getReturnType().isArray()
&& !method.getReturnType().isPrimitive()
&& !method.getReturnType().equals(String[].class)
&& !method.getReturnType().equals(byte[].class)) {
System.out.println(method.getReturnType());
// Type check for primitive arrays (would fail typecasting
// in case of int[], char[] etc)
if (method.invoke(object) instanceof Object[]) {
Object[] objectArray = (Object[]) method.invoke(object);
if (objectArray != null) {
for (Object obj : (Object[]) objectArray) {
// Recursively revisit with the current property
trimReflective(obj);
}
}
}
}
// If a String array
if (method.getReturnType().equals(String[].class)) {
String[] propertyArray = (String[]) method.invoke(object);
if (propertyArray != null) {
Method setter = c.getMethod("set" + name.substring(3),
new Class<?>[] { String[].class });
if (setter != null) {
String[] modifiedArray = new String[propertyArray.length];
for (int i = 0; i < propertyArray.length; i++)
if (propertyArray[i] != null)
modifiedArray[i] = propertyArray[i].trim();
// Explicit wrapping
setter.invoke(object,
new Object[] { modifiedArray });
}
}
}
// Collections start
if (Collection.class.isAssignableFrom(method.getReturnType())) {
Collection collectionProperty = (Collection) method
.invoke(object);
if (collectionProperty != null) {
for (int index = 0; index < collectionProperty.size(); index++) {
if (collectionProperty.toArray()[index] instanceof String) {
String element = (String) collectionProperty
.toArray()[index];
if (element != null) {
// Check if List was created with
// Arrays.asList (non-resizable Array)
if (collectionProperty instanceof List) {
((List) collectionProperty).set(index,
element.trim());
} else {
collectionProperty.remove(element);
collectionProperty.add(element.trim());
}
}
} else {
// Recursively revisit with the current property
trimReflective(collectionProperty.toArray()[index]);
}
}
}
}
// Separate placement for Map with special conditions to process
// keys and values
if (method.getReturnType().equals(Map.class)) {
Map mapProperty = (Map) method.invoke(object);
if (mapProperty != null) {
// Keys
for (int index = 0; index < mapProperty.keySet().size(); index++) {
if (mapProperty.keySet().toArray()[index] instanceof String) {
String element = (String) mapProperty.keySet()
.toArray()[index];
if (element != null) {
mapProperty.put(element.trim(),
mapProperty.get(element));
mapProperty.remove(element);
}
} else {
// Recursively revisit with the current property
trimReflective(mapProperty.get(index));
}
}
// Values
for (Map.Entry entry : (Set<Map.Entry>) mapProperty
.entrySet()) {
if (entry.getValue() instanceof String) {
String element = (String) entry.getValue();
if (element != null) {
entry.setValue(element.trim());
}
} else {
// Recursively revisit with the current property
trimReflective(entry.getValue());
}
}
}
} else {// Catch a custom data type as property and send through
// recursion
Object property = (Object) method.invoke(object);
if (property != null) {
trimReflective(property);
}
}
}
} catch (Exception e) {
throw new Exception("Strings cannot be trimmed because: ", e);
}
return object;
}
Тестовое задание
У меня также есть тестовый класс, который создает относительно сложный объект. Тестовый класс имеет различные сценарии, которые охватывают:
String
свойства- Свойства как пользовательские типы данных, которые в свою очередь имеют
String
свойства - Свойства как пользовательские типы данных, которые, в свою очередь, имеют свойства как пользовательские типы данных, которые, в свою очередь, имеют
String
свойства List
пользовательских типов данныхSet
изStrings
Array
пользовательских типов данныхArray
изStrings
Map
изString
и пользовательский тип данных
Граф объектов:
Фрагмент кода тестового объекта:
public static Music buildObj() {
Song song1 = new Song();
Song song2 = new Song();
Song song3 = new Song();
Artist artist1 = new Artist();
Artist artist2 = new Artist();
song1.setGenre("ROCK ");
song1.setSonnet("X ");
song1.setNotes("Y ");
song1.setCompostions(Arrays.asList(new String[] { "SOME X DATA ",
"SOME OTHER DATA X ", "SOME MORE DATA X ", " " }));
Set<String> instruments = new HashSet<String>();
instruments.add(" GUITAR ");
instruments.add(" SITAR ");
instruments.add(" DRUMS ");
instruments.add(" BASS ");
song1.setInstruments(instruments);
song2.setGenre("METAL ");
song2.setSonnet("A ");
song2.setNotes("B ");
song2.setCompostions(Arrays.asList(new String[] { "SOME Y DATA ",
" SOME OTHER DATA Y ",
" SOME MORE DATA Y ", " " }));
song3.setGenre("POP ");
song3.setSonnet("DONT ");
song3.setNotes("KNOW ");
song3.setCompostions(Arrays.asList(new String[] { "SOME Z DATA ",
" SOME OTHER DATA Z ",
" SOME MORE DATA Z ", " " }));
artist1.setSongList(Arrays.asList(new Song[] { song1, song3 }));
artist2.setSongList(Arrays.asList(new Song[] { song1, song2, song3 }));
Map<String, Person> artistMap = new HashMap<String, Person>();
Person tutor1 = new Person();
tutor1.setName("JOHN JACKSON DOE ");
artistMap.put(" Name ", tutor1);
Person coach1 = new Person();
coach1.setName("CARTER ");
artistMap.put("Coach ", coach1);
artist2.setTutor(artistMap);
music.setSongs(Arrays.asList(new Song[] { song1, song2, song3 }));
music.setArtists(Arrays.asList(new Artist[] { artist1, artist2 }));
music.setLanguages(new String[] { " ENGLISH ", "FRENCH ",
"HINDI " });
Person singer1 = new Person();
singer1.setName("DAVID ");
Person singer2 = new Person();
singer2.setName("JACOB ");
music.setSingers(new Person[] { singer1, singer2 });
Human man = new Human();
Person p = new Person();
p.setName(" JACK'S RAGING BULL ");
SomeGuy m = new SomeGuy();
m.setPerson(p);
man.setMan(m);
music.setHuman(man);
return music;
}
Результат:
#######BEFORE#######
>>[>>DAVID ---<<, >>JACOB ---<<]---[ ENGLISH , FRENCH , HINDI ]---[>>ROCK ---X ---Y ---[SOME X DATA , SOME OTHER DATA X , SOME MORE DATA X , ]---[ SITAR , GUITAR , BASS , DRUMS ]<<, >>METAL ---A ---B ---[SOME Y DATA , SOME OTHER DATA Y , SOME MORE DATA Y , ]---<<, >>POP ---DONT ---KNOW ---[SOME Z DATA , SOME OTHER DATA Z , SOME MORE DATA Z , ]---<<]---[>>---[>>ROCK ---X ---Y ---[SOME X DATA , SOME OTHER DATA X , SOME MORE DATA X , ]---[ SITAR , GUITAR , BASS , DRUMS ]<<, >>POP ---DONT ---KNOW ---[SOME Z DATA , SOME OTHER DATA Z , SOME MORE DATA Z , ]---<<]<<, >>{Coach =>>CARTER ---<<, Name =>>JOHN JACKSON DOE ---<<}---[>>ROCK ---X ---Y ---[SOME X DATA , SOME OTHER DATA X , SOME MORE DATA X , ]---[ SITAR , GUITAR , BASS , DRUMS ]<<, >>METAL ---A ---B ---[SOME Y DATA , SOME OTHER DATA Y , SOME MORE DATA Y , ]---<<, >>POP ---DONT ---KNOW ---[SOME Z DATA , SOME OTHER DATA Z , SOME MORE DATA Z , ]---<<]<<]---=> JACK'S RAGING BULL <=<<
Number of spaces : 644
#######AFTER#######
>>[>>DAVID---<<, >>JACOB---<<]---[ENGLISH, FRENCH, HINDI]---[>>ROCK---X---Y---[SOME X DATA, SOME OTHER DATA X, SOME MORE DATA X, ]---[GUITAR, SITAR, DRUMS, BASS]<<, >>METAL---A---B---[SOME Y DATA, SOME OTHER DATA Y, SOME MORE DATA Y, ]---<<, >>POP---DONT---KNOW---[SOME Z DATA, SOME OTHER DATA Z, SOME MORE DATA Z, ]---<<]---[>>---[>>ROCK---X---Y---[SOME X DATA, SOME OTHER DATA X, SOME MORE DATA X, ]---[GUITAR, SITAR, DRUMS, BASS]<<, >>POP---DONT---KNOW---[SOME Z DATA, SOME OTHER DATA Z, SOME MORE DATA Z, ]---<<]<<, >>{Name=>>JOHN JACKSON DOE---<<, Coach=>>CARTER---<<}---[>>ROCK---X---Y---[SOME X DATA, SOME OTHER DATA X, SOME MORE DATA X, ]---[GUITAR, SITAR, DRUMS, BASS]<<, >>METAL---A---B---[SOME Y DATA, SOME OTHER DATA Y, SOME MORE DATA Y, ]---<<, >>POP---DONT---KNOW---[SOME Z DATA, SOME OTHER DATA Z, SOME MORE DATA Z, ]---<<]<<]---=>JACK'S RAGING BULL<=<<
Number of spaces : 111
Существует ненулевой счетчик количества пробелов в приведенном выше trimmed
вывод, потому что я не пытался переопределить toString
из любых коллекций (List
, Set
) или же Map
, Есть некоторые улучшения в коде, который я хочу сделать, но для вашего случая решение должно работать просто отлично.
Ограничения (дальнейшие улучшения)
- Невозможно обработать недисциплинированный синтаксис свойств (недопустимые методы получения / установки)
- Не может обрабатывать цепочки коллекций: например,
List<List<Person>>
- из-за исключительной поддержки дисциплинированной конвенции получателей / установщиков - нет
Guava
поддержка библиотеки коллекций
Нет, для чего-то подобного нет встроенного обхода, и помните, что Java String
Они являются неизменными, поэтому вы не можете на самом деле обрезать на месте - вы должны обрезать и заменить. Некоторые объекты могут не разрешать изменение их String
переменные.
Создавая @SwissArmyKnife, я преобразовал его простую функцию обрезки строк в интерфейс с методом по умолчанию. Таким образом, любой объект, где вы хотели бы использовать object.trim(), вам просто нужно добавить "Implements Trimmable".
Простой интерфейс обрезки строк: Trimmable.class
/**
* Utility interface that trims all String fields of the implementing class.
*/
public interface Trimmable {
/**
* Trim all Strings
*/
default void trim(){
for (Field field : this.getClass().getDeclaredFields()) {
try {
field.setAccessible(true);
Object value = field.get(this);
if (value != null){
if (value instanceof String){
String trimmed = (String) value;
field.set(this, trimmed.trim());
}
}
} catch(Exception e) {
e.printStackTrace();
}
}
}
}
Объект, который мы хотели бы обрезать: Person.class (реализует интерфейс Trimmable)
public class Person implements Trimmable {
private String firstName;
private String lastName;
private int age;
// getters/setters omitted
}
Теперь вы можете использовать person.trim()
Person person = new Person();
person.setFirstName(" John ");
person.setLastName(" Doe");
person.setAge(30);
person.trim();
Я сделал простой метод обрезки строковых значений с помощью Reflection API.
public Object trimStringValues(Object model){
for(Field field : model.getClass().getDeclaredFields()){
try{
field.setAccessible(true);
Object value = field.get(model);
String fieldName = field.getName();
if(value != null){
if(value instanceof String){
String trimmed = (String) value;
field.set(model, trimmed.trim());
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
Я еще не сталкивался с какими-либо проблемами с этим. Я знаю, что это старая тема, но она может помочь кому-то, кто ищет что-то простое.