2017-09-16 5 views
0

오랫동안 죄송합니다. 미리 감사드립니다! JAXB를 사용하여 중첩 세트 비 정렬 화


내가이 XML 비 직렬화해야 내 JAXB 내부

<RootTag> 

    <Container type="type1"> 
    <value name="name1"/> 
    <value name="name2"/> 
    </Container> 

    <Container type="type2"> 
    <value name="name3"/> 
    <value name="name4"/> 
    </Container> 

</RootTag> 


요약

을 나는 내부 세트가 싸여 값 세트의 세트가 (코드는 아래와 같습니다) 컨테이너 클래스로 그래서 저는 값의 컨테이너 집합을 가지고 있는데, 여기서 값은 일반적인 클래스입니다. 문제점 : 선택한 제네릭 클래스가 하드 코드되지 않으면 값이 해석되지 않습니다. 는 XML에


자세한

주 :

  • <Container> 태그 <value> 태그 설정이 포함되어 있습니다. <value> 태그는 무엇이든 포함 할 수 있습니다. (우리는 하나 "name"속성이있는 ElementName을 클래스를 가지고 위의 예에서)이 JAXB에서 컨테이너 클래스는설정 <T>를 사용합니다.

  • 만 정확히 두 <Container> 태그있을 가정된다 (그 속성이 두 항목의 열거이지만,이 예제에서 그것을 간단하게하기 위해 단지 문자열입니다). 추가 질문 (선택 사항) : 태그 양을 정확히 2 개로 제한 할 수있는 방법이 있습니까? 이 모든 포장

, 우리는 Set<Container<ElementName>>이있는 루트 클래스가 있습니다. 문제는 가장 깊은 ElementName (<value> 태그를 나타냄) 클래스가 JAXB에 의해 해결되지 않는다는 것입니다 (너무 많은 수준의 간접 참조?). 그러나 JAXB는 모든 정보를 내부 형식 인 ElementNSImpl으로 읽습니다. 태그의 값 (즉, name1, name2, name3name4)이있는 곳에서 afterUnmarshall 메서드 내에 중단 점을 배치하여 계산했습니다. 비 정렬 화는 오류없이 완료,하지만 난 어떤 <value> 태그에 액세스하려고 할 때이 얻을 :

컨테이너 클래스 내부
java.lang.ClassCastException: com.sun.org.apache.xerces.internal.dom.ElementNSImpl cannot be cast to ElementName 

, 내가

@XmlElement(name = "value") 

내가 만약 사용하여 <value>의 태그 이름을 지정 다음과 같이 유형을 하드 코드하면됩니다.

@XmlElement(name = "value", type = ElementName.class) 

그러면 JAXB가 태그를 올바르게 해석합니다. 그러나 제가 말했듯이, 수업은 일반적인 수업으로되어있어서 정말 저를위한 해결책은 아닙니다.


JAXB 클래스

루트

import javax.xml.bind.Unmarshaller; 
import javax.xml.bind.annotation.*; 
import java.util.HashMap; 
import java.util.Map; 
import java.util.Set; 
import java.util.stream.Collectors; 

/* 
* <RootTag> 
*  * 
* </RootTag> 
*/ 
@XmlRootElement(name = "RootTag") 
public class Root { 

    /* <Container type = "type1"> 
    * <value name = "name1"/> 
    * <value name = "name2"/> 
    * </Container> 
    * <Container type = "type2"> 
    * <value name = "name3"/> 
    * <value name = "name4"/> 
    * </Container> 
    */ 
    @XmlElement(name = "Container") 
    private Set<Container<ElementName>> containers; 

    // for each of the two Container types 
    // we create a set of its corresponding values' names 
    // and then we null the containers field 
    @XmlTransient 
    private Map<String, Set<String>> valuesNames = new HashMap<>(2); 

    //Basically that is @PostConstruct, but works with JAXB 
    void afterUnmarshal(Unmarshaller u, Object parent) { 

     containers.forEach(container -> valuesNames.put(container.getType(), 
                 container.getValues().stream() 
                   .map(ElementName::getName) 
                   .collect(Collectors.toSet()))); 
     containers = null; 
    } 

