Currently I'm using WildFly 21.0.2 and JSON-B and JSON-P APIs. The Yasson version in WildFly modules is 1.0.5. I have the following JSON coming from REST endpoint:
{
"circuitInfoResponseList": [
{
"org.my.company.dto.FiberCircuitInfoResponse": {
"attendanceType": "BY_RADIUS",
"index": 0,
...
This is my JsonbDeserializer implementation:
public CircuitInfoResponse deserialize(JsonParser jsonParser, DeserializationContext deserializationContext, Type type) {
jsonParser.next();
String className = jsonParser.getString();
jsonParser.next();
try {
return deserializationContext.deserialize(Class.forName(className).asSubclass(CircuitInfoResponse.class), jsonParser);
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new JsonbException("Cannot deserialize object.");
}
//return deserializationContext.deserialize(FiberCircuitInfoResponse.class, jsonParser);
}
This method gets the SECOND entry from the json attendanceType and NOT the desired org.my.company.dto.FiberCircuitInfoResponse. BTW... when I serialize the JSON Object I can see the string org.my.company.dto.FiberCircuitInfoResponse however when it arrives and the client side it does NOT contain that string. It comes likes this:
[
{
"circuitInfoResponseList": [
{
"attendanceType": "BY_RADIUS",
"index": 0,
Without that information I cannot tell which subclass to create. I've already tried to follow this tips but without success:
https://javaee.github.io/jsonb-spec/users-guide.html
https://github.com/m0mus/JavaOne2016-JSONB-Demo/blob/4ecc22f69d57fda765631237d897b0a487f58d90/src/main/java/com/oracle/jsonb/demo/serializer/AnimalDeserializer.java
https://javaee.github.io/javaee-spec/javadocs/javax/json/bind/serializer/JsonbDeserializer.html
These are my POJO classes.
Parent class:
import lombok.*;
import lombok.experimental.SuperBuilder;
import javax.json.bind.annotation.JsonbTypeDeserializer;
#Data
#SuperBuilder
#NoArgsConstructor(access = AccessLevel.PROTECTED)
#JsonbTypeDeserializer(CircuitInfoResponseJsonbXerializer.class)
public class CircuitInfoResponse {
...
}
Child class:
import lombok.AccessLevel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
#Data
#SuperBuilder
#EqualsAndHashCode(callSuper = false)
#NoArgsConstructor(access = AccessLevel.PROTECTED)
public class FiberCircuitInfoResponse extends CircuitInfoResponse {
...
}
Serialize code:
Type responseListType = new ArrayList<SimulationServiceResponse>() {}.getClass().getGenericSuperclass();
JsonbConfig config = new JsonbConfig()
.withSerializers(new CircuitInfoResponseJsonbXerializer());
Jsonb jsonb = JsonbBuilder.create(config);
String json = jsonb.toJson(response, responseListType);
System.out.println(json);
return Response.status(Response.Status.OK).entity(json).build();
Deserialize code:
String restJsonResponse = restResponse.readEntity(String.class);
JsonbConfig config = new JsonbConfig()
.withDeserializers(new CircuitInfoResponseJsonbXerializer());
Jsonb jsonbCustom = JsonbBuilder.create(config);
List<SimulationServiceResponse> restResponseEntity = jsonbCustom.fromJson(restJsonResponse, new ArrayList<SimulationServiceResponse>() {}.getClass().getGenericSuperclass());
This is the class that contains a list of Parent class above:
import lombok.AccessLevel;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
#Data
public class SimulationServiceResponse {
...
#Getter(AccessLevel.NONE)
#Setter(AccessLevel.NONE)
private List<CircuitInfoResponse> circuitInfoResponseList;
public List<CircuitInfoResponse> getCircuitInfoResponseList() {
if (circuitInfoResponseList == null) {
circuitInfoResponseList = new ArrayList<>();
}
return circuitInfoResponseList;
}
public void setCircuitInfoResponseList(List<CircuitInfoResponse> list) {
this.circuitInfoResponseList = list;
}
}
Do you guys have any idea of what I'm doing wrong?
Please forgive me if this has been asked but I have not found any matches yet.
I have some PDF files where images are duplicated on each page's resources but never used in its content stream. I think this is causing the PDFSplit command to create very bloated pages. Is there any utility code or examples to clean up unused resources like this? Maybe a starting point for me to get going?
I was able to clean up the resources for each page by gathering a list of the images used inside the page's content stream. With the list of images, I then check the resources for the page and remove any that weren't used. See the PageExtractor.stripUnusedImages below for implementation details.
The resource object was shared between pages so I also had to make sure each page had its own copy of the resource object before removing images. See PageExtractor.copyResources below for implementation details.
The page splitter:
package org.apache.pdfbox.examples;
import org.apache.pdfbox.contentstream.operator.Operator;
import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.interactive.action.PDAction;
import org.apache.pdfbox.pdmodel.interactive.action.PDActionGoTo;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDDestination;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageDestination;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class PageExtractor {
private final Logger log = LoggerFactory.getLogger(this.getClass());
public PDDocument extractPage(PDDocument source, Integer pageNumber) throws IOException {
PDDocument targetPdf = new PDDocument();
targetPdf.getDocument().setVersion(source.getVersion());
targetPdf.setDocumentInformation(source.getDocumentInformation());
targetPdf.getDocumentCatalog().setViewerPreferences(source.getDocumentCatalog().getViewerPreferences());
PDPage sourcePage = source.getPage(pageNumber);
PDPage targetPage = targetPdf.importPage(sourcePage);
targetPage.setResources(sourcePage.getResources());
stripUnusedImages(targetPage);
stripPageLinks(targetPage);
return targetPdf;
}
/**
* Collect the images used from a custom PDFStreamEngine (BI and DO operators)
* Create an empty COSDictionary
* Loop through the page's XObjects that are images and add them to the new COSDictionary if they were found in the PDFStreamEngine
* Assign the newly filled COSDictionary to the page's resource as COSName.XOBJECT
*/
protected void stripUnusedImages(PDPage page) throws IOException {
PDResources resources = copyResources(page);
COSDictionary pageObjects = (COSDictionary) resources.getCOSObject().getDictionaryObject(COSName.XOBJECT);
COSDictionary newObjects = new COSDictionary();
Set<String> imageNames = findImageNames(page);
Iterable<COSName> xObjectNames = resources.getXObjectNames();
for (COSName xObjectName : xObjectNames) {
if (resources.isImageXObject(xObjectName)) {
Boolean used = imageNames.contains(xObjectName.getName());
if (used) {
newObjects.setItem(xObjectName, pageObjects.getItem(xObjectName));
} else {
log.info("Found unused image: name={}", xObjectName.getName());
}
} else {
newObjects.setItem(xObjectName, pageObjects.getItem(xObjectName));
}
}
resources.getCOSObject().setItem(COSName.XOBJECT, newObjects);
page.setResources(resources);
}
/**
* It is necessary to copy the page's resources since it can be shared with other pages. We must ensure changes
* to the resources are scoped to the current page.
*/
protected PDResources copyResources(PDPage page) {
return new PDResources(new COSDictionary(page.getResources().getCOSObject()));
}
protected Set<String> findImageNames(PDPage page) throws IOException {
Set<String> imageNames = new HashSet<>();
PdfImageStreamEngine engine = new PdfImageStreamEngine() {
#Override
void handleImage(Operator operator, List<COSBase> operands) {
COSName name = (COSName) operands.get(0);
imageNames.add(name.getName());
}
};
engine.processPage(page);
return imageNames;
}
/**
* Borrowed from PDFBox page splitter
*
* #see org.apache.pdfbox.multipdf.Splitter#processAnnotations(org.apache.pdfbox.pdmodel.PDPage)
*/
protected void stripPageLinks(PDPage imported) throws IOException {
List<PDAnnotation> annotations = imported.getAnnotations();
for (PDAnnotation annotation : annotations) {
if (annotation instanceof PDAnnotationLink) {
PDAnnotationLink link = (PDAnnotationLink) annotation;
PDDestination destination = link.getDestination();
if (destination == null && link.getAction() != null) {
PDAction action = link.getAction();
if (action instanceof PDActionGoTo) {
destination = ((PDActionGoTo) action).getDestination();
}
}
if (destination instanceof PDPageDestination) {
// TODO preserve links to pages within the splitted result
((PDPageDestination) destination).setPage(null);
}
}
// TODO preserve links to pages within the splitted result
annotation.setPage(null);
}
}
}
The stream reader used to analyze the page's images:
package org.apache.pdfbox.examples;
import org.apache.pdfbox.contentstream.PDFStreamEngine;
import org.apache.pdfbox.contentstream.operator.Operator;
import org.apache.pdfbox.contentstream.operator.OperatorProcessor;
import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.graphics.PDXObject;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.pdmodel.graphics.form.PDTransparencyGroup;
import java.io.IOException;
import java.util.List;
abstract public class PdfImageStreamEngine extends PDFStreamEngine {
PdfImageStreamEngine() {
addOperator(new DrawObjectCounter());
}
abstract void handleImage(Operator operator, List<COSBase> operands);
protected class DrawObjectCounter extends OperatorProcessor {
#Override
public void process(Operator operator, List<COSBase> operands) throws IOException {
if (operands != null && isImage(operands.get(0))) {
handleImage(operator, operands);
}
}
protected Boolean isImage(COSBase base) throws IOException {
if (!(base instanceof COSName)) {
return false;
}
COSName name = (COSName)base;
if (context.getResources().isImageXObject(name)) {
return true;
}
PDXObject xObject = context.getResources().getXObject(name);
if (xObject instanceof PDTransparencyGroup) {
context.showTransparencyGroup((PDTransparencyGroup)xObject);
} else if (xObject instanceof PDFormXObject) {
context.showForm((PDFormXObject)xObject);
}
return false;
}
#Override
public String getName() {
return "Do";
}
}
}
We have an issue with one of our Kafka topics which is consumed by the DefaultKafkaConsumerFactory & ConcurrentMessageListenerContainer combination described here with a JsonDeserializer used by the Factory. Unfortunately someone got a little enthusiastic and published some invalid messages onto the topic. It appears that spring-kafka silently fails to process past the first of these messages. Is it possible to have spring-kafka log an error and continue? Looking at the error messages which are logged it seems that perhaps the Apache kafka-clients library should deal with the case that when iterating a batch of messages one or more of them may fail to parse?
The below code is an example test case illustrating this issue:
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.Serializer;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;
import org.junit.ClassRule;
import org.junit.Test;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.listener.KafkaMessageListenerContainer;
import org.springframework.kafka.listener.MessageListener;
import org.springframework.kafka.listener.config.ContainerProperties;
import org.springframework.kafka.support.SendResult;
import org.springframework.kafka.support.serializer.JsonDeserializer;
import org.springframework.kafka.support.serializer.JsonSerializer;
import org.springframework.kafka.test.rule.KafkaEmbedded;
import org.springframework.kafka.test.utils.ContainerTestUtils;
import org.springframework.util.concurrent.ListenableFuture;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.springframework.kafka.test.hamcrest.KafkaMatchers.hasKey;
import static org.springframework.kafka.test.hamcrest.KafkaMatchers.hasValue;
/**
* #author jfreedman
*/
public class TestSpringKafka {
private static final String TOPIC1 = "spring.kafka.1.t";
#ClassRule
public static KafkaEmbedded embeddedKafka = new KafkaEmbedded(1, true, 1, TOPIC1);
#Test
public void submitMessageThenGarbageThenAnotherMessage() throws Exception {
final BlockingQueue<ConsumerRecord<String, JsonObject>> records = createListener(TOPIC1);
final KafkaTemplate<String, JsonObject> objectTemplate = createPublisher("json", new JsonSerializer<JsonObject>());
sendAndVerifyMessage(records, objectTemplate, "foo", new JsonObject("foo"), 0L);
// push some garbage text to Kafka which cannot be marshalled, this should not interrupt processing
final KafkaTemplate<String, String> garbageTemplate = createPublisher("garbage", new StringSerializer());
final SendResult<String, String> garbageResult = garbageTemplate.send(TOPIC1, "bar","bar").get(5, TimeUnit.SECONDS);
assertEquals(1L, garbageResult.getRecordMetadata().offset());
sendAndVerifyMessage(records, objectTemplate, "baz", new JsonObject("baz"), 2L);
}
private <T> KafkaTemplate<String, T> createPublisher(final String label, final Serializer<T> serializer) {
final Map<String, Object> producerProps = new HashMap<>();
producerProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, embeddedKafka.getBrokersAsString());
producerProps.put(ProducerConfig.CLIENT_ID_CONFIG, "TestPublisher-" + label);
producerProps.put(ProducerConfig.ACKS_CONFIG, "all");
producerProps.put(ProducerConfig.RETRIES_CONFIG, 2);
producerProps.put(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION, 1);
producerProps.put(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG, 5000);
producerProps.put(ProducerConfig.MAX_BLOCK_MS_CONFIG, 5000);
producerProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
producerProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, serializer.getClass());
final DefaultKafkaProducerFactory<String, T> pf = new DefaultKafkaProducerFactory<>(producerProps);
pf.setValueSerializer(serializer);
return new KafkaTemplate<>(pf);
}
private BlockingQueue<ConsumerRecord<String, JsonObject>> createListener(final String topic) throws Exception {
final Map<String, Object> consumerProps = new HashMap<>();
consumerProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, embeddedKafka.getBrokersAsString());
consumerProps.put(ConsumerConfig.GROUP_ID_CONFIG, "TestConsumer");
consumerProps.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, true);
consumerProps.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "100");
consumerProps.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, 15000);
consumerProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
consumerProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class);
final DefaultKafkaConsumerFactory<String, JsonObject> cf = new DefaultKafkaConsumerFactory<>(consumerProps);
cf.setValueDeserializer(new JsonDeserializer<>(JsonObject.class));
final KafkaMessageListenerContainer<String, JsonObject> container = new KafkaMessageListenerContainer<>(cf, new ContainerProperties(topic));
final BlockingQueue<ConsumerRecord<String, JsonObject>> records = new LinkedBlockingQueue<>();
container.setupMessageListener((MessageListener<String, JsonObject>) records::add);
container.setBeanName("TestListener");
container.start();
ContainerTestUtils.waitForAssignment(container, embeddedKafka.getPartitionsPerTopic());
return records;
}
private void sendAndVerifyMessage(final BlockingQueue<ConsumerRecord<String, JsonObject>> records,
final KafkaTemplate<String, JsonObject> template,
final String key, final JsonObject value,
final long expectedOffset) throws InterruptedException, ExecutionException, TimeoutException {
final ListenableFuture<SendResult<String, JsonObject>> future = template.send(TOPIC1, key, value);
final ConsumerRecord<String, JsonObject> record = records.poll(5, TimeUnit.SECONDS);
assertThat(record, hasKey(key));
assertThat(record, hasValue(value));
assertEquals(expectedOffset, future.get(5, TimeUnit.SECONDS).getRecordMetadata().offset());
}
public static final class JsonObject {
private String value;
public JsonObject() {}
JsonObject(final String value) {
this.value = value;
}
public String getValue() {
return value;
}
public void setValue(final String value) {
this.value = value;
}
#Override
public boolean equals(final Object o) {
if (this == o) { return true; }
if (o == null || getClass() != o.getClass()) { return false; }
final JsonObject that = (JsonObject) o;
return Objects.equals(value, that.value);
}
#Override
public int hashCode() {
return Objects.hash(value);
}
#Override
public String toString() {
return "JsonObject{" +
"value='" + value + '\'' +
'}';
}
}
}
I have a solution but I don't know if it's the best one, I extended JsonDeserializer as follows which results in a null value being consumed by spring-kafka and requires the necessary downstream changes to handle that case.
class SafeJsonDeserializer[A >: Null](targetType: Class[A], objectMapper: ObjectMapper) extends JsonDeserializer[A](targetType, objectMapper) with Logging {
override def deserialize(topic: String, data: Array[Byte]): A = try {
super.deserialize(topic, data)
} catch {
case e: Exception =>
logger.error("Failed to deserialize data [%s] from topic [%s]".format(new String(data), topic), e)
null
}
}
Starting from the spring-kafka-2.x.x, we now have the comfort of declaring beans in the config file for the interface KafkaListenerErrorHandler with a implementation something as
#Bean
public ConsumerAwareListenerErrorHandler listen3ErrorHandler() {
return (m, e, c) -> {
this.listen3Exception = e;
MessageHeaders headers = m.getHeaders();
c.seek(new org.apache.kafka.common.TopicPartition(
headers.get(KafkaHeaders.RECEIVED_TOPIC, String.class),
headers.get(KafkaHeaders.RECEIVED_PARTITION_ID, Integer.class)),
headers.get(KafkaHeaders.OFFSET, Long.class));
return null;
};
}
more resources can be found at https://docs.spring.io/spring-kafka/reference/htmlsingle/#annotation-error-handling There is also another link with the similar issue: Spring Kafka error handling - v1.1.x and How to handle SerializationException after deserialization
Use ErrorHandlingDeserializer2. This is a delegating key/value deserializer that catches exceptions, returning them in the headers as serialized java objects.
Under consumer configuration, add/update the below lines:
import org.apache.kafka.clients.consumer.ConsumerConfig
import org.springframework.kafka.support.serializer.ErrorHandlingDeserializer2
configProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,
classOf[ErrorHandlingDeserializer2[JsonDeserializer]].getName)
configProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, classOf[ErrorHandlingDeserializer2[StringDeserializer]].getName)
configProps.put(ErrorHandlingDeserializer2.KEY_DESERIALIZER_CLASS, classOf[StringDeserializer].getName)
configProps.put(ErrorHandlingDeserializer2.VALUE_DESERIALIZER_CLASS, classOf[JsonDeserializer].getName)
I'd like to remove all the comments from a docx file using docx4j.
I can remove the actual comments with a piece of code like is shown below, but I think I also need to remove the comment references from the main document part as well (otherwise the document is corrupted), but I can't figure out how to do that.
CommentsPart cmtsPart = wordMLPackage.getMainDocumentPart().getCommentsPart();
org.docx4j.wml.Comments cmts = cpart.getJaxbElement();
List<Comments.Comment> coms = cmts.getComment();
coms.clear();
Any guidance appreciated!
I also posted this question on the docx4j forum: http://www.docx4java.org/forums/docx-java-f6/how-to-remove-all-comments-from-docx-file-t1329.html.
Thanks.
You can use TraversalUtil to find the relevant objects (CommentRangeStart, CommentRangeEnd, R.CommentReference), and then remove them.
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.JAXBElement;
import org.docx4j.TraversalUtil;
import org.docx4j.TraversalUtil.CallbackImpl;
import org.docx4j.XmlUtils;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.docx4j.wml.Body;
import org.docx4j.wml.CommentRangeEnd;
import org.docx4j.wml.CommentRangeStart;
import org.docx4j.wml.ContentAccessor;
import org.docx4j.wml.R.CommentReference;
import org.jvnet.jaxb2_commons.ppp.Child;
public class Foo {
public static void main(String[] args) throws Exception {
WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage
.load(new java.io.File(System.getProperty("user.dir") + "/Foo.docx"));
MainDocumentPart documentPart = wordMLPackage.getMainDocumentPart();
org.docx4j.wml.Document wmlDocumentEl = (org.docx4j.wml.Document) documentPart
.getJaxbElement();
Body body = wmlDocumentEl.getBody();
CommentFinder cf = new CommentFinder();
new TraversalUtil(body, cf);
for (Child commentElement : cf.commentElements) {
System.out.println(commentElement.getClass().getName());
Object parent = commentElement.getParent();
List<Object> theList = ((ContentAccessor)parent).getContent();
boolean removeResult = remove(theList, commentElement );
System.out.println(removeResult);
}
}
private static boolean remove(List<Object> theList, Object bm) {
// Can't just remove the object from the parent,
// since in the parent, it may be wrapped in a JAXBElement
for (Object ox : theList) {
if (XmlUtils.unwrap(ox).equals(bm)) {
return theList.remove(ox);
}
}
return false;
}
static class CommentFinder extends CallbackImpl {
List<Child> commentElements = new ArrayList<Child>();
#Override
public List<Object> apply(Object o) {
if (o instanceof javax.xml.bind.JAXBElement
&& (((JAXBElement)o).getName().getLocalPart().equals("commentReference")
|| ((JAXBElement)o).getName().getLocalPart().equals("commentRangeStart")
|| ((JAXBElement)o).getName().getLocalPart().equals("commentRangeEnd")
)) {
System.out.println(((JAXBElement)o).getName().getLocalPart());
commentElements.add( (Child)XmlUtils.unwrap(o) );
} else
if (o instanceof CommentReference ||
o instanceof CommentRangeStart ||
o instanceof CommentRangeEnd) {
System.out.println(o.getClass().getName());
commentElements.add((Child)o);
}
return null;
}
#Override // to setParent
public void walkJAXBElements(Object parent) {
List children = getChildren(parent);
if (children != null) {
for (Object o : children) {
if (o instanceof javax.xml.bind.JAXBElement
&& (((JAXBElement)o).getName().getLocalPart().equals("commentReference")
|| ((JAXBElement)o).getName().getLocalPart().equals("commentRangeStart")
|| ((JAXBElement)o).getName().getLocalPart().equals("commentRangeEnd")
)) {
((Child)((JAXBElement)o).getValue()).setParent(XmlUtils.unwrap(parent));
} else {
o = XmlUtils.unwrap(o);
if (o instanceof Child) {
((Child)o).setParent(XmlUtils.unwrap(parent));
}
}
this.apply(o);
if (this.shouldTraverse(o)) {
walkJAXBElements(o);
}
}
}
}
}
}
I would like to have a layout manager that can arrange two elements as follows:
one main element ABCDEF centered
one "postscript" element XYZ, positioned on the top right corner of the encapsulating figure
For example:
***********XYZ*
* ABCDEF *
***************
Can I use an existing layoutmanager for this? How do I build a custom layout manager that supports it?
Many thanks for your advice.
You can do that by using XYLayout.
There is a sample you can build on:
import org.eclipse.draw2d.AbstractLayout;
import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.FigureCanvas;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.Label;
import org.eclipse.draw2d.LayoutManager;
import org.eclipse.draw2d.LineBorder;
import org.eclipse.draw2d.Panel;
import org.eclipse.draw2d.XYLayout;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Insets;
import org.eclipse.draw2d.geometry.PrecisionRectangle;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
public class HHH {
public static void main(String[] args) {
Display d = new Display();
Shell s = new Shell();
s.setLayout(new FillLayout());
FigureCanvas canvas = new FigureCanvas(s);
Figure content = new Figure();
content.setLayoutManager(new XYLayout());
PostScriptedFigure figure = new PostScriptedFigure();
content.add(figure, new Rectangle(100, 100, -1, -1));
figure.setMainFigure(new Label("The Main figure"));
figure.setPostScriptFigure(new Label("ps"));
figure.setBorder(new LineBorder());
canvas.setContents(content);
s.setSize(600, 500);
s.open();
while (!s.isDisposed()) {
if (!d.readAndDispatch()) {
d.sleep();
}
}
}
}
class PostScriptedFigure extends Panel {
private IFigure mainFigure, postScriptFigure;
private class PostScriptedLayoutManager extends AbstractLayout {
#Override
public void layout(IFigure container) {
final Rectangle clientArea = container.getClientArea();
final IFigure mainFigure = getMainFigure();
final IFigure postScriptFigure = getPostScriptFigure();
if (mainFigure != null) {
final Rectangle bounds = new PrecisionRectangle();
bounds.setSize(mainFigure.getPreferredSize(SWT.DEFAULT, SWT.DEFAULT));
final Rectangle mainClientArea = clientArea.getCopy();
if (postScriptFigure != null) {
mainClientArea.shrink(new Insets(postScriptFigure.getPreferredSize().height(), 0, 0, 0));
}
bounds.translate(mainClientArea.getCenter().getTranslated(bounds.getSize().getScaled(0.5f).getNegated()));
mainFigure.setBounds(bounds);
}
if (postScriptFigure != null) {
final Rectangle bounds = new PrecisionRectangle();
bounds.setSize(postScriptFigure.getPreferredSize(SWT.DEFAULT, SWT.DEFAULT));
bounds.translate(clientArea.getTopRight().getTranslated(bounds.getSize().getNegated().width(), 0));
postScriptFigure.setBounds(bounds);
}
// note that other potentionally added figures are ignored
}
#Override
protected Dimension calculatePreferredSize(IFigure container, int wHint, int hHint) {
final Rectangle rect = new PrecisionRectangle();
final IFigure mainFigure = getMainFigure();
if (mainFigure != null) {
rect.setSize(mainFigure.getPreferredSize());
}
final IFigure postScriptFigure = getPostScriptFigure();
if (postScriptFigure != null) {
rect.resize(mainFigure != null ? 0 : postScriptFigure.getPreferredSize().width() , postScriptFigure.getPreferredSize().height());
}
// note that other potentionally added figures are ignored
final Dimension d = rect.getSize();
final Insets insets = container.getInsets();
return new Dimension(d.width + insets.getWidth(), d.height + insets.getHeight()).union(getBorderPreferredSize(container));
}
}
public PostScriptedFigure() {
super.setLayoutManager(new PostScriptedLayoutManager());
}
#Override
public void setLayoutManager(LayoutManager manager) {
// prevent from setting wrong layout manager
}
public IFigure getMainFigure() {
return mainFigure;
}
public void setMainFigure(IFigure mainFigure) {
if (getMainFigure() != null) {
remove(getMainFigure());
}
this.mainFigure = mainFigure;
add(mainFigure);
}
public IFigure getPostScriptFigure() {
return postScriptFigure;
}
public void setPostScriptFigure(IFigure postScriptFigure) {
if (getPostScriptFigure() != null) {
remove(getPostScriptFigure());
}
this.postScriptFigure = postScriptFigure;
add(postScriptFigure);
}
}