In here im logging the changes that has been done to a particular Object record. So im comparing the old record and the updated record to log the updated fields as a String. Any idea how can I do this?
Well i found a solution as below :
private static List<String> getDifference(Object s1, Object s2) throws IllegalAccessException {
List<String> values = new ArrayList<>();
for (Field field : s1.getClass().getDeclaredFields()) {
field.setAccessible(true);
Object value1 = field.get(s1);
Object value2 = field.get(s2);
if (value1 != null && value2 != null) {
if (!Objects.equals(value1, value2)) {
values.add(String.valueOf(field.getName()+": "+value1+" -> "+value2));
}
}
}
return values;
}
You may use javers library for this.
<groupId>org.javers</groupId>
<artifactId>javers-core</artifactId>
POJO:
public class Person {
private Integer id;
private String name;
// standard getters/constructors
}
Usage:
#Test
public void givenPersonObject_whenApplyModificationOnIt_thenShouldDetectChange() {
// given
Javers javers = JaversBuilder.javers().build();
Person person = new Person(1, "Michael Program");
Person personAfterModification = new Person(1, "Michael Java");
// when
Diff diff = javers.compare(person, personAfterModification);
// then
ValueChange change = diff.getChangesByType(ValueChange.class).get(0);
assertThat(diff.getChanges()).hasSize(1);
assertThat(change.getPropertyName()).isEqualTo("name");
assertThat(change.getLeft()).isEqualTo("Michael Program");
assertThat(change.getRight()).isEqualTo("Michael Java");
}
Plus other use cases are supported as well.
maybe this method can help you to solve you problem
/**
* get o1 and o2 different value of field name
* #param o1 source
* #param o2 target
* #return
* #throws IllegalAccessException
*/
public static List<String> getDiffName(Object o1,Object o2) throws IllegalAccessException {
//require o1 and o2 is not null
if (o1==null&&o2==null){
return Collections.emptyList();
}
//if only one has null
if (o1 == null){
return getAllFiledName(o2);
}
if (o2 == null){
return getAllFiledName(o1);
}
//source field
Field[] fields=o1.getClass().getDeclaredFields();
List<String> fieldList=new ArrayList<>(fields.length);
//if class is same using this to call
if (o1.getClass().equals(o2.getClass())){
//loop field to equals the field
for (Field field : fields) {
//to set the field access
field.setAccessible(true);
Object source = field.get(o1);
Object target = field.get(o2);
//using jdk8 equals to compare two objects
if (!Objects.equals(source, target)){
fieldList.add(field.getName());
}
}
}else {
//maybe o1 class is not same as o2 class
Field[] targetFields=o2.getClass().getDeclaredFields();
List<String> sameFieldNameList=new ArrayList<>();
//loop o1 field
for (Field field : fields) {
String name = field.getName();
//loop target field to get same field
for (Field targetField : targetFields) {
//if name is equal to compare
if (targetField.getName().equals(name)){
//add same field to list
sameFieldNameList.add(name);
//set access
field.setAccessible(true);
Object source = field.get(o1);
//set target access
targetField.setAccessible(true);
Object target = targetField.get(o2);
//equals
if (!Objects.equals(source, target)){
fieldList.add(field.getName());
}
}
}
}
//after loop add different source
for (Field targetField : targetFields) {
//add not same field
if (!sameFieldNameList.contains(targetField.getName())){
fieldList.add(targetField.getName());
}
}
}
return fieldList;
}
/**
* getAllFiledName
* #param obj
* #return
*/
private static List<String> getAllFiledName(Object obj) {
Field[] declaredFields = obj.getClass().getDeclaredFields();
List<String> list=new ArrayList<>(declaredFields.length);
for (Field field : declaredFields) {
list.add(field.getName());
}
return list;
}
this method can compare two object which have the same name field ,if they don't have same field,return all field Name
Kotlin generic function with reflection
It's not a perfect answer to this question, but maybe someone could use it.
data class Difference(val old: Any?, val new: Any?)
fun findDifferencesInObjects(
old: Any,
new: Any,
propertyPath: String? = null
): MutableMap<String, Difference> {
val differences = mutableMapOf<String, Difference>()
if (old::class != new::class) return differences
#Suppress("UNCHECKED_CAST")
old::class.memberProperties.map { property -> property as KProperty1<Any, *>
val newPropertyPath = propertyPath?.plus('.')?.plus(property.name) ?: property.name
property.isAccessible = true
val oldValue = property.get(old)
val newValue = property.get(new)
if (oldValue == null && newValue == null) return#map
if (oldValue == null || newValue == null) {
differences[newPropertyPath] = Difference(oldValue, newValue)
return#map
}
if (oldValue::class.isData || newValue::class.isData) {
differences.putAll(findDifferencesInObject(oldValue, newValue, newPropertyPath))
} else if (!Objects.equals(oldValue, newValue)) {
differences[newPropertyPath] = Difference(oldValue, newValue)
}
}
return differences
}
Result
{
"some.nested.and.changed.field": {
"old": "this value is old",
"new": "this value is new"
},
...
}
Suppose you have this protobuf model:
message ComplexKey {
string name = 1;
int32 domainId = 2;
}
message KeyMsg {
oneof KeyMsgOneOf {
string name = 1;
ComplexKey complexName= 2;
}
}
and an object obj, that you know is either a string or a ComplexKey.
Question
Whitout explicitly checking the obj class type, which is the most efficient way to build a new KeyMsg instance with the obj placed in the correct field using the protobuf Java API?
UPDATE: it would be great if protoc generates an helper method to do what I need.
UPDATE2: given the correct comment from Mark G. below and supposing that all fields differ in type, the best solution I've find so far is (simplified version):
List<FieldDescriptor> lfd = oneOfFieldDescriptor.getFields();
for (FieldDescriptor fieldDescriptor : lfd) {
if (fieldDescriptor.getDefaultValue().getClass() == oVal.getClass()) {
vmVal = ValueMsg.newBuilder().setField(fieldDescriptor, oVal).build();
break;
}
}
You can use switch-case:
public Object demo() {
KeyMsg keyMsg = KeyMsg.newBuilder().build();
final KeyMsg.KeyMsgOneOfCase oneOfCase = keyMsg.getKeyMsgOneOfCase();
switch (oneOfCase) {
case NAME: return keyMsg.getName();
case COMPLEX_NAME: return keyMsg.getComplexName();
case KEY_MSG_ONE_OF_NOT_SET: return null;
}
}
I doubt there is a better way than using instanceof:
KeyMsg.Builder builder = KeyMsg.newBuilder();
if (obj instanceof String) {
builder.setName((String) obj);
} else if (obj instanceof ComplexKey) {
builder.setComplexName((ComplexKey) obj);
} else {
throw new AssertionError("Not a String or ComplexKey");
}
KeyMsg msg = builder.build();
I'm working on a procedure that should find the lowest-weighted path between two nodes using Dijkstra's algorithm. The procedure should only return paths whose all nodes match specific criteria (i.e. all nodes should have properties with specific values). If at least one node in a path doesn't match the criteria, then the path becomes invalid, and the algorithm should look for the next lowest-weighted path.
In order to achieve this, I'm using a PathExpanderBuilder with node filters, but they don't seem to filter anything.
Here is my code:
public class Demo {
#Procedure
#Description("apoc.algo.dijkstraWithFilters(startNode, endNode, " +
"'distance', 10, 'prop1', 2, 'prop2', [100, 200], 'prop3') " +
" YIELD path, weight - run dijkstra with relationship property name as cost function" +
" and a default weight if the property does not exist")
public Stream<WeightedPathResult> dijkstraWithFilters(
#Name("startNode") Node startNode,
#Name("endNode") Node endNode,
#Name("weightPropertyName") String weightPropertyName,
#Name("defaultWeight") double defaultWeight,
#Name("longPropName") String longPropName,
#Name("longPropValue") long longPropValue,
#Name("listPropName") String listPropName,
#Name("listPropValues") List<Long> listPropValues,
#Name("boolPropName") String boolPropName) {
PathFinder<WeightedPath> algo = GraphAlgoFactory.dijkstra(
buildPathExpanderByPermissions(longPropName, longPropValue, listPropName, listPropValues, boolPropName),
(relationship, direction) -> convertToDouble(relationship.getProperty(weightPropertyName, defaultWeight))
);
return WeightedPathResult.streamWeightedPathResult(startNode, endNode, algo);
}
private double convertToDouble(Object property) {
if (property instanceof Double)
return (double) property;
else if (property instanceof Long)
return ((Long) property).doubleValue();
else if (property instanceof Integer)
return ((Integer) property).doubleValue();
return 1;
}
private PathExpander<Object> buildPathExpanderByPermissions(
String longPropName,
long longPropValue,
String listPropName,
List<Long> listPropValue,
String boolPropName
) {
PathExpanderBuilder builder = PathExpanderBuilder.allTypesAndDirections();
builder.addNodeFilter(
node -> !node.hasProperty(longPropName) ||
node.getProperty(longPropName) instanceof Long &&
(long) node.getProperty(longPropName) < longPropValue
);
builder.addNodeFilter(
node -> {
try {
return !node.hasProperty(listPropName) ||
(boolean) node.getProperty(boolPropName, false) ||
!Collections.disjoint((List<Long>) node.getProperty(listPropName), listPropValue);
}
catch (Exception e){
return false;
}
}
);
return builder.build();
}
}
What am I missing here? Am I making a wrong use of PathExpanderBuilder?
PathExpanderBuilder's are immutable and so calling e.g. addNodeFilter returns a new PathExpanderBuilder with the added filter and so you need to re-assign your builder with that returned instance.
I'm hoping to trim all Strings that are part of an object graph.
So I have an object graph like so
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)
There is a get and set pair for every property of every class represented in this object graph. Ideally every field of type String would end up trimmed, including enumerating any child objects contained in collections. There are no cycles contained within the object graph.
Is there any java library that implements some sort of generic object graph visitor pattern or String\Reflection utility library that does this?
An external third party library that does this would also be fine, it does not have to be part of the standard java libraries.
No, there's no built-in traversal for something like this, and remember that Java Strings are immutable, so you can't actually trim in place--you have to trim and replace. Some objects may not permit modification of their String variables.
Below is the explanation of solution that I have built using Java Reflection API. I have posted the working code (with its url to github) below. This solution mainly uses:
Java Reflection API
Independent handling of Java Collections
Recursion
To start with, I have used Introspector to go over the readMethods of the Class omitting the methods defined for Object
for (PropertyDescriptor propertyDescriptor : Introspector
.getBeanInfo(c, Object.class).getPropertyDescriptors()) {
Method method = propertyDescriptor.getReadMethod();
Cases
If the current level of Property is of type String
If its an Object Array of Properties
If its a String array
If its a type of Java Collection class
Separate placement for Map with special conditions to process its keys and values
This utility uses the Java Reflection API to traverse through an object graph with disciplined syntax of getters and setters and trims all strings encountered within an Object graph recursively.
Code
This entire util class with the main test class (and custom data types/pojos) is here on my github
Usage:
myObj = (MyObject) SpaceUtil.trimReflective(myObj);
Util method:
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;
}
Test
I also have a test class in there which creates a relatively complex object. The test class has different scenarios that cover:
String properties
Properties as custom datatypes which in turn have String properties
Properties as custom datatypes which in turn have properties as custom datatypes which in turn have String properties
List of custom data types
Set of Strings
Array of custom data types
Array of Strings
Map of String and custom data type
Object Graph:
Test Object Code Snippet:
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;
}
Outcome:
#######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
There is a non-zero count of the number of spaces in the above trimmed output because I didn't make an effort to override toString of any collections (List, Set) or Map. There are certain improvements to the code I want to make but for your case the solution should work just fine.
Limitations (further improvements)
Cannot handle undisciplined syntax of properties (invalid getters/setters)
Cannot handle chained Collections: for example, List<List<Person>> - because of the exclusive support to disciplined getters/setters convention
No Guava collection library support
Building off #SwissArmyKnife I converted his simple String trimming function into an interface with a default method. So any object where you would like to use object.trim(), you just have to add "implements Trimmable".
Simple String trim interface: 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();
}
}
}
}
An object that we would like to be trimmable: Person.class (implements Trimmable interface)
public class Person implements Trimmable {
private String firstName;
private String lastName;
private int age;
// getters/setters omitted
}
Now you can use person.trim()
Person person = new Person();
person.setFirstName(" John ");
person.setLastName(" Doe");
person.setAge(30);
person.trim();
I made a simple method for trimming String values with 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();
}
}
}
I haven't bumped in to any problems with this one yet. I know its an old thread, but it might help somone whos is looking for something simple.
Assume there's an XMLBeans XmlObject with attributes, how can I get selected attributes in single step?
I'm expecting like something ....
removeAttributes(XmlObject obj, String[] selectableAttributes){};
Now the above method should return me the XMLObject with only those attributes.
Assumption: the attributes that you want to remove from your XmlObject must be optional in the corresponding XML Schema. Under this assumption, XMLBeans provides you with a couple of useful methods: unsetX and isSetX (where X is your attribute name. So, we can implement a removeAttributes method in this way:
public void removeAttributes(XmlObject obj,
String[] removeAttributeNames)
throws IllegalArgumentException, IllegalAccessException,
InvocationTargetException, SecurityException,
NoSuchMethodException {
Class<?> clazz = obj.getClass();
for (int i = 0; i < removeAttributeNames.length; i++) {
String attrName =
removeAttributeNames[i].substring(0, 1).toUpperCase() +
removeAttributeNames[i].substring(1);
String isSetMethodName = "isSet" + attrName;
Boolean isSet = null;
try {
Method isSetMethod = clazz.getMethod(isSetMethodName);
isSet = (Boolean) isSetMethod.invoke(obj, new Object[] {});
} catch (NoSuchMethodException e) {
System.out.println("attribute " + removeAttributeNames[i]
+ " is not optional");
}
if (isSet != null && isSet.booleanValue() == true) {
String unsetMethodName = "unset" + attrName;
Method unsetMethod = clazz.getMethod(unsetMethodName);
unsetMethod.invoke(obj, new Object[] {});
}
}
}
Note 1: I have slightly modified the semantics of your method signature: the second argument (the String[]) is actually the list of attributes that you want to remove. I think this is more consistent with the method name (removeAttributes), and it also simplify things (using unsetX and isSetX).
Note 2: The reason for calling isSetX before calling unsetX is that unsetX would throw an InvocationTargetException if called when the attribute X is not set.
Note 3: You may want to change exception handling according to your needs.
I think you can use a cursor ... they are cumbersome to handle, but so is reflection.
public static XmlObject RemoveAllAttributes(XmlObject xo) {
return RemoveAllofType(xo, TokenType.ATTR);
}
public static XmlObject RemoveAllofTypes(XmlObject xo, final TokenType... tts) {
printTokens(xo);
final XmlCursor xc = xo.newCursor();
while (TokenType.STARTDOC == xc.currentTokenType() || TokenType.START == xc.currentTokenType()) {
xc.toNextToken();
}
while (TokenType.ENDDOC != xc.currentTokenType() && TokenType.STARTDOC != xc.prevTokenType()) {
if (ArrayUtils.contains(tts, xc.currentTokenType())) {
xc.removeXml();
continue;
}
xc.toNextToken();
}
xc.dispose();
return xo;
}
I am using this simple method to clean everything in the element. You can omit the cursor.removeXmlContents to only delete attributes. Second cursor is used to return to the initial position:
public static void clearElement(final XmlObject object)
{
final XmlCursor cursor = object.newCursor();
cursor.removeXmlContents();
final XmlCursor start = object.newCursor();
while (cursor.toFirstAttribute())
{
cursor.removeXml();
cursor.toCursor(start);
}
start.dispose();
cursor.dispose();
}