    /** return unique value names from both container types */ 
    public Set<String> getAllValuesNames() { 

     return valuesNames.values().stream() 
          .flatMap(Set::stream) 
          .collect(Collectors.toSet()); 
    } 
} 

컨테이너

import javax.xml.bind.Unmarshaller; 
import javax.xml.bind.annotation.*; 
import java.util.Set; 

/* 
* <* type = "type1"> 
* <value *>* 
* <value *>* 
* </*> 
*/ 
@XmlRootElement 
@XmlAccessorType(XmlAccessType.FIELD) 
public class Container<T> { 

    @XmlAttribute 
    private String type; 

    @XmlElement(name = "value") 
    private Set<T> values; 

    public String getType() { 

     return type; 
    } 

    public Set<T> getValues() { 

     return values; 
    } 
} 

ElementName을

import javax.xml.bind.Unmarshaller; 
import javax.xml.bind.annotation.XmlAccessType; 
import javax.xml.bind.annotation.XmlAccessorType; 
import javax.xml.bind.annotation.XmlAttribute; 

/* 
* <* name = "name2"/> 
*/ 
@XmlAccessorType(XmlAccessType.FIELD) 
public class ElementName { 

    @XmlAttribute 
    private String name; 

    public String getName() { 

     return name; 
    } 
} 


홈페이지

public static void main(String[] args) throws JAXBException { 

    final String xmlSerialized = "<RootTag>\n" + 
           " \n" + 
           " <Container type=\"type1\">\n" + 
           " <value name=\"name1\"/>\n" + 
           " <value name=\"name2\"/>\n" + 
           " </Container>\n" + 
           " \n" + 
           " <Container type=\"type2\">\n" + 
           " <value name=\"name3\"/>\n" + 
           " <value name=\"name4\"/>\n" + 
           " </Container>\n" + 
           " \n" + 
           "</RootTag>"; 

    System.out.println("XML:\n" + 
         "=======================================\n" + 
         xmlSerialized + 
         "\n======================================="); 

    //works just the same with only Root.class 
    JAXBContext context  = JAXBContext.newInstance(Root.class, Container.class, ElementName.class); 
    Unmarshaller unmarshaller = context.createUnmarshaller(); 

    Root xmlDeserialized = (Root) unmarshaller.unmarshal(new StringReader(xmlSerialized)); 
    System.out.println("Values' names: " + xmlDeserialized.getAllMetricNames()); 
} 

경우 모든 작동합니다 (I가 컨테이너 클래스 내부의 주석에 ElementName.class를 하드 경우) 다음과 같은 출력을 제공합니다

XML: 
======================================= 
<RootTag> 
    <Body> 
    <Container type="type1"> 
     <value name="name1"/> 
     <value name="name2"/> 
    </Container> 
    <Container type="type2"> 
     <value name="name3"/> 
     <value name="name4"/> 
    </Container> 
    </Body> 
</RootTag> 
======================================= 
Values' names: [name4, name3, name2, name1] 


내가 시도한 것

Unmarshalling generic list with JAXB.

  1. 주요 아이디어는 대신 내부적으로 그 목록을 사용하는 Wrapper<T>을 사용하고 게터 통해 제공 List<T>의 사용이다. 내 컨테이너은 이미 속성을 가지고있는 것을 제외하고는 이미 래퍼입니다.

    하기 전에 :

    @XmlElement(name = "value") 
    private Set<T> values; 
    

    후 :

    @XmlAnyElement(lax = true) 
    private Set<T> values; 
    
  2. 가 실행 다시 (JAXBContext.newInstance(Root.class, Container.class, ElementName.class)가) : 아무것도 변경, 값은 여전히의없는 그러나 설정의 주석에서 차이가있다 ElementNSImpl을 입력하십시오. 또한 태그의 이름을 허용하는 것이 싫어서 '가치'여야합니다. 자신의 경우

    Intermittent ClassCastException from ElementNSImpl to own type during unmarshalling

