Существует ли общий метод 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) ниже. Это решение в основном использует:

  1. Java Reflection API
  2. Независимая обработка Java Collections
  3. Рекурсия

Для начала я использовал Introspector пройти через readMethods из Class опуская методы, определенные для Object

for (PropertyDescriptor propertyDescriptor : Introspector
                    .getBeanInfo(c, Object.class).getPropertyDescriptors()) {
            Method method = propertyDescriptor.getReadMethod();

случаи

  1. Если текущий уровень Property имеет тип String
  2. Если это Object Массив свойств
  3. Если это String массив
  4. Если это тип Java Collection учебный класс
  5. Раздельное размещение для 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;

    }

Тестовое задание

У меня также есть тестовый класс, который создает относительно сложный объект. Тестовый класс имеет различные сценарии, которые охватывают:

  1. String свойства
  2. Свойства как пользовательские типы данных, которые в свою очередь имеют String свойства
  3. Свойства как пользовательские типы данных, которые, в свою очередь, имеют свойства как пользовательские типы данных, которые, в свою очередь, имеют String свойства
  4. List пользовательских типов данных
  5. Set из Strings
  6. Array пользовательских типов данных
  7. Array из Strings
  8. 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, Есть некоторые улучшения в коде, который я хочу сделать, но для вашего случая решение должно работать просто отлично.

Ограничения (дальнейшие улучшения)

  1. Невозможно обработать недисциплинированный синтаксис свойств (недопустимые методы получения / установки)
  2. Не может обрабатывать цепочки коллекций: например, List<List<Person>> - из-за исключительной поддержки дисциплинированной конвенции получателей / установщиков
  3. нет 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();
                    }
                }
}

Я еще не сталкивался с какими-либо проблемами с этим. Я знаю, что это старая тема, но она может помочь кому-то, кто ищет что-то простое.

Другие вопросы по тегам