는 문제는 그들이 필요한 클래스의 일부를 제공하지 않고 JAXBContext를 만든 것이 었습니다. 나에게 해당되지 않는다. 자신의 경우

JAXB Unmarshalling XML Class Cast Exception

문제는 다음 코드를 컴파일하고 JVM이 알고하지 않도록 다음 는 제네릭의 유형을 삭제하고 자바는 컴파일시에 일반적인 유형을 결정한다 자바 유형 말소했다 그 일반 유형은 무엇입니까? 나는 그것이 나의 경우라고 생각하지만, 그것을 확인하거나 고치는 방법에 대해서는 모른다.

답변

0

해결책을 찾았습니다! 나는 XmlAdapter 클래스의 사용법 인 Blaise Doughan에 의해 here으로 설명 된 방법을 사용했습니다. 나는 일련의 값들을 ElementNameString에 맞추는 문자열 세트로 바꾸었다. 컨테이너를 매핑합니다.

p.s. 나는 내 다른 대답처럼 ContainerOfElementName을 유지. 그것은 중복 된 코드이지만, JAXB 계층 구조를 만드는 더 좋은 방법이없는 것 같습니다.

p.p.s. 문제는 (IMHO) Java Type Erasure입니다. 컴파일 후 Set<Container<ElementName>>Set입니다. JAXB가 T1<T2>과 같은 두 번째 수준의 간접 참조를 수행 할 때 제네릭 문제를 해결하는 것을 멈추었지만 T1<T2<T3>>은 그렇지 않습니다.

0

현재 해결 방법은 - 다른 답변은 내가 제네릭 형식을 하드 코딩으로 약간에게 아이디어를 활용

매우 환영합니다 - 나는 두 개의 클래스로 컨테이너 클래스를 분할하고 <value> 클래스는 이제 내부 있습니다 (하지만

컨테이너

import javax.xml.bind.annotation.*; 
import java.util.Set; 

/* 
* <Container type = "type1"> 
* * 
* </Container> 
*/ 
@XmlRootElement 
@XmlAccessorType(XmlAccessType.FIELD) 
public abstract class Container { 

    @XmlAttribute 
    private String type; 

    public String getType() { 

     return type; 
    } 

    /** Derived classes will declare the generic type for us. */ 
    public abstract Set<?> getValues(); 
} 
: 나는 그들에게 서로를 상속 할 수 있도록 공개)를 왼쪽

ContainerOfElementName

import javax.xml.bind.annotation.XmlAccessType; 
import javax.xml.bind.annotation.XmlAccessorType; 
import javax.xml.bind.annotation.XmlAttribute; 
import javax.xml.bind.annotation.XmlElement; 
import java.util.Set; 
import java.util.stream.Collectors; 

/* 
* <Container type = "type1"> 
* <value name="name1"/> 
* <value name="name2"/> 
* </Container> 
*/ 
public class ContainerOfElementName extends Container { 

    @XmlElement(name = "value") 
    private Set<ElementName> values; 

    @Override 
    public Set<ElementName> getValues() { 

     return values; 
    } 

    public Set<String> getNames() { 
     return values.stream().map(v -> v.name).collect(Collectors.toSet()); 
    } 

    /* 
    * <* name = "name2"/> 
    */ 
    @XmlAccessorType(XmlAccessType.FIELD) 
    public static class ElementName { 

     @XmlAttribute 
     private String name; 
    } 
} 

그리고 이제 루트 클래스 :

작동하지만, 가능한 각 클래스에 대한 컨테이너의 하위 클래스를 만들 저를 강제로
@XmlElement(name = "Container") 
private Set<ContainerOfElementName> containers; 

void afterUnmarshal(Unmarshaller u, Object parent) { 

    containers.forEach(container -> valuesNames.put(container.getType(), 
                container.getNames())); 
    containers = null; 
} 

그 많은 상용구 코드 인 <value>을 나타냅니